diff options
804 files changed, 32276 insertions, 12653 deletions
diff --git a/Android.bp b/Android.bp index f47ee20ed7b4..33b66b602024 100644 --- a/Android.bp +++ b/Android.bp @@ -669,7 +669,6 @@ java_defaults { // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. "gps_debug.conf", "icu4j-platform-compat-config", - "libcore-platform-compat-config", "protolog.conf.json.gz", "services-platform-compat-config", "documents-ui-compat-config", @@ -1462,7 +1461,7 @@ java_library { ], libs: [ "framework-annotations-lib", - "framework-connectivity", + "framework-connectivity.stubs.module_lib", "unsupportedappusage", ], visibility: [ diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index e08d22ce194c..5acfe6ab6430 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>>); @@ -18,10 +26,8 @@ package android.app.appsearch { } public static final class AppSearchManager.SearchContext.Builder { - ctor @Deprecated public AppSearchManager.SearchContext.Builder(); ctor public AppSearchManager.SearchContext.Builder(@NonNull String); method @NonNull public android.app.appsearch.AppSearchManager.SearchContext build(); - method @Deprecated @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String); } public final class AppSearchResult<ValueType> { @@ -29,6 +35,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 +44,13 @@ 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(); } public static final class AppSearchSchema.BooleanPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { @@ -58,7 +66,6 @@ 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); } public static final class AppSearchSchema.BytesPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { @@ -130,13 +137,15 @@ 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 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> { @@ -167,15 +176,12 @@ package android.app.appsearch { method public int getScore(); method public long getTtlMillis(); method @NonNull public String getUri(); - field @Deprecated public static final String DEFAULT_NAMESPACE = ""; } public static class GenericDocument.Builder<BuilderType extends android.app.appsearch.GenericDocument.Builder> { - ctor @Deprecated public GenericDocument.Builder(@NonNull String, @NonNull String); ctor public GenericDocument.Builder(@NonNull String, @NonNull String, @NonNull String); method @NonNull public android.app.appsearch.GenericDocument build(); method @NonNull public BuilderType setCreationTimestampMillis(long); - method @Deprecated @NonNull public BuilderType setNamespace(@NonNull String); method @NonNull public BuilderType setPropertyBoolean(@NonNull String, @NonNull boolean...); method @NonNull public BuilderType setPropertyBytes(@NonNull String, @NonNull byte[]...); method @NonNull public BuilderType setPropertyDocument(@NonNull String, @NonNull android.app.appsearch.GenericDocument...); @@ -194,25 +200,36 @@ package android.app.appsearch { } public static final class GetByUriRequest.Builder { - ctor @Deprecated public GetByUriRequest.Builder(); ctor public GetByUriRequest.Builder(@NonNull String); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.lang.String...); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.GetByUriRequest build(); - method @Deprecated @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String); + } + + public class GetSchemaResponse { + 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 { @@ -238,12 +255,25 @@ package android.app.appsearch { } public static final class RemoveByUriRequest.Builder { - ctor @Deprecated public RemoveByUriRequest.Builder(); ctor public RemoveByUriRequest.Builder(@NonNull String); method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.lang.String...); method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.RemoveByUriRequest build(); - 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 { @@ -253,20 +283,18 @@ package android.app.appsearch { } public static final class ReportUsageRequest.Builder { - ctor @Deprecated public ReportUsageRequest.Builder(); ctor public ReportUsageRequest.Builder(@NonNull String); method @NonNull public android.app.appsearch.ReportUsageRequest build(); - method @Deprecated @NonNull public android.app.appsearch.ReportUsageRequest.Builder setNamespace(@NonNull String); method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setUri(@NonNull String); method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setUsageTimeMillis(long); } public final class SearchResult { method @NonNull public String getDatabaseName(); - method @Deprecated @NonNull public android.app.appsearch.GenericDocument getDocument(); 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,16 +302,15 @@ 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 { method @NonNull public CharSequence getExactMatch(); - method @Deprecated @NonNull public android.app.appsearch.SearchResult.MatchRange getExactMatchPosition(); method @NonNull public android.app.appsearch.SearchResult.MatchRange getExactMatchRange(); method @NonNull public String getFullText(); method @NonNull public String getPropertyPath(); method @NonNull public CharSequence getSnippet(); - method @Deprecated @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetPosition(); method @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetRange(); } @@ -315,9 +342,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 +356,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 +378,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); @@ -354,8 +388,8 @@ package android.app.appsearch { method @NonNull public java.util.Map<java.lang.String,android.app.appsearch.Migrator> getMigrators(); method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas(); 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 +400,10 @@ 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 +442,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/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java index 0c6b86b68636..977682771ca3 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java @@ -147,22 +147,10 @@ public class AppSearchManager { /** Builder for {@link SearchContext} objects. */ public static final class Builder { - private String mDatabaseName; + private final String mDatabaseName; private boolean mBuilt = false; /** - * TODO(b/181887768): This method exists only for dogfooder transition and must be - * removed. - * - * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This - * method exists only for dogfooder transition and must be removed. - */ - @Deprecated - public Builder() { - mDatabaseName = ""; - } - - /** * Creates a new {@link SearchContext.Builder}. * * <p>{@link AppSearchSession} will create or open a database under the given name. @@ -182,37 +170,6 @@ public class AppSearchManager { mDatabaseName = databaseName; } - /** - * Sets the name of the database associated with {@link AppSearchSession}. - * - * <p>{@link AppSearchSession} will create or open a database under the given name. - * - * <p>Databases with different names are fully separate with distinct types, namespaces, - * and data. - * - * <p>Database name cannot contain {@code '/'}. - * - * <p>If not specified, defaults to the empty string. - * - * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be - * removed. - * - * @param databaseName The name of the database. - * @throws IllegalArgumentException if the databaseName contains {@code '/'}. - * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This - * method exists only for dogfooder transition and must be removed. - */ - @Deprecated - @NonNull - public Builder setDatabaseName(@NonNull String databaseName) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - Objects.requireNonNull(databaseName); - Preconditions.checkArgument( - !databaseName.contains("/"), "Database name cannot contain '/'"); - mDatabaseName = databaseName; - return this; - } - /** Builds a {@link SearchContext} instance. */ @NonNull public SearchContext build() { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java new file mode 100644 index 000000000000..e585d9147895 --- /dev/null +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.appsearch; + +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; +import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.annotation.WorkerThread; +import android.app.appsearch.exceptions.AppSearchException; +import android.os.Bundle; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import com.android.internal.infra.AndroidFuture; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; + +/** + * The helper class for {@link AppSearchSchema} migration. + * + * <p>It will query and migrate {@link GenericDocument} in given type to a new version. + * @hide + */ +public class AppSearchMigrationHelper implements Closeable { + private final IAppSearchManager mService; + private final String mPackageName; + private final String mDatabaseName; + private final int mUserId; + private final File mMigratedFile; + private final Map<String, Integer> mCurrentVersionMap; + private final Map<String, Integer> mFinalVersionMap; + private boolean mAreDocumentsMigrated = false; + + AppSearchMigrationHelper(@NonNull IAppSearchManager service, + @UserIdInt int userId, + @NonNull Map<String, Integer> currentVersionMap, + @NonNull Map<String, Integer> finalVersionMap, + @NonNull String packageName, + @NonNull String databaseName) throws IOException { + mService = Objects.requireNonNull(service); + mCurrentVersionMap = Objects.requireNonNull(currentVersionMap); + mFinalVersionMap = Objects.requireNonNull(finalVersionMap); + mPackageName = Objects.requireNonNull(packageName); + mDatabaseName = Objects.requireNonNull(databaseName); + mUserId = userId; + mMigratedFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null); + } + + /** + * Queries all documents that need to be migrated to a different version and transform + * documents to that version by passing them to the provided {@link Migrator}. + * + * <p>The method will be executed on the executor provided to + * {@link AppSearchSession#setSchema}. + * + * @param schemaType The schema type that needs to be updated and whose {@link GenericDocument} + * need to be migrated. + * @param migrator The {@link Migrator} that will upgrade or downgrade a {@link + * GenericDocument} to new version. + */ + @WorkerThread + public void queryAndTransform(@NonNull String schemaType, @NonNull Migrator migrator) + throws IOException, AppSearchException, InterruptedException, ExecutionException { + File queryFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null); + try (ParcelFileDescriptor fileDescriptor = + ParcelFileDescriptor.open(queryFile, MODE_WRITE_ONLY)) { + AndroidFuture<AppSearchResult<Void>> androidFuture = new AndroidFuture<>(); + mService.writeQueryResultsToFile(mPackageName, mDatabaseName, + fileDescriptor, + /*queryExpression=*/ "", + new SearchSpec.Builder() + .addFilterSchemas(schemaType) + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .build().getBundle(), + mUserId, + new IAppSearchResultCallback.Stub() { + @Override + public void onResult(AppSearchResult result) throws RemoteException { + androidFuture.complete(result); + } + }); + AppSearchResult<Void> result = androidFuture.get(); + if (!result.isSuccess()) { + throw new AppSearchException(result.getResultCode(), result.getErrorMessage()); + } + readAndTransform(queryFile, migrator); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + queryFile.delete(); + } + } + + /** + * Puts all {@link GenericDocument} migrated from the previous call to + * {@link #queryAndTransform} into AppSearch. + * + * <p> This method should be only called once. + * + * @param responseBuilder a SetSchemaResponse builder whose result will be returned by this + * function with any + * {@link android.app.appsearch.SetSchemaResponse.MigrationFailure} + * added in. + * @return the {@link SetSchemaResponse} for {@link AppSearchSession#setSchema} call. + */ + @NonNull + AppSearchResult<SetSchemaResponse> putMigratedDocuments( + @NonNull SetSchemaResponse.Builder responseBuilder) { + if (!mAreDocumentsMigrated) { + return AppSearchResult.newSuccessfulResult(responseBuilder.build()); + } + try (ParcelFileDescriptor fileDescriptor = + ParcelFileDescriptor.open(mMigratedFile, MODE_READ_ONLY)) { + AndroidFuture<AppSearchResult<List<Bundle>>> androidFuture = new AndroidFuture<>(); + mService.putDocumentsFromFile(mPackageName, mDatabaseName, fileDescriptor, mUserId, + new IAppSearchResultCallback.Stub() { + @Override + public void onResult(AppSearchResult result) throws RemoteException { + androidFuture.complete(result); + } + }); + AppSearchResult<List<Bundle>> result = androidFuture.get(); + if (!result.isSuccess()) { + return AppSearchResult.newFailedResult(result); + } + List<Bundle> migratedFailureBundles = result.getResultValue(); + for (int i = 0; i < migratedFailureBundles.size(); i++) { + responseBuilder.addMigrationFailure( + new SetSchemaResponse.MigrationFailure(migratedFailureBundles.get(i))); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (Throwable t) { + return AppSearchResult.throwableToFailedResult(t); + } finally { + mMigratedFile.delete(); + } + return AppSearchResult.newSuccessfulResult(responseBuilder.build()); + } + + /** + * Reads all saved {@link GenericDocument}s from the given {@link File}. + * + * <p>Transforms those {@link GenericDocument}s to the final version. + * + * <p>Save migrated {@link GenericDocument}s to the {@link #mMigratedFile}. + */ + private void readAndTransform(@NonNull File file, @NonNull Migrator migrator) + throws IOException { + try (DataInputStream inputStream = new DataInputStream(new FileInputStream(file)); + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream( + mMigratedFile, /*append=*/ true))) { + GenericDocument document; + while (true) { + try { + document = readDocumentFromInputStream(inputStream); + } catch (EOFException e) { + break; + // Nothing wrong. We just finished reading. + } + + int currentVersion = mCurrentVersionMap.get(document.getSchemaType()); + int finalVersion = mFinalVersionMap.get(document.getSchemaType()); + + GenericDocument newDocument; + if (currentVersion < finalVersion) { + newDocument = migrator.onUpgrade(currentVersion, finalVersion, document); + } else { + // currentVersion == finalVersion case won't trigger migration and get here. + newDocument = migrator.onDowngrade(currentVersion, finalVersion, document); + } + writeBundleToOutputStream(outputStream, newDocument.getBundle()); + } + mAreDocumentsMigrated = true; + } + } + + /** + * Reads the {@link Bundle} of a {@link GenericDocument} from given {@link DataInputStream}. + * + * @param inputStream The inputStream to read from + * + * @throws IOException on read failure. + * @throws EOFException if {@link java.io.InputStream} reaches the end. + */ + @NonNull + public static GenericDocument readDocumentFromInputStream( + @NonNull DataInputStream inputStream) throws IOException { + int length = inputStream.readInt(); + if (length == 0) { + throw new EOFException(); + } + byte[] serializedMessage = new byte[length]; + inputStream.read(serializedMessage); + + Parcel parcel = Parcel.obtain(); + try { + parcel.unmarshall(serializedMessage, 0, serializedMessage.length); + parcel.setDataPosition(0); + Bundle bundle = parcel.readBundle(); + return new GenericDocument(bundle); + } finally { + parcel.recycle(); + } + } + + /** + * Serializes a {@link Bundle} and writes into the given {@link DataOutputStream}. + */ + public static void writeBundleToOutputStream( + @NonNull DataOutputStream outputStream, @NonNull Bundle bundle) + throws IOException { + Parcel parcel = Parcel.obtain(); + try { + parcel.writeBundle(bundle); + byte[] serializedMessage = parcel.marshall(); + outputStream.writeInt(serializedMessage.length); + outputStream.write(serializedMessage); + } finally { + parcel.recycle(); + } + } + + @Override + public void close() throws IOException { + mMigratedFile.delete(); + } +} 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..0f6468a62794 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -19,6 +19,8 @@ package android.app.appsearch; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.appsearch.exceptions.AppSearchException; +import android.app.appsearch.util.SchemaMigrationUtil; import android.os.Bundle; import android.os.ParcelableException; import android.os.RemoteException; @@ -26,14 +28,17 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; import java.io.Closeable; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -45,15 +50,16 @@ 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; - /** * Creates a search session for the client, defined by the {@code userId} and * {@code packageName}. @@ -111,7 +117,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 +127,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()); @@ -139,23 +149,65 @@ public final class AppSearchSession implements Closeable { } schemasPackageAccessibleBundles.put(entry.getKey(), packageIdentifierBundles); } + + // No need to trigger migration if user never set migrator + if (request.getMigrators().isEmpty()) { + setSchemaNoMigrations( + request, + schemaBundles, + schemasPackageAccessibleBundles, + callbackExecutor, + callback); + return; + } + try { + // Migration process + // 1. Generate the current and the final version map. + // TODO(b/182855402) Release binder thread and move the heavy work into worker thread. + AndroidFuture<AppSearchResult<GetSchemaResponse>> future = new AndroidFuture<>(); + getSchema(callbackExecutor, future::complete); + AppSearchResult<GetSchemaResponse> getSchemaResult = future.get(); + if (!getSchemaResult.isSuccess()) { + callback.accept(AppSearchResult.newFailedResult(getSchemaResult)); + return; + } + GetSchemaResponse getSchemaResponse = getSchemaResult.getResultValue(); + Set<AppSearchSchema> currentSchemas = getSchemaResponse.getSchemas(); + Map<String, Integer> currentVersionMap = + SchemaMigrationUtil.buildVersionMap(currentSchemas, + getSchemaResponse.getVersion()); + Map<String, Integer> finalVersionMap = + SchemaMigrationUtil.buildVersionMap(request.getSchemas(), request.getVersion()); + + // 2. SetSchema with forceOverride=false, to retrieve the list of incompatible/deleted + // types. mService.setSchema( mPackageName, mDatabaseName, schemaBundles, new ArrayList<>(request.getSchemasNotDisplayedBySystem()), schemasPackageAccessibleBundles, - request.isForceOverride(), + /*forceOverride=*/ false, 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. - AppSearchResult.newSuccessfulResult( - new SetSchemaResponse.Builder().build())); + // TODO(b/183177268): once migration is implemented, run + // it on workExecutor. + try { + Bundle bundle = (Bundle) result.getResultValue(); + SetSchemaResponse setSchemaResponse = + new SetSchemaResponse(bundle); + setSchemaMigration( + request, setSchemaResponse, schemaBundles, + schemasPackageAccessibleBundles, currentVersionMap, + finalVersionMap, callback); + } catch (Throwable t) { + callback.accept(AppSearchResult.throwableToFailedResult(t)); + } } else { callback.accept(result); } @@ -165,6 +217,8 @@ public final class AppSearchSession implements Closeable { mIsMutated = true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (Throwable t) { + callback.accept(AppSearchResult.throwableToFailedResult(t)); } } @@ -176,7 +230,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 +243,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 +383,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 +402,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 +522,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 +627,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. */ @@ -555,4 +660,159 @@ public final class AppSearchSession implements Closeable { } } } + + /** + * Set schema to Icing for no-migration scenario. + * + * <p>We only need one time {@link #setSchema} call for no-migration scenario by using the + * forceoverride in the request. + */ + private void setSchemaNoMigrations(@NonNull SetSchemaRequest request, + @NonNull List<Bundle> schemaBundles, + @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) { + try { + mService.setSchema( + mPackageName, + mDatabaseName, + schemaBundles, + new ArrayList<>(request.getSchemasNotDisplayedBySystem()), + schemasPackageAccessibleBundles, + request.isForceOverride(), + mUserId, + request.getVersion(), + new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + executor.execute(() -> { + if (result.isSuccess()) { + try { + SetSchemaResponse setSchemaResponse = + new SetSchemaResponse( + (Bundle) result.getResultValue()); + if (!request.isForceOverride()) { + // Throw exception if there is any deleted types or + // incompatible types. That's the only case we swallowed + // in the AppSearchImpl#setSchema(). + checkDeletedAndIncompatible( + setSchemaResponse.getDeletedTypes(), + setSchemaResponse.getIncompatibleTypes()); + } + callback.accept(AppSearchResult + .newSuccessfulResult(setSchemaResponse)); + } catch (Throwable t) { + callback.accept(AppSearchResult.throwableToFailedResult(t)); + } + } else { + callback.accept(result); + } + }); + } + }); + mIsMutated = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set schema to Icing for migration scenario. + * + * <p>First time {@link #setSchema} call with forceOverride is false gives us all incompatible + * changes. After trigger migrations, the second time call {@link #setSchema} will actually + * apply the changes. + * + * @param setSchemaResponse the result of the first setSchema call with forceOverride=false. + */ + private void setSchemaMigration(@NonNull SetSchemaRequest request, + @NonNull SetSchemaResponse setSchemaResponse, + @NonNull List<Bundle> schemaBundles, + @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, + @NonNull Map<String, Integer> currentVersionMap, Map<String, Integer> finalVersionMap, + @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) + throws AppSearchException, IOException, RemoteException, ExecutionException, + InterruptedException { + // 1. If forceOverride is false, check that all incompatible types will be migrated. + // If some aren't we must throw an error, rather than proceeding and deleting those + // types. + if (!request.isForceOverride()) { + Set<String> unmigratedTypes = SchemaMigrationUtil.getUnmigratedIncompatibleTypes( + setSchemaResponse.getIncompatibleTypes(), + request.getMigrators(), + currentVersionMap, + finalVersionMap); + // check if there are any unmigrated types or deleted types. If there are, we will throw + // an exception. + // Since the force override is false, the schema will not have been set if there are any + // incompatible or deleted types. + checkDeletedAndIncompatible(setSchemaResponse.getDeletedTypes(), + unmigratedTypes); + } + + try (AppSearchMigrationHelper migrationHelper = + new AppSearchMigrationHelper(mService, mUserId, currentVersionMap, + finalVersionMap, mPackageName, mDatabaseName)) { + Map<String, Migrator> migratorMap = request.getMigrators(); + + // 2. Trigger migration for all migrators. + // TODO(b/177266929) trigger migration for all types together rather than separately. + Set<String> migratedTypes = new ArraySet<>(); + for (Map.Entry<String, Migrator> entry : migratorMap.entrySet()) { + String schemaType = entry.getKey(); + Migrator migrator = entry.getValue(); + if (SchemaMigrationUtil.shouldTriggerMigration( + schemaType, migrator, currentVersionMap, finalVersionMap)) { + migrationHelper.queryAndTransform(schemaType, migrator); + migratedTypes.add(schemaType); + } + } + + // 3. SetSchema a second time with forceOverride=true if the first attempted failed. + if (!setSchemaResponse.getIncompatibleTypes().isEmpty() + || !setSchemaResponse.getDeletedTypes().isEmpty()) { + AndroidFuture<AppSearchResult<SetSchemaResponse>> future = new AndroidFuture<>(); + // only trigger second setSchema() call if the first one is fail. + mService.setSchema( + mPackageName, + mDatabaseName, + schemaBundles, + new ArrayList<>(request.getSchemasNotDisplayedBySystem()), + schemasPackageAccessibleBundles, + /*forceOverride=*/ true, + mUserId, + request.getVersion(), + new IAppSearchResultCallback.Stub() { + @Override + public void onResult(AppSearchResult result) throws RemoteException { + future.complete(result); + } + }); + AppSearchResult<SetSchemaResponse> secondSetSchemaResult = future.get(); + if (!secondSetSchemaResult.isSuccess()) { + // we failed to set the schema in second time with force override = true, which + // is an impossible case. Since we only swallow the incompatible error in the + // first setSchema call, all other errors will be thrown at the first time. + callback.accept(secondSetSchemaResult); + return; + } + } + + SetSchemaResponse.Builder responseBuilder = setSchemaResponse.toBuilder() + .addMigratedTypes(migratedTypes); + callback.accept(migrationHelper.putMigratedDocuments(responseBuilder)); + } + } + + /** Checks the setSchema() call won't delete any types or has incompatible types. */ + //TODO(b/177266929) move this method to util + private void checkDeletedAndIncompatible(Set<String> deletedTypes, + Set<String> incompatibleTypes) + throws AppSearchException { + if (!deletedTypes.isEmpty() || !incompatibleTypes.isEmpty()) { + String newMessage = "Schema is incompatible." + + "\n Deleted types: " + deletedTypes + + "\n Incompatible types: " + incompatibleTypes; + throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage); + } + } } 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..48c397f324ec 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -21,6 +21,7 @@ import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchResult; import android.app.appsearch.IAppSearchBatchResultCallback; import android.app.appsearch.IAppSearchResultCallback; +import android.os.ParcelFileDescriptor; import com.android.internal.infra.AndroidFuture; parcelable SearchResults; @@ -41,7 +42,8 @@ interface IAppSearchManager { * incompatible documents will be deleted. * @param userId Id of the calling user * @param callback {@link IAppSearchResultCallback#onResult} will be called with an - * {@link AppSearchResult}<{@link Void}>. + * {@link AppSearchResult}<{@link Bundle}>, where the value are + * {@link SetSchemaResponse} bundle. */ void setSchema( in String packageName, @@ -51,6 +53,7 @@ interface IAppSearchManager { in Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, in int userId, + in int schemaVersion, in IAppSearchResultCallback callback); /** @@ -60,8 +63,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 +72,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. @@ -174,6 +191,47 @@ interface IAppSearchManager { void invalidateNextPageToken(in long nextPageToken, in int userId); /** + * Searches a document based on a given specifications. + * + * <p>Documents will be save to the given ParcelFileDescriptor + * + * @param packageName The name of the package to query over. + * @param databaseName The databaseName this query for. + * @param fileDescriptor The ParcelFileDescriptor where documents should be written to. + * @param queryExpression String to search for. + * @param searchSpecBundle SearchSpec bundle. + * @param userId Id of the calling user. + * @param callback {@link IAppSearchResultCallback#onResult} will be called with an + * {@link AppSearchResult}<{@code null}>. + */ + void writeQueryResultsToFile( + in String packageName, + in String databaseName, + in ParcelFileDescriptor fileDescriptor, + in String queryExpression, + in Bundle searchSpecBundle, + in int userId, + in IAppSearchResultCallback callback); + + /** + * Inserts documents from the given file into the index. + * + * @param packageName The name of the package that owns this document. + * @param databaseName The name of the database where this document lives. + * @param fileDescriptor The ParcelFileDescriptor where documents should be read from. + * @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 + * MigrationFailure bundles. + */ + void putDocumentsFromFile( + in String packageName, + in String databaseName, + in ParcelFileDescriptor fileDescriptor, + in int userId, + in IAppSearchResultCallback callback); + + /** * Reports usage of a particular document by URI and namespace. * * <p>A usage report represents an event in which a user interacted with or viewed a document. @@ -190,6 +248,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 +259,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..a8048dc5a4c4 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java @@ -17,7 +17,6 @@ package android.app.appsearch; import android.annotation.IntDef; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.exceptions.IllegalSchemaException; @@ -46,7 +45,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,11 +76,6 @@ public final class AppSearchSchema { return mBundle.getString(SCHEMA_TYPE_FIELD, ""); } - /** Returns the version of this {@link AppSearchSchema}. */ - public @IntRange(from = 0) int getVersion() { - return mBundle.getInt(VERSION_FIELD); - } - /** * Returns the list of {@link PropertyConfig}s that are part of this schema. * @@ -115,15 +108,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 +121,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,42 +143,6 @@ 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 - */ - @NonNull - public AppSearchSchema.Builder setVersion(@IntRange(from = 0) int version) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkArgumentNonnegative(version); - mVersion = version; - return this; - } - - /** * Constructs a new {@link AppSearchSchema} from the contents of this builder. * * <p>After calling this method, the builder must no longer be used. @@ -199,7 +152,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/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java index 4ce95ea358f4..8c9d950abe25 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java @@ -46,15 +46,6 @@ import java.util.Set; public class GenericDocument { private static final String TAG = "AppSearchGenericDocumen"; - /** - * The default empty namespace. - * - * <p>TODO(b/181887768): This exists only for dogfooder transition and must be removed. - * - * @deprecated This exists only for dogfooder transition and must be removed. - */ - @Deprecated public static final String DEFAULT_NAMESPACE = ""; - /** The maximum number of elements in a repeatable field. */ private static final int MAX_REPEATED_PROPERTY_LENGTH = 100; @@ -585,41 +576,6 @@ public class GenericDocument { * * <p>Once {@link #build} is called, the instance can no longer be used. * - * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be - * removed. - * - * @param uri the URI to set for the {@link GenericDocument}. - * @param schemaType the {@link AppSearchSchema} type of the {@link GenericDocument}. The - * provided {@code schemaType} must be defined using {@link AppSearchSession#setSchema} - * prior to inserting a document of this {@code schemaType} into the AppSearch index - * using {@link AppSearchSession#put}. Otherwise, the document will be rejected by - * {@link AppSearchSession#put} with result code {@link - * AppSearchResult#RESULT_NOT_FOUND}. - * @deprecated Please supply the namespace in {@link #Builder(String, String, String)} - * instead. This method exists only for dogfooder transition and must be removed. - */ - @Deprecated - @SuppressWarnings("unchecked") - public Builder(@NonNull String uri, @NonNull String schemaType) { - Preconditions.checkNotNull(uri); - Preconditions.checkNotNull(schemaType); - mBuilderTypeInstance = (BuilderType) this; - mBundle.putString(GenericDocument.URI_FIELD, uri); - mBundle.putString(GenericDocument.SCHEMA_TYPE_FIELD, schemaType); - mBundle.putString(GenericDocument.NAMESPACE_FIELD, DEFAULT_NAMESPACE); - // Set current timestamp for creation timestamp by default. - mBundle.putLong( - GenericDocument.CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis()); - mBundle.putLong(GenericDocument.TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS); - mBundle.putInt(GenericDocument.SCORE_FIELD, DEFAULT_SCORE); - mBundle.putBundle(PROPERTIES_FIELD, mProperties); - } - - /** - * Creates a new {@link GenericDocument.Builder}. - * - * <p>Once {@link #build} is called, the instance can no longer be used. - * * <p>URIs are unique within a namespace. * * <p>The number of namespaces per app should be kept small for efficiency reasons. @@ -651,29 +607,6 @@ public class GenericDocument { } /** - * Sets the app-defined namespace this document resides in. No special values are reserved - * or understood by the infrastructure. - * - * <p>URIs are unique within a namespace. - * - * <p>The number of namespaces per app should be kept small for efficiency reasons. - * - * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be - * removed. - * - * @throws IllegalStateException if the builder has already been used. - * @deprecated Please supply the namespace in {@link #Builder(String, String, String)} - * instead. This method exists only for dogfooder transition and must be removed. - */ - @Deprecated - @NonNull - public BuilderType setNamespace(@NonNull String namespace) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace); - return mBuilderTypeInstance; - } - - /** * Sets the score of the {@link GenericDocument}. * * <p>The score is a query-independent measure of the document's quality, relative to other diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java index 6881a27d6846..1719e14b01e3 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java @@ -107,49 +107,17 @@ public final class GetByUriRequest { * <p>Once {@link #build} is called, the instance can no longer be used. */ public static final class Builder { - private String mNamespace; + private final String mNamespace; private final Set<String> mUris = new ArraySet<>(); private final Map<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>(); private boolean mBuilt = false; - /** - * TODO(b/181887768): This method exists only for dogfooder transition and must be removed. - * - * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method - * exists only for dogfooder transition and must be removed. - */ - @Deprecated - public Builder() { - mNamespace = GenericDocument.DEFAULT_NAMESPACE; - } - /** Creates a {@link GetByUriRequest.Builder} instance. */ public Builder(@NonNull String namespace) { mNamespace = Preconditions.checkNotNull(namespace); } /** - * Sets the namespace to retrieve documents for. - * - * <p>If this is not called, the namespace defaults to an empty string. - * - * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be - * removed. - * - * @throws IllegalStateException if the builder has already been used. - * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method - * exists only for dogfooder transition and must - */ - @Deprecated - @NonNull - public Builder setNamespace(@NonNull String namespace) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(namespace); - mNamespace = namespace; - return this; - } - - /** * Adds one or more URIs to the request. * * @throws IllegalStateException if the builder has already been used. 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..1f56ef3588b1 --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.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.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.Set; + +/** The response class of {@link AppSearchSession#getSchema} */ +public class GetSchemaResponse { + private static final String VERSION_FIELD = "version"; + private static final String SCHEMAS_FIELD = "schemas"; + + private final Bundle mBundle; + + GetSchemaResponse(@NonNull Bundle bundle) { + mBundle = Preconditions.checkNotNull(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}. + * + * <p>It is inefficient to call this method repeatedly. + */ + @NonNull + public Set<AppSearchSchema> getSchemas() { + ArrayList<Bundle> schemaBundles = mBundle.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; + } + + /** 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/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java index 455cf3a26b50..8da68c0b4898 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java @@ -59,48 +59,16 @@ public final class RemoveByUriRequest { * <p>Once {@link #build} is called, the instance can no longer be used. */ public static final class Builder { - private String mNamespace; + private final String mNamespace; private final Set<String> mUris = new ArraySet<>(); private boolean mBuilt = false; - /** - * TODO(b/181887768): This method exists only for dogfooder transition and must be removed. - * - * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method - * exists only for dogfooder transition and must be removed. - */ - @Deprecated - public Builder() { - mNamespace = GenericDocument.DEFAULT_NAMESPACE; - } - /** Creates a {@link RemoveByUriRequest.Builder} instance. */ public Builder(@NonNull String namespace) { mNamespace = Preconditions.checkNotNull(namespace); } /** - * Sets the namespace to remove documents for. - * - * <p>If this is not set, it defaults to an empty string. - * - * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be - * removed. - * - * @throws IllegalStateException if the builder has already been used. - * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method - * exists only for dogfooder transition and must - */ - @Deprecated - @NonNull - public Builder setNamespace(@NonNull String namespace) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(namespace); - mNamespace = namespace; - return this; - } - - /** * Adds one or more URIs to the request. * * @throws IllegalStateException if the builder has already been used. 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/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java index 2cd08c631006..646e73c24bd9 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java @@ -62,49 +62,17 @@ public final class ReportUsageRequest { /** Builder for {@link ReportUsageRequest} objects. */ public static final class Builder { - private String mNamespace; + private final String mNamespace; private String mUri; private Long mUsageTimeMillis; private boolean mBuilt = false; - /** - * TODO(b/181887768): This method exists only for dogfooder transition and must be removed. - * - * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method - * exists only for dogfooder transition and must be removed. - */ - @Deprecated - public Builder() { - mNamespace = GenericDocument.DEFAULT_NAMESPACE; - } - /** Creates a {@link ReportUsageRequest.Builder} instance. */ public Builder(@NonNull String namespace) { mNamespace = Preconditions.checkNotNull(namespace); } /** - * Sets which namespace the document being used belongs to. - * - * <p>If this is not set, it defaults to an empty string. - * - * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be - * removed. - * - * @throws IllegalStateException if the builder has already been used - * @deprecated Please supply the namespace in {@link #Builder(String)} instead. This method - * exists only for dogfooder transition and must - */ - @Deprecated - @NonNull - public ReportUsageRequest.Builder setNamespace(@NonNull String namespace) { - Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(namespace); - mNamespace = namespace; - return this; - } - - /** * Sets the URI of the document being used. * * <p>This field is required. 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..55a228d94c10 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; @@ -67,13 +68,6 @@ public final class SearchResult { return mBundle; } - /** @deprecated TODO(b/181887768): This method exists only for dogfooder transition. */ - @NonNull - @Deprecated - public GenericDocument getDocument() { - return getGenericDocument(); - } - /** * Contains the matching {@link GenericDocument}. * @@ -131,6 +125,36 @@ 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 + * #getGenericDocument()} + * <li>{@link SearchSpec#RANKING_STRATEGY_CREATION_TIMESTAMP} - the value returned by calling + * {@link GenericDocument#getCreationTimestampMillis()} on the document returned by {@link + * #getGenericDocument()} + * <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 #getGenericDocument()} + * <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 + * #getGenericDocument()} + * </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 +197,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}. * @@ -316,13 +348,6 @@ public final class SearchResult { return mFullText; } - /** @deprecated TODO(b/181887768): This method exists only for dogfooder transition. */ - @NonNull - @Deprecated - public MatchRange getExactMatchPosition() { - return getExactMatchRange(); - } - /** * Gets the exact {@link MatchRange} corresponding to the given entry. * @@ -349,13 +374,6 @@ public final class SearchResult { return getSubstring(getExactMatchRange()); } - /** @deprecated TODO(b/181887768): This method exists only for dogfooder transition. */ - @NonNull - @Deprecated - public MatchRange getSnippetPosition() { - return getSnippetRange(); - } - /** * Gets the snippet {@link MatchRange} corresponding to the given entry. * 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..1324451f47f9 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. */ @@ -105,16 +109,6 @@ public final class SetSchemaRequest { } /** - * 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 - @NonNull - public Set<String> getSchemasNotVisibleToSystemUi() { - return getSchemasNotDisplayedBySystem(); - } - - /** * Returns all the schema types that are opted out of being displayed and visible on any system * UI surface. */ @@ -166,6 +160,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 +178,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; /** @@ -211,17 +212,6 @@ public final class SetSchemaRequest { } /** - * 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 - @NonNull - public Builder setSchemaTypeVisibilityForSystemUi( - @NonNull String schemaType, boolean displayed) { - return setSchemaTypeDisplayedBySystem(schemaType, displayed); - } - - /** * Sets whether or not documents from the provided {@code schemaType} will be displayed and * visible on any system UI surface. * @@ -323,6 +313,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 +343,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 +406,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..c9473bdeff32 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 @@ -70,7 +70,7 @@ public final class SchemaMigrationUtil { // 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)) { + || !migrator.shouldMigrate(currentVersion, finalVersion)) { unmigratedSchemaTypes.add(unmigratedSchemaType); } } @@ -102,16 +102,17 @@ public final class SchemaMigrationUtil { + schemaType + ", but the schema doesn't exist in the request."); } - return migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion); + return migrator.shouldMigrate(currentVersion, finalVersion); } /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */ + //TODO(b/182620003) remove this method once support migrate to another type @NonNull public static Map<String, Integer> buildVersionMap( - @NonNull Collection<AppSearchSchema> schemas) { + @NonNull Collection<AppSearchSchema> schemas, int version) { Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size()); for (AppSearchSchema currentSchema : schemas) { - currentVersionMap.put(currentSchema.getSchemaType(), currentSchema.getVersion()); + currentVersionMap.put(currentSchema.getSchemaType(), version); } 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..91ed6cd4a638 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -21,19 +21,23 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.appsearch.AppSearchBatchResult; +import android.app.appsearch.AppSearchMigrationHelper; 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; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; +import android.app.appsearch.SetSchemaResponse; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Bundle; +import android.os.ParcelFileDescriptor; import android.os.ParcelableException; import android.os.RemoteException; import android.os.UserHandle; @@ -48,6 +52,11 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.appsearch.external.localstorage.AppSearchImpl; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -96,6 +105,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); @@ -123,15 +133,16 @@ public class AppSearchManagerService extends SystemService { schemasPackageAccessible.put(entry.getKey(), packageIdentifiers); } AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); - impl.setSchema( + SetSchemaResponse setSchemaResponse = impl.setSchema( packageName, databaseName, schemas, schemasNotDisplayedBySystem, schemasPackageAccessible, - forceOverride); - invokeCallbackOnResult( - callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); + forceOverride, + schemaVersion); + invokeCallbackOnResult(callback, + AppSearchResult.newSuccessfulResult(setSchemaResponse.getBundle())); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { @@ -156,13 +167,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 { @@ -374,12 +407,105 @@ public class AppSearchManagerService extends SystemService { } @Override + public void writeQueryResultsToFile( + @NonNull String packageName, + @NonNull String databaseName, + @NonNull ParcelFileDescriptor fileDescriptor, + @NonNull String queryExpression, + @NonNull Bundle searchSpecBundle, + @UserIdInt int userId, + @NonNull IAppSearchResultCallback callback) { + int callingUid = Binder.getCallingUid(); + int callingUserId = handleIncomingUser(userId, callingUid); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(callingUserId); + // we don't need to append the file. The file is always brand new. + try (DataOutputStream outputStream = new DataOutputStream( + new FileOutputStream(fileDescriptor.getFileDescriptor()))) { + SearchResultPage searchResultPage = impl.query( + packageName, + databaseName, + queryExpression, + new SearchSpec(searchSpecBundle)); + while (!searchResultPage.getResults().isEmpty()) { + for (int i = 0; i < searchResultPage.getResults().size(); i++) { + AppSearchMigrationHelper.writeBundleToOutputStream( + outputStream, searchResultPage.getResults().get(i) + .getGenericDocument().getBundle()); + } + searchResultPage = impl.getNextPage(searchResultPage.getNextPageToken()); + } + } + invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); + } catch (Throwable t) { + invokeCallbackOnError(callback, t); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override + public void putDocumentsFromFile( + @NonNull String packageName, + @NonNull String databaseName, + @NonNull ParcelFileDescriptor fileDescriptor, + @UserIdInt int userId, + @NonNull IAppSearchResultCallback callback) { + int callingUid = Binder.getCallingUid(); + int callingUserId = handleIncomingUser(userId, callingUid); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(callingUserId); + + GenericDocument document; + ArrayList<Bundle> migrationFailureBundles = new ArrayList<>(); + try (DataInputStream inputStream = new DataInputStream( + new FileInputStream(fileDescriptor.getFileDescriptor()))) { + while (true) { + try { + document = AppSearchMigrationHelper + .readDocumentFromInputStream(inputStream); + } catch (EOFException e) { + // nothing wrong, we just finish the reading. + break; + } + try { + impl.putDocument(packageName, databaseName, document, /*logger=*/ null); + } catch (Throwable t) { + migrationFailureBundles.add( + new SetSchemaResponse.MigrationFailure.Builder() + .setNamespace(document.getNamespace()) + .setSchemaType(document.getSchemaType()) + .setUri(document.getUri()) + .setAppSearchResult( + AppSearchResult.throwableToFailedResult(t)) + .build().getBundle()); + } + } + } + impl.persistToDisk(); + invokeCallbackOnResult(callback, + AppSearchResult.newSuccessfulResult(migrationFailureBundles)); + } catch (Throwable t) { + invokeCallbackOnError(callback, t); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override public void reportUsage( @NonNull String packageName, @NonNull String databaseName, @NonNull String namespace, @NonNull String uri, long usageTimeMillis, + boolean systemUsage, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { Objects.requireNonNull(databaseName); @@ -391,9 +517,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/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index 8c953d12fd98..cacf8801a8bb 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -25,7 +25,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.Environment; import android.os.UserHandle; -import android.os.storage.StorageManager; import android.util.SparseArray; import com.android.internal.R; @@ -130,11 +129,7 @@ public final class ImplInstanceManager { private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) { // See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs - //TODO(b/177685938):Switch from getDataUserCePackageDirectory to getDataSystemCeDirectory - File userCeDir = - Environment.getDataUserCePackageDirectory( - StorageManager.UUID_PRIVATE_INTERNAL, userId, context.getPackageName()); - return new File(userCeDir, APP_SEARCH_DIR); + return new File(Environment.getDataSystemCeDirectory(userId), APP_SEARCH_DIR); } /** 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..1ed26d670f36 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. */ @@ -152,7 +156,7 @@ public class VisibilityStore { AppSearchImpl.createPrefix(PACKAGE_NAME, DATABASE_NAME); /** Namespace of documents that contain visibility settings */ - private static final String NAMESPACE = GenericDocument.DEFAULT_NAMESPACE; + private static final String NAMESPACE = ""; /** * Prefix to add to all visibility document uri's. IcingSearchEngine doesn't allow empty uri's. @@ -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 @@ -333,9 +337,9 @@ public class VisibilityStore { Preconditions.checkNotNull(schemasPackageAccessible); // Persist the document - GenericDocument.Builder visibilityDocument = - new GenericDocument.Builder(/*uri=*/ addUriPrefix(prefix), VISIBILITY_TYPE) - .setNamespace(NAMESPACE); + GenericDocument.Builder<?> visibilityDocument = + new GenericDocument.Builder<>( + NAMESPACE, /*uri=*/ addUriPrefix(prefix), VISIBILITY_TYPE); if (!schemasNotPlatformSurfaceable.isEmpty()) { visibilityDocument.setPropertyString( NOT_PLATFORM_SURFACEABLE_PROPERTY, @@ -347,17 +351,16 @@ public class VisibilityStore { for (Map.Entry<String, List<PackageIdentifier>> entry : schemasPackageAccessible.entrySet()) { for (int i = 0; i < entry.getValue().size(); i++) { - GenericDocument packageAccessibleDocument = - new GenericDocument.Builder(/*uri=*/ "", PACKAGE_ACCESSIBLE_TYPE) - .setNamespace(NAMESPACE) - .setPropertyString( - PACKAGE_NAME_PROPERTY, - entry.getValue().get(i).getPackageName()) - .setPropertyBytes( - SHA_256_CERT_PROPERTY, - entry.getValue().get(i).getSha256Certificate()) - .setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, entry.getKey()) - .build(); + GenericDocument packageAccessibleDocument = new GenericDocument.Builder<>( + NAMESPACE, /*uri=*/ "", PACKAGE_ACCESSIBLE_TYPE) + .setPropertyString( + PACKAGE_NAME_PROPERTY, + entry.getValue().get(i).getPackageName()) + .setPropertyBytes( + SHA_256_CERT_PROPERTY, + entry.getValue().get(i).getSha256Certificate()) + .setPropertyString(ACCESSIBLE_SCHEMA_PROPERTY, entry.getKey()) + .build(); packageAccessibleDocuments.add(packageAccessibleDocument); } schemaToPackageIdentifierMap.put(entry.getKey(), new ArraySet<>(entry.getValue())); 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/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 78c5b156bc45..3ea1922547d5 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -18,6 +18,7 @@ package android.app; import android.Manifest; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; @@ -29,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -42,7 +44,9 @@ import com.android.i18n.timezone.ZoneInfoDb; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; +import java.util.Objects; import java.util.WeakHashMap; +import java.util.concurrent.Executor; /** * This class provides access to the system alarm services. These allow you @@ -194,6 +198,15 @@ public class AlarmManager { public static final int FLAG_ALLOW_WHILE_IDLE_COMPAT = 1 << 5; /** + * Flag for alarms: Used to mark prioritized alarms. These alarms will get to execute while idle + * and can be sent separately from other alarms that may be already due at the time. + * These alarms can be set via + * {@link #setPrioritized(int, long, long, String, Executor, OnAlarmListener)} + * @hide + */ + public static final int FLAG_PRIORITIZE = 1 << 6; + + /** * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new @@ -227,15 +240,15 @@ public class AlarmManager { final class ListenerWrapper extends IAlarmListener.Stub implements Runnable { final OnAlarmListener mListener; - Handler mHandler; + Executor mExecutor; IAlarmCompleteListener mCompletion; public ListenerWrapper(OnAlarmListener listener) { mListener = listener; } - public void setHandler(Handler h) { - mHandler = h; + void setExecutor(Executor e) { + mExecutor = e; } public void cancel() { @@ -250,7 +263,7 @@ public class AlarmManager { public void doAlarm(IAlarmCompleteListener alarmManager) { mCompletion = alarmManager; - mHandler.post(this); + mExecutor.execute(this); } @Override @@ -368,7 +381,7 @@ public class AlarmManager { */ public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null, - null, null, null); + (Handler) null, null, null); } /** @@ -457,7 +470,7 @@ public class AlarmManager { public void setRepeating(@AlarmType int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, - null, null, null, null, null); + null, null, (Handler) null, null, null); } /** @@ -507,7 +520,7 @@ public class AlarmManager { public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) { setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, - null, null, null, null, null); + null, null, (Handler) null, null, null); } /** @@ -526,6 +539,53 @@ public class AlarmManager { } /** + * Schedule an alarm that is prioritized by the system while the device is in power saving modes + * such as battery saver and device idle (doze). + * + * <p> + * Apps that use this are not guaranteed to get all alarms as requested during power saving + * modes, i.e. the system may still impose restrictions on how frequently these alarms will go + * off for a particular application, like requiring a certain minimum duration be elapsed + * between consecutive alarms. This duration will be normally be in the order of a few minutes. + * + * <p> + * When the system wakes up to deliver these alarms, it may not deliver any of the other pending + * alarms set earlier by the calling app, even the special ones set via + * {@link #setAndAllowWhileIdle(int, long, PendingIntent)} or + * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)}. So the caller should not + * expect these to arrive in any relative order to its other alarms. + * + * @param type type of alarm + * @param windowStartMillis The earliest time, in milliseconds, that the alarm should + * be delivered, expressed in the appropriate clock's units (depending on the alarm + * type). + * @param windowLengthMillis The length of the requested delivery window, + * in milliseconds. The alarm will be delivered no later than this many + * milliseconds after {@code windowStartMillis}. Note that this parameter + * is a <i>duration,</i> not the timestamp of the end of the window. + * @param tag string describing the alarm, used for logging and battery-use + * attribution + * @param listener {@link OnAlarmListener} instance whose + * {@link OnAlarmListener#onAlarm() onAlarm()} method will be + * called when the alarm time is reached. A given OnAlarmListener instance can + * only be the target of a single pending alarm, just as a given PendingIntent + * can only be used with one alarm at a time. + * @param executor {@link Executor} on which to execute the listener's onAlarm() + * callback. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) + public void setPrioritized(@AlarmType int type, long windowStartMillis, long windowLengthMillis, + @NonNull String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(tag); + Objects.requireNonNull(listener); + setImpl(type, windowStartMillis, windowLengthMillis, 0, FLAG_PRIORITIZE, null, listener, + tag, executor, null, null); + } + + /** * Schedule an alarm to be delivered precisely at the stated time. * * <p> @@ -565,7 +625,7 @@ public class AlarmManager { */ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true) public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { - setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null, + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null, null, null); } @@ -645,7 +705,7 @@ public class AlarmManager { @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, - null, null, null, null, info); + null, null, (Handler) null, null, info); } /** @hide */ @@ -654,7 +714,7 @@ public class AlarmManager { public void set(@AlarmType int type, long triggerAtMillis, long windowMillis, long intervalMillis, PendingIntent operation, WorkSource workSource) { setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null, - null, workSource, null); + (Handler) null, workSource, null); } /** @@ -698,6 +758,15 @@ public class AlarmManager { long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener, String listenerTag, Handler targetHandler, WorkSource workSource, AlarmClockInfo alarmClock) { + final Handler handlerToUse = (targetHandler != null) ? targetHandler : mMainThreadHandler; + setImpl(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation, listener, + listenerTag, new HandlerExecutor(handlerToUse), workSource, alarmClock); + } + + private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis, + long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener, + String listenerTag, Executor targetExecutor, WorkSource workSource, + AlarmClockInfo alarmClock) { if (triggerAtMillis < 0) { /* NOTYET if (mAlwaysExact) { @@ -726,9 +795,7 @@ public class AlarmManager { sWrappers.put(listener, new WeakReference<>(recipientWrapper)); } } - - final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler; - recipientWrapper.setHandler(handler); + recipientWrapper.setExecutor(targetExecutor); } try { @@ -834,7 +901,7 @@ public class AlarmManager { public void setInexactRepeating(@AlarmType int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, - null, null, null, null); + null, (Handler) null, null, null); } /** @@ -884,7 +951,7 @@ public class AlarmManager { public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, - operation, null, null, null, null, null); + operation, null, null, (Handler) null, null, null); } /** @@ -945,7 +1012,7 @@ public class AlarmManager { public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation, - null, null, null, null, null); + null, null, (Handler) null, null, null); } /** diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index baec0c3f829b..57293856a9ad 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1497,7 +1497,7 @@ public class JobInfo implements Parcelable { * <ol> * <li>Run as soon as possible</li> * <li>Be less restricted during Doze and battery saver</li> - * <li>Have the same network access as foreground services</li> + * <li>Bypass Doze, app standby, and battery saver network restrictions</li> * <li>Be less likely to be killed than regular jobs</li> * <li>Be subject to background location throttling</li> * </ol> @@ -1518,7 +1518,7 @@ public class JobInfo implements Parcelable { * * <p> * Assuming all constraints remain satisfied (including ideal system load conditions), - * expedited jobs are guaranteed to have a minimum allowed runtime of 1 minute. If your + * expedited jobs will have a maximum execution time of at least 1 minute. If your * app has remaining expedited job quota, then the expedited job <i>may</i> potentially run * longer until remaining quota is used up. Just like with regular jobs, quota is not * consumed while the app is on top and visible to the user. diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 58fc87476f2a..3c9496f557a1 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -23,6 +23,7 @@ import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; import static android.app.AlarmManager.FLAG_IDLE_UNTIL; +import static android.app.AlarmManager.FLAG_PRIORITIZE; import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; import static android.app.AlarmManager.INTERVAL_DAY; import static android.app.AlarmManager.INTERVAL_HOUR; @@ -100,6 +101,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -221,6 +223,7 @@ public class AlarmManagerService extends SystemService { AlarmHandler mHandler; AppWakeupHistory mAppWakeupHistory; AppWakeupHistory mAllowWhileIdleHistory; + private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray(); ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); IBinder.DeathRecipient mListenerDeathRecipient; @@ -432,6 +435,8 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps"; + @VisibleForTesting + static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; @@ -470,6 +475,8 @@ public class AlarmManagerService extends SystemService { // TODO (b/171306433): Change to true by default. private static final boolean DEFAULT_CRASH_NON_CLOCK_APPS = false; + private static final long DEFAULT_PRIORITY_ALARM_DELAY = 9 * 60_000; + // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -525,6 +532,12 @@ public class AlarmManagerService extends SystemService { */ public boolean CRASH_NON_CLOCK_APPS = DEFAULT_CRASH_NON_CLOCK_APPS; + /** + * Minimum delay between two slots that an app can get for their prioritized alarms, while + * the device is in doze. + */ + public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY; + private long mLastAllowWhileIdleWhitelistDuration = -1; Constants() { @@ -662,6 +675,10 @@ public class AlarmManagerService extends SystemService { CRASH_NON_CLOCK_APPS = properties.getBoolean(KEY_CRASH_NON_CLOCK_APPS, DEFAULT_CRASH_NON_CLOCK_APPS); break; + case KEY_PRIORITY_ALARM_DELAY: + PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY, + DEFAULT_PRIORITY_ALARM_DELAY); + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -809,6 +826,11 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_CRASH_NON_CLOCK_APPS, CRASH_NON_CLOCK_APPS); pw.println(); + pw.print(KEY_PRIORITY_ALARM_DELAY); + pw.print("="); + TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw); + pw.println(); + pw.decreaseIndent(); } @@ -1794,6 +1816,11 @@ public class AlarmManagerService extends SystemService { batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage( alarm.sourcePackage, userId, quota) + window; } + } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) { + final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0); + batterySaverPolicyElapsed = (lastDispatch == 0) + ? nowElapsed + : lastDispatch + mConstants.PRIORITY_ALARM_DELAY; } else { // Not allowed. batterySaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY; @@ -1849,6 +1876,12 @@ public class AlarmManagerService extends SystemService { alarm.sourcePackage, userId, quota) + window; deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed()); } + } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) { + final long lastDispatch = mLastPriorityAlarmDispatch.get(alarm.creatorUid, 0); + final long whenAllowed = (lastDispatch == 0) + ? nowElapsed + : lastDispatch + mConstants.PRIORITY_ALARM_DELAY; + deviceIdlePolicyTime = Math.min(whenAllowed, mPendingIdleUntil.getWhenElapsed()); } else { // Not allowed. deviceIdlePolicyTime = mPendingIdleUntil.getWhenElapsed(); @@ -2025,7 +2058,12 @@ public class AlarmManagerService extends SystemService { // make sure the caller is allowed to use the requested kind of alarm, and also // decide what quota and broadcast options to use. Bundle idleOptions = null; - if (exact || allowWhileIdle) { + if ((flags & FLAG_PRIORITIZE) != 0) { + getContext().enforcePermission( + Manifest.permission.SCHEDULE_PRIORITIZED_ALARM, + Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized"); + flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT); + } else if (exact || allowWhileIdle) { final boolean needsPermission; boolean lowerQuota; if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, @@ -2107,6 +2145,7 @@ public class AlarmManagerService extends SystemService { flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags &= ~FLAG_ALLOW_WHILE_IDLE; flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT; + flags &= ~FLAG_PRIORITIZE; idleOptions = null; } @@ -2489,6 +2528,19 @@ public class AlarmManagerService extends SystemService { pw.println("Allow while idle history:"); mAllowWhileIdleHistory.dump(pw, nowELAPSED); + if (mLastPriorityAlarmDispatch.size() > 0) { + pw.println("Last priority alarm dispatches:"); + pw.increaseIndent(); + for (int i = 0; i < mLastPriorityAlarmDispatch.size(); i++) { + pw.print("UID: "); + UserHandle.formatUid(pw, mLastPriorityAlarmDispatch.keyAt(i)); + pw.print(": "); + TimeUtils.formatDuration(mLastPriorityAlarmDispatch.valueAt(i), nowELAPSED, pw); + pw.println(); + } + pw.decreaseIndent(); + } + if (mLog.dump(pw, "Recent problems:")) { pw.println(); } @@ -3303,6 +3355,11 @@ public class AlarmManagerService extends SystemService { mPendingBackgroundAlarms.removeAt(i); } } + for (int i = mLastPriorityAlarmDispatch.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mLastPriorityAlarmDispatch.keyAt(i)) == userHandle) { + mLastPriorityAlarmDispatch.removeAt(i); + } + } if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) { mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); if (mPendingIdleUntil != null) { @@ -4103,6 +4160,7 @@ public class AlarmManagerService extends SystemService { IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); + sdFilter.addAction(Intent.ACTION_UID_REMOVED); getContext().registerReceiver(this, sdFilter); } @@ -4132,6 +4190,9 @@ public class AlarmManagerService extends SystemService { mAllowWhileIdleHistory.removeForUser(userHandle); } return; + case Intent.ACTION_UID_REMOVED: + mLastPriorityAlarmDispatch.delete(uid); + return; case Intent.ACTION_PACKAGE_REMOVED: if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // This package is being updated; don't kill its alarms. @@ -4522,11 +4583,11 @@ public class AlarmManagerService extends SystemService { if (inflight.isBroadcast()) { notifyBroadcastAlarmPendingLocked(alarm.uid); } - if (isAllowedWhileIdleRestricted(alarm)) { - final boolean doze = (mPendingIdleUntil != null); - final boolean batterySaver = (mAppStateTracker != null - && mAppStateTracker.isForceAllAppsStandbyEnabled()); - if (doze || batterySaver) { + final boolean doze = (mPendingIdleUntil != null); + final boolean batterySaver = (mAppStateTracker != null + && mAppStateTracker.isForceAllAppsStandbyEnabled()); + if (doze || batterySaver) { + if (isAllowedWhileIdleRestricted(alarm)) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the // device was in doze or battery saver. mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage, @@ -4538,6 +4599,16 @@ public class AlarmManagerService extends SystemService { return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a)) || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a)); }); + } else if ((alarm.flags & FLAG_PRIORITIZE) != 0) { + mLastPriorityAlarmDispatch.put(alarm.creatorUid, nowELAPSED); + mAlarmStore.updateAlarmDeliveries(a -> { + if (a.creatorUid != alarm.creatorUid + || (alarm.flags & FLAG_PRIORITIZE) == 0) { + return false; + } + return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a)) + || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a)); + }); } if (RECORD_DEVICE_IDLE_ALARMS) { IdleDispatchEntry ent = new IdleDispatchEntry(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 325be1b5c3a5..d94d638a7021 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -891,7 +891,7 @@ class JobConcurrencyManager { } // Only expedited jobs can replace expedited jobs. - if (js.shouldTreatAsExpeditedJob()) { + if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) { // Keep fg/bg user distinction. if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) { // Let any important bg user job replace a bg user expedited job. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index a041f8c0b512..8ac237e63877 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -2187,6 +2187,10 @@ public class JobSchedulerService extends com.android.server.SystemService */ @VisibleForTesting boolean isReadyToBeExecutedLocked(JobStatus job) { + return isReadyToBeExecutedLocked(job, true); + } + + boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) { final boolean jobReady = job.isReady(); if (DEBUG) { @@ -2225,7 +2229,7 @@ public class JobSchedulerService extends com.android.server.SystemService } final boolean jobPending = mPendingJobs.contains(job); - final boolean jobActive = isCurrentlyActiveLocked(job); + final boolean jobActive = rejectActive && isCurrentlyActiveLocked(job); if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index e8bcbfbb2c58..790fae0860de 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -151,6 +151,14 @@ public final class JobServiceContext implements ServiceConnection { /** The absolute maximum amount of time the job can run */ private long mMaxExecutionTimeMillis; + /** + * The stop reason for a pending cancel. If there's not pending cancel, then the value should be + * {@link JobParameters#STOP_REASON_UNDEFINED}. + */ + private int mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + private int mPendingLegacyStopReason; + private String mPendingDebugStopReason; + // Debugging: reason this job was last stopped. public String mStoppedReason; @@ -328,6 +336,7 @@ public final class JobServiceContext implements ServiceConnection { mAvailable = false; mStoppedReason = null; mStoppedTime = 0; + job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob(); return true; } } @@ -625,6 +634,19 @@ public final class JobServiceContext implements ServiceConnection { } return; } + if (mRunningJob.startedAsExpeditedJob + && stopReasonCode == JobParameters.STOP_REASON_QUOTA) { + // EJs should be able to run for at least the min upper limit regardless of quota. + final long earliestStopTimeElapsed = + mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis; + final long nowElapsed = sElapsedRealtimeClock.millis(); + if (nowElapsed < earliestStopTimeElapsed) { + mPendingStopReason = stopReasonCode; + mPendingLegacyStopReason = legacyStopReason; + mPendingDebugStopReason = debugReason; + return; + } + } mParams.setStopReason(stopReasonCode, legacyStopReason, debugReason); if (legacyStopReason == JobParameters.REASON_PREEMPT) { mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : @@ -777,6 +799,23 @@ public final class JobServiceContext implements ServiceConnection { closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping"); break; case VERB_EXECUTING: + if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) { + if (mService.isReadyToBeExecutedLocked(mRunningJob, false)) { + // Job became ready again while we were waiting to stop it (for example, + // the device was temporarily taken off the charger). Ignore the pending + // stop and see what the manager says. + mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + mPendingLegacyStopReason = 0; + mPendingDebugStopReason = null; + } else { + Slog.i(TAG, "JS was waiting to stop this job." + + " Sending onStop: " + getRunningJobNameLocked()); + mParams.setStopReason(mPendingStopReason, mPendingLegacyStopReason, + mPendingDebugStopReason); + sendStopMessageLocked(mPendingDebugStopReason); + break; + } + } final long latestStopTimeElapsed = mExecutionStartTimeElapsed + mMaxExecutionTimeMillis; final long nowElapsed = sElapsedRealtimeClock.millis(); @@ -886,6 +925,9 @@ public final class JobServiceContext implements ServiceConnection { mCancelled = false; service = null; mAvailable = true; + mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED; + mPendingLegacyStopReason = 0; + mPendingDebugStopReason = null; removeOpTimeOutLocked(); mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule); mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType); @@ -972,7 +1014,16 @@ public final class JobServiceContext implements ServiceConnection { pw.print(", "); TimeUtils.formatDuration( (mExecutionStartTimeElapsed + mMaxExecutionTimeMillis) - nowElapsed, pw); - pw.println("]"); + pw.print("]"); + if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) { + pw.print(" Pending stop because "); + pw.print(mPendingStopReason); + pw.print("/"); + pw.print(mPendingLegacyStopReason); + pw.print("/"); + pw.print(mPendingDebugStopReason); + } + pw.println(); pw.decreaseIndent(); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index aa8d98c01853..9cd3a8fa6624 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -541,18 +541,23 @@ public final class JobStore { /** * Write out a tag with data identifying this job's constraints. If the constraint isn't here * it doesn't apply. + * TODO: b/183455312 Update this code to use proper serialization for NetworkRequest, + * because currently store is not including everything (like, UIDs, bandwidth, + * signal strength etc. are lost). */ private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException { out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS); if (jobStatus.hasConnectivityConstraint()) { final NetworkRequest network = jobStatus.getJob().getRequiredNetwork(); + // STOPSHIP b/183071974: improve the scheme for backward compatibility and + // mainline cleanliness. out.attribute(null, "net-capabilities", Long.toString( - BitUtils.packBits(network.networkCapabilities.getCapabilities()))); + BitUtils.packBits(network.getCapabilities()))); out.attribute(null, "net-unwanted-capabilities", Long.toString( - BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities()))); + BitUtils.packBits(network.getUnwantedCapabilities()))); out.attribute(null, "net-transport-types", Long.toString( - BitUtils.packBits(network.networkCapabilities.getTransportTypes()))); + BitUtils.packBits(network.getTransportTypes()))); } if (jobStatus.hasIdleConstraint()) { out.attribute(null, "idle", Boolean.toString(true)); @@ -976,18 +981,23 @@ public final class JobStore { null, "net-unwanted-capabilities"); final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types"); if (netCapabilities != null && netTransportTypes != null) { - final NetworkRequest request = new NetworkRequest.Builder().build(); + final NetworkRequest.Builder builder = new NetworkRequest.Builder() + .clearCapabilities(); final long unwantedCapabilities = netUnwantedCapabilities != null ? Long.parseLong(netUnwantedCapabilities) - : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities()); - + : BitUtils.packBits(builder.build().getUnwantedCapabilities()); // We're okay throwing NFE here; caught by caller - request.networkCapabilities.setCapabilities( - BitUtils.unpackBits(Long.parseLong(netCapabilities)), - BitUtils.unpackBits(unwantedCapabilities)); - request.networkCapabilities.setTransportTypes( - BitUtils.unpackBits(Long.parseLong(netTransportTypes))); - jobBuilder.setRequiredNetwork(request); + for (int capability : BitUtils.unpackBits(Long.parseLong(netCapabilities))) { + builder.addCapability(capability); + } + for (int unwantedCapability : BitUtils.unpackBits( + Long.parseLong(netUnwantedCapabilities))) { + builder.addUnwantedCapability(unwantedCapability); + } + for (int transport : BitUtils.unpackBits(Long.parseLong(netTransportTypes))) { + builder.addTransportType(transport); + } + jobBuilder.setRequiredNetwork(builder.build()); } else { // Read legacy values val = parser.getAttributeValue(null, "connectivity"); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 6e542f346f81..df21d753ea1f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -22,6 +22,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.JobInfo; import android.net.ConnectivityManager; @@ -380,15 +381,23 @@ public final class ConnectivityController extends RestrictingController implemen } } + private static NetworkCapabilities.Builder copyCapabilities( + @NonNull final NetworkRequest request) { + final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); + for (int transport : request.getTransportTypes()) builder.addTransportType(transport); + for (int capability : request.getCapabilities()) builder.addCapability(capability); + return builder; + } + private static boolean isStrictSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { // A restricted job that's out of quota MUST use an unmetered network. if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { - final NetworkCapabilities required = new NetworkCapabilities.Builder( - jobStatus.getJob().getRequiredNetwork().networkCapabilities) - .addCapability(NET_CAPABILITY_NOT_METERED).build(); - return required.satisfiedByNetworkCapabilities(capabilities); + final NetworkCapabilities.Builder builder = + copyCapabilities(jobStatus.getJob().getRequiredNetwork()); + builder.addCapability(NET_CAPABILITY_NOT_METERED); + return builder.build().satisfiedByNetworkCapabilities(capabilities); } else { return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities); } @@ -402,10 +411,10 @@ public final class ConnectivityController extends RestrictingController implemen } // See if we match after relaxing any unmetered request - final NetworkCapabilities relaxed = new NetworkCapabilities.Builder( - jobStatus.getJob().getRequiredNetwork().networkCapabilities) - .removeCapability(NET_CAPABILITY_NOT_METERED).build(); - if (relaxed.satisfiedByNetworkCapabilities(capabilities)) { + final NetworkCapabilities.Builder builder = + copyCapabilities(jobStatus.getJob().getRequiredNetwork()); + builder.removeCapability(NET_CAPABILITY_NOT_METERED); + if (builder.build().satisfiedByNetworkCapabilities(capabilities)) { // TODO: treat this as "maybe" response; need to check quotas return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC; } else { @@ -716,13 +725,6 @@ public final class ConnectivityController extends RestrictingController implemen StateControllerProto.ConnectivityController.REQUESTED_STANDBY_EXCEPTION_UIDS, mRequestedWhitelistJobs.keyAt(i)); } - for (int i = 0; i < mAvailableNetworks.size(); i++) { - Network network = mAvailableNetworks.keyAt(i); - if (network != null) { - network.dumpDebug(proto, - StateControllerProto.ConnectivityController.AVAILABLE_NETWORKS); - } - } for (int i = 0; i < mTrackedJobs.size(); i++) { final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i); for (int j = 0; j < jobs.size(); j++) { @@ -736,12 +738,6 @@ public final class ConnectivityController extends RestrictingController implemen StateControllerProto.ConnectivityController.TrackedJob.INFO); proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID, js.getSourceUid()); - NetworkRequest rn = js.getJob().getRequiredNetwork(); - if (rn != null) { - rn.dumpDebug(proto, - StateControllerProto.ConnectivityController.TrackedJob - .REQUIRED_NETWORK); - } proto.end(jsToken); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 8fa0d3ec06c6..8d999e1e7e36 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -328,6 +328,12 @@ public final class JobStatus { /** The evaluated priority of the job when it started running. */ public int lastEvaluatedPriority; + /** + * Whether or not this particular JobStatus instance was treated as an EJ when it started + * running. This isn't copied over when a job is rescheduled. + */ + public boolean startedAsExpeditedJob = false; + // If non-null, this is work that has been enqueued for the job. public ArrayList<JobWorkItem> pendingWork; @@ -1122,18 +1128,19 @@ public final class JobStatus { */ public boolean canRunInDoze() { return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 - || (shouldTreatAsExpeditedJob() + || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob) && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0); } boolean canRunInBatterySaver() { return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0 - || (shouldTreatAsExpeditedJob() + || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob) && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0); } boolean shouldIgnoreNetworkBlocking() { - return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob(); + return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 + || (shouldTreatAsExpeditedJob() || startedAsExpeditedJob); } /** @return true if the constraint was changed, false otherwise. */ @@ -2032,7 +2039,10 @@ public final class JobStatus { pw.println(serviceInfo != null); if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) { pw.print("readyWithinExpeditedQuota: "); - pw.println(mReadyWithinExpeditedQuota); + pw.print(mReadyWithinExpeditedQuota); + pw.print(" (started as EJ: "); + pw.print(startedAsExpeditedJob); + pw.println(")"); } pw.decreaseIndent(); @@ -2171,9 +2181,6 @@ public final class JobStatus { if (uriPerms != null) { uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS); } - if (job.getRequiredNetwork() != null) { - job.getRequiredNetwork().dumpDebug(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK); - } if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_DOWNLOAD_BYTES, mTotalNetworkDownloadBytes); @@ -2262,10 +2269,6 @@ public final class JobStatus { } } - if (network != null) { - network.dumpDebug(proto, JobStatusDumpProto.NETWORK); - } - if (pendingWork != null) { for (int i = 0; i < pendingWork.size(); i++) { dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i)); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 2b79969f4378..91189e4b6e74 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -849,10 +849,9 @@ public final class QuotaController extends StateController { return true; } // A job is within quota if one of the following is true: - // 1. it's already running (already executing expedited jobs should be allowed to finish) - // 2. the app is currently in the foreground - // 3. the app overall is within its quota - // 4. It's on the temp allowlist (or within the grace period) + // 1. the app is currently in the foreground + // 2. the app overall is within its quota + // 3. It's on the temp allowlist (or within the grace period) if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { return true; } @@ -873,13 +872,6 @@ public final class QuotaController extends StateController { return true; } - Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(), - jobStatus.getSourcePackageName()); - // Any already executing expedited jobs should be allowed to finish. - if (ejTimer != null && ejTimer.isRunning(jobStatus)) { - return true; - } - return 0 < getRemainingEJExecutionTimeLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); } @@ -4153,6 +4145,8 @@ public final class QuotaController extends StateController { pw.print(", "); if (js.shouldTreatAsExpeditedJob()) { pw.print("within EJ quota"); + } else if (js.startedAsExpeditedJob) { + pw.print("out of EJ quota"); } else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { pw.print("within regular quota"); } else { @@ -4163,6 +4157,8 @@ public final class QuotaController extends StateController { pw.print(getRemainingEJExecutionTimeLocked( js.getSourceUserId(), js.getSourcePackageName())); pw.print("ms remaining in EJ quota"); + } else if (js.startedAsExpeditedJob) { + pw.print("should be stopped after min execution time"); } else { pw.print(getRemainingExecutionTimeLocked(js)); pw.print("ms remaining in quota"); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 24f7b37aedac..a62e258153a0 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -190,7 +190,7 @@ public class AppStandbyController ONE_HOUR, ONE_HOUR, 2 * ONE_HOUR, - 4 * ONE_DAY + 4 * ONE_HOUR }; private static final int[] THRESHOLD_BUCKETS = { 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 5f3d041c1501..935cf7067043 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -55,7 +55,9 @@ package android { field public static final String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final String BLUETOOTH = "android.permission.BLUETOOTH"; field public static final String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN"; + field public static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT"; field public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; + field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN"; field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS"; field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED"; field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS"; @@ -194,6 +196,7 @@ package android { field public static final String CONTACTS = "android.permission-group.CONTACTS"; field public static final String LOCATION = "android.permission-group.LOCATION"; field public static final String MICROPHONE = "android.permission-group.MICROPHONE"; + field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES"; field public static final String PHONE = "android.permission-group.PHONE"; field public static final String SENSORS = "android.permission-group.SENSORS"; field public static final String SMS = "android.permission-group.SMS"; @@ -1006,7 +1009,7 @@ package android { field public static final int multiArch = 16843918; // 0x101048e field public static final int multiprocess = 16842771; // 0x1010013 field public static final int name = 16842755; // 0x1010003 - field public static final int nativeHeapZeroInit = 16844325; // 0x1010625 + field public static final int nativeHeapZeroInitialized = 16844325; // 0x1010625 field public static final int navigationBarColor = 16843858; // 0x1010452 field public static final int navigationBarDividerColor = 16844141; // 0x101056d field public static final int navigationContentDescription = 16843969; // 0x10104c1 @@ -1176,6 +1179,7 @@ package android { field public static final int reqNavigation = 16843306; // 0x101022a field public static final int reqTouchScreen = 16843303; // 0x1010227 field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603 + field public static final int requestOptimizedExternalStorageAccess = 16844357; // 0x1010645 field public static final int requireDeviceScreenOn = 16844317; // 0x101061d field public static final int requireDeviceUnlock = 16843756; // 0x10103ec field public static final int required = 16843406; // 0x101028e @@ -6316,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(); @@ -7009,6 +7013,7 @@ package android.app.admin { method public void onBugreportShared(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String); method public void onBugreportSharingDeclined(@NonNull android.content.Context, @NonNull android.content.Intent); method @Nullable public String onChoosePrivateKeyAlias(@NonNull android.content.Context, @NonNull android.content.Intent, int, @Nullable android.net.Uri, @Nullable String); + method public void onComplianceAcknowledgementRequired(@NonNull android.content.Context, @NonNull android.content.Intent); method @Nullable public CharSequence onDisableRequested(@NonNull android.content.Context, @NonNull android.content.Intent); method public void onDisabled(@NonNull android.content.Context, @NonNull android.content.Intent); method public void onEnabled(@NonNull android.content.Context, @NonNull android.content.Intent); @@ -7063,6 +7068,7 @@ package android.app.admin { } public class DevicePolicyManager { + method public void acknowledgeDeviceCompliant(); method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int); method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String); method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting); @@ -7181,8 +7187,10 @@ package android.app.admin { method public boolean isBackupServiceEnabled(@NonNull android.content.ComponentName); method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName); + method public boolean isComplianceAcknowledgementRequired(); method public boolean isDeviceIdAttestationSupported(); method public boolean isDeviceOwnerApp(String); + method public boolean isEnterpriseNetworkPreferenceEnabled(); method public boolean isEphemeralUser(@NonNull android.content.ComponentName); method public boolean isKeyPairGrantedToWifiAuth(@NonNull String); method public boolean isLockTaskPermitted(String); @@ -7190,7 +7198,6 @@ package android.app.admin { method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName); - method public boolean isNetworkSlicingEnabled(); method public boolean isOrganizationOwnedDeviceWithManagedProfile(); method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName); method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -7245,6 +7252,7 @@ package android.app.admin { method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>); method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence); method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); + method public void setEnterpriseNetworkPreferenceEnabled(boolean); method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy); method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName); method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String); @@ -7264,7 +7272,6 @@ package android.app.admin { method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long); method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean); - method public void setNetworkSlicingEnabled(boolean); method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int); method public void setOrganizationId(@NonNull String); method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence); @@ -8996,6 +9003,7 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getName(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getType(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.os.ParcelUuid[] getUuids(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPairingConfirmation(boolean); method public boolean setPin(byte[]); method public void writeToParcel(android.os.Parcel, int); @@ -11859,7 +11867,7 @@ package android.content.pm { method public static CharSequence getCategoryTitle(android.content.Context, int); method public int getGwpAsanMode(); method public int getMemtagMode(); - method @Nullable public Boolean isNativeHeapZeroInit(); + method public int getNativeHeapZeroInitialized(); method public boolean isProfileableByShell(); method public boolean isResourceOverlay(); method public boolean isVirtualPreload(); @@ -11914,6 +11922,9 @@ package android.content.pm { field public static final int MEMTAG_DEFAULT = -1; // 0xffffffff field public static final int MEMTAG_OFF = 0; // 0x0 field public static final int MEMTAG_SYNC = 2; // 0x2 + field public static final int ZEROINIT_DEFAULT = -1; // 0xffffffff + field public static final int ZEROINIT_DISABLED = 0; // 0x0 + field public static final int ZEROINIT_ENABLED = 1; // 0x1 field public String appComponentFactory; field public String backupAgentName; field public int category; @@ -13368,7 +13379,8 @@ package android.content.res { method public void setTo(android.content.res.Resources.Theme); } - public class TypedArray { + public class TypedArray implements java.lang.AutoCloseable { + method public void close(); method public boolean getBoolean(@StyleableRes int, boolean); method public int getChangingConfigurations(); method @ColorInt public int getColor(@StyleableRes int, @ColorInt int); @@ -17854,6 +17866,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_DISTORTION; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_DISTORTION_MAXIMUM_RESOLUTION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_FACING; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INFO_AVAILABLE_APERTURES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INFO_AVAILABLE_FILTER_DENSITIES; @@ -17863,6 +17876,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_HYPERFOCAL_DISTANCE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INTRINSIC_CALIBRATION; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INTRINSIC_CALIBRATION_MAXIMUM_RESOLUTION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_POSE_REFERENCE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_ROTATION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_TRANSLATION; @@ -17882,9 +17896,11 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SCALER_CROPPING_TYPE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SCALER_DEFAULT_SECURE_IMAGE_SIZE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_STREAM_COMBINATIONS; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap> SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SENSOR_AVAILABLE_TEST_PATTERN_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM1; @@ -17894,13 +17910,17 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX1; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX2; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SENSOR_INFO_BINNING_FACTOR; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_COLOR_FILTER_ARRANGEMENT; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> SENSOR_INFO_LENS_SHADING_APPLIED; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Long> SENSOR_INFO_MAX_FRAME_DURATION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.SizeF> SENSOR_INFO_PHYSICAL_SIZE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect> SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> SENSOR_INFO_SENSITIVITY_RANGE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_TIMESTAMP_SOURCE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_INFO_WHITE_LEVEL; @@ -18197,8 +18217,10 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING = 4; // 0x4 field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3 field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING = 17; // 0x11 field public static final int REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13; // 0xd field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe + field public static final int REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR = 16; // 0x10 field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7 field public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; // 0x0 field public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; // 0x1 @@ -18216,6 +18238,8 @@ package android.hardware.camera2 { field public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB = 0; // 0x0 field public static final int SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME = 1; // 0x1 field public static final int SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN = 0; // 0x0 + field public static final int SENSOR_PIXEL_MODE_DEFAULT = 0; // 0x0 + field public static final int SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION = 1; // 0x1 field public static final int SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER = 10; // 0xa field public static final int SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT = 14; // 0xe field public static final int SENSOR_REFERENCE_ILLUMINANT1_D50 = 23; // 0x17 @@ -18345,6 +18369,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SCALER_ROTATE_AND_CROP; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Long> SENSOR_EXPOSURE_TIME; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Long> SENSOR_FRAME_DURATION; + field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SENSOR_PIXEL_MODE; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SENSOR_SENSITIVITY; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<int[]> SENSOR_TEST_PATTERN_DATA; field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> SENSOR_TEST_PATTERN_MODE; @@ -18448,6 +18473,8 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> SENSOR_GREEN_SPLIT; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Rational[]> SENSOR_NEUTRAL_COLOR_POINT; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Double,java.lang.Double>[]> SENSOR_NOISE_PROFILE; + field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_PIXEL_MODE; + field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> SENSOR_RAW_BINNING_FACTOR_USED; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_ROLLING_SHUTTER_SKEW; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_SENSITIVITY; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> SENSOR_TEST_PATTERN_DATA; @@ -18622,6 +18649,7 @@ package android.hardware.camera2.params { ctor public OutputConfiguration(@NonNull android.view.Surface); ctor public OutputConfiguration(int, @NonNull android.view.Surface); ctor public OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>); + method public void addSensorPixelModeUsed(int); method public void addSurface(@NonNull android.view.Surface); method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader); method public int describeContents(); @@ -18630,6 +18658,7 @@ package android.hardware.camera2.params { method @Nullable public android.view.Surface getSurface(); method public int getSurfaceGroupId(); method @NonNull public java.util.List<android.view.Surface> getSurfaces(); + method public void removeSensorPixelModeUsed(int); method public void removeSurface(@NonNull android.view.Surface); method public void setPhysicalCameraId(@Nullable String); method public void writeToParcel(android.os.Parcel, int); @@ -19800,7 +19829,8 @@ package android.location { method public boolean hasSpeed(); method public boolean hasSpeedAccuracy(); method public boolean hasVerticalAccuracy(); - method public boolean isFromMockProvider(); + method @Deprecated public boolean isFromMockProvider(); + method public boolean isMock(); method @Deprecated public void removeAccuracy(); method @Deprecated public void removeAltitude(); method @Deprecated public void removeBearing(); @@ -19816,6 +19846,7 @@ package android.location { method public void setExtras(@Nullable android.os.Bundle); method public void setLatitude(double); method public void setLongitude(double); + method public void setMock(boolean); method public void setProvider(String); method public void setSpeed(float); method public void setSpeedAccuracyMetersPerSecond(float); @@ -20273,6 +20304,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); @@ -20295,6 +20327,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(); @@ -20314,6 +20347,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); @@ -20323,6 +20357,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); @@ -20361,6 +20396,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"; @@ -20574,6 +20613,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(); @@ -20596,6 +20636,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); @@ -20711,6 +20752,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); @@ -20748,6 +20790,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); @@ -21302,7 +21345,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; @@ -21328,8 +21371,9 @@ 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 @Nullable public android.media.MediaCodec.ParameterDescriptor getParameterDescriptor(@NonNull String); method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int); + method @NonNull public java.util.List<java.lang.String> getSupportedVendorParameters(); 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; method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; @@ -21341,14 +21385,16 @@ package android.media { method public void setCallback(@Nullable android.media.MediaCodec.Callback, @Nullable android.os.Handler); method public void setCallback(@Nullable android.media.MediaCodec.Callback); method public void setInputSurface(@NonNull android.view.Surface); + method public void setOnFirstTunnelFrameReadyListener(@Nullable android.os.Handler, @Nullable android.media.MediaCodec.OnFirstTunnelFrameReadyListener); 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(); method public void stop(); + method public void subscribeToVendorParameters(@NonNull java.util.List<java.lang.String>); + method public void unsubscribeFromVendorParameters(@NonNull java.util.List<java.lang.String>); field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2 field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4 field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1 @@ -21368,6 +21414,7 @@ package android.media { field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames"; field public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us"; + field public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek"; field public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate"; field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1 field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2 @@ -21458,6 +21505,10 @@ package android.media { field public static final String WIDTH = "android.media.mediacodec.width"; } + public static interface MediaCodec.OnFirstTunnelFrameReadyListener { + method public void onFirstTunnelFrameReady(@NonNull android.media.MediaCodec); + } + public static interface MediaCodec.OnFrameRenderedListener { method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long); } @@ -21471,6 +21522,11 @@ package android.media { method public long getPresentationTimeUs(); } + public static class MediaCodec.ParameterDescriptor { + method @NonNull public String getName(); + method public int getType(); + } + public final class MediaCodec.QueueRequest { method public void queue(); method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer); @@ -21796,6 +21852,7 @@ package android.media { method public android.util.Range<java.lang.Integer> getQualityRange(); method public boolean isBitrateModeSupported(int); field public static final int BITRATE_MODE_CBR = 2; // 0x2 + field public static final int BITRATE_MODE_CBR_FD = 3; // 0x3 field public static final int BITRATE_MODE_CQ = 0; // 0x0 field public static final int BITRATE_MODE_VBR = 1; // 0x1 } @@ -21953,7 +22010,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(); @@ -22158,6 +22215,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(); @@ -22190,6 +22252,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); @@ -22211,6 +22274,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 @@ -22814,6 +22878,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(); @@ -22836,6 +22901,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; @@ -24467,6 +24533,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 { @@ -24500,11 +24568,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(); @@ -26956,7 +27019,7 @@ package android.net.vcn { public abstract static class VcnManager.VcnStatusCallback { ctor public VcnManager.VcnStatusCallback(); method public abstract void onGatewayConnectionError(@NonNull int[], int, @Nullable Throwable); - method public abstract void onVcnStatusChanged(int); + method public abstract void onStatusChanged(int); } } @@ -34880,6 +34943,7 @@ package android.provider { field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS"; field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS"; field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS"; + field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "com.android.settings.APP_OPEN_BY_DEFAULT_SETTINGS"; field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS"; field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS"; field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS"; @@ -34908,7 +34972,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"; @@ -35539,6 +35603,9 @@ package android.provider { method public static android.net.Uri getUriForSubscriptionIdAndField(int, String); 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"; @@ -37446,6 +37513,7 @@ package android.service.autofill { method public void onDisconnected(); method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback); method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback); + method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback); field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final String SERVICE_META_DATA = "android.autofill"; } @@ -37721,6 +37789,22 @@ package android.service.autofill { field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR; } + public final class SavedDatasetsInfo { + ctor public SavedDatasetsInfo(@NonNull String, @IntRange(from=0) int); + method @IntRange(from=0) public int getCount(); + method @NonNull public String getType(); + field public static final String TYPE_OTHER = "other"; + field public static final String TYPE_PASSWORDS = "passwords"; + } + + public interface SavedDatasetsInfoCallback { + method public void onError(int); + method public void onSuccess(@NonNull java.util.Set<android.service.autofill.SavedDatasetsInfo>); + field public static final int ERROR_NEEDS_USER_ACTION = 2; // 0x2 + field public static final int ERROR_OTHER = 0; // 0x0 + field public static final int ERROR_UNSUPPORTED = 1; // 0x1 + } + public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer { ctor public TextValueSanitizer(@NonNull java.util.regex.Pattern, @NonNull String); method public int describeContents(); @@ -40538,6 +40622,7 @@ package android.telephony { field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool"; field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool"; field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array"; + field public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL = "display_call_strength_indicator_bool"; field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool"; field public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool"; field public static final String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool"; @@ -40562,6 +40647,7 @@ package android.telephony { field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool"; field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool"; field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool"; + field public static final String KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL = "hide_no_calling_indicator_on_data_network_bool"; field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool"; field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool"; field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool"; @@ -40727,6 +40813,7 @@ package android.telephony { field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int"; field public static final String KEY_PREFIX = "ims."; field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool"; + field public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = "ims.rcs_feature_tag_allowed_string_array"; field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int"; } @@ -42043,7 +42130,6 @@ package android.telephony { method public static int[] calculateLength(String, boolean); method @Deprecated public static android.telephony.SmsMessage createFromPdu(byte[]); method public static android.telephony.SmsMessage createFromPdu(byte[], String); - method @Nullable public static android.telephony.SmsMessage createSmsSubmitPdu(@NonNull byte[], boolean); method public String getDisplayMessageBody(); method public String getDisplayOriginatingAddress(); method public String getEmailBody(); @@ -42144,6 +42230,7 @@ package android.telephony { method public static int getDefaultSmsSubscriptionId(); method public static int getDefaultSubscriptionId(); method public static int getDefaultVoiceSubscriptionId(); + method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int); method public int getDeviceToDeviceStatusSharingPreference(int); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions(); method public static int getSlotIndex(int); @@ -42157,6 +42244,7 @@ package android.telephony { method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(@NonNull java.util.List<android.net.Uri>, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int); method public void setSubscriptionOverrideCongested(int, boolean, long); @@ -42172,8 +42260,9 @@ package android.telephony { field public static final int D2D_SHARING_ALL = 3; // 0x3 field public static final int D2D_SHARING_ALL_CONTACTS = 1; // 0x1 field public static final int D2D_SHARING_DISABLED = 0; // 0x0 - field public static final int D2D_SHARING_STARRED_CONTACTS = 2; // 0x2 + field public static final int D2D_SHARING_SELECTED_CONTACTS = 2; // 0x2 field public static final String D2D_STATUS_SHARING = "d2d_sharing_status"; + field public static final String D2D_STATUS_SHARING_SELECTED_CONTACTS = "d2d_sharing_contacts"; field public static final int DATA_ROAMING_DISABLE = 0; // 0x0 field public static final int DATA_ROAMING_ENABLE = 1; // 0x1 field public static final int DEFAULT_SUBSCRIPTION_ID = 2147483647; // 0x7fffffff @@ -42281,7 +42370,7 @@ package android.telephony { } public static interface TelephonyCallback.DisplayInfoListener { - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo); + method public void onDisplayInfoChanged(@NonNull android.telephony.TelephonyDisplayInfo); } public static interface TelephonyCallback.EmergencyNumberListListener { @@ -42329,7 +42418,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 } @@ -42491,6 +42580,7 @@ package android.telephony { field public static final int CALL_STATE_IDLE = 0; // 0x0 field public static final int CALL_STATE_OFFHOOK = 2; // 0x2 field public static final int CALL_STATE_RINGING = 1; // 0x1 + field public static final String CAPABILITY_SLICING_CONFIG_SUPPORTED = "CAPABILITY_SLICING_CONFIG_SUPPORTED"; field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1 field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2 field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0 @@ -48080,6 +48170,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); @@ -52563,6 +52654,7 @@ package android.view.translation { method public void addTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent); method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationContext); method @NonNull @WorkerThread public java.util.Set<android.view.translation.TranslationCapability> getTranslationCapabilities(int, int); + method @Nullable public android.app.PendingIntent getTranslationSettingsActivityIntent(); method public void removeTranslationCapabilityUpdateListener(int, int, @NonNull android.app.PendingIntent); } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index e3b7c8898039..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 { @@ -312,6 +304,10 @@ package android.os.storage { field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 0; // 0x0 } + public final class StorageVolume implements android.os.Parcelable { + method @NonNull public android.os.UserHandle getOwner(); + } + } package android.provider { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a2f895b92f97..761774b373b7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3,6 +3,7 @@ package android { public static final class Manifest.permission { field public static final String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS"; + field public static final String ACCESS_BLOBS_ACROSS_USERS = "android.permission.ACCESS_BLOBS_ACROSS_USERS"; field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO"; field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM"; field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB"; @@ -92,6 +93,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"; @@ -242,6 +244,7 @@ package android { field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES"; field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS"; field public static final String ROTATE_SURFACE_FLINGER = "android.permission.ROTATE_SURFACE_FLINGER"; + field public static final String SCHEDULE_PRIORITIZED_ALARM = "android.permission.SCHEDULE_PRIORITIZED_ALARM"; field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION"; field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"; @@ -267,6 +270,7 @@ package android { field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"; + field public static final String SUGGEST_EXTERNAL_TIME = "android.permission.SUGGEST_EXTERNAL_TIME"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY"; field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA"; @@ -310,6 +314,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 @@ -419,6 +424,7 @@ package android.app { public class AlarmManager { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource); + method @RequiresPermission(android.Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) public void setPrioritized(int, long, long, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener); } public class AppOpsManager { @@ -699,6 +705,9 @@ package android.app { method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback); method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int); method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean); + field public static final int PASSWORD = 0; // 0x0 + field public static final int PATTERN = 2; // 0x2 + field public static final int PIN = 1; // 0x1 } public class Notification implements android.os.Parcelable { @@ -945,6 +954,9 @@ package android.app.admin { field @Deprecated public static final int PROVISIONING_TRIGGER_PERSISTENT_DEVICE_OWNER = 3; // 0x3 field public static final int PROVISIONING_TRIGGER_QR_CODE = 2; // 0x2 field public static final int PROVISIONING_TRIGGER_UNSPECIFIED = 0; // 0x0 + field public static final String REQUIRED_APP_MANAGED_DEVICE = "android.app.REQUIRED_APP_MANAGED_DEVICE"; + field public static final String REQUIRED_APP_MANAGED_PROFILE = "android.app.REQUIRED_APP_MANAGED_PROFILE"; + field public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER"; field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4 field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5 field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2 @@ -1834,7 +1846,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); @@ -1947,7 +1959,6 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnect(android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); - method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPriority(android.bluetooth.BluetoothDevice, int); } public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile { @@ -2174,6 +2185,8 @@ package android.bluetooth.le { package android.companion { public final class CompanionDeviceManager { + method @RequiresPermission("android.permission.ASSOCIATE_COMPANION_DEVICES") public boolean associate(@NonNull String, @NonNull android.net.MacAddress); + method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, int); method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle); } @@ -2440,6 +2453,7 @@ package android.content.om { package android.content.pm { public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { + method @Nullable public Boolean hasRequestOptimizedExternalStorageAccess(); method public boolean isEncryptionAware(); method public boolean isInstantApp(); method public boolean isOem(); @@ -2799,7 +2813,9 @@ package android.content.pm { method @NonNull public android.content.pm.SuspendDialogInfo.Builder setMessage(@StringRes int); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonAction(int); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonText(@StringRes int); + method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonText(@NonNull String); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@StringRes int); + method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@NonNull String); } } @@ -2865,18 +2881,15 @@ package android.content.pm.verify.domain { } public final class DomainVerificationManager { - method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @Nullable @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames(); method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException; - method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException; + method @CheckResult @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @CheckResult @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException; field public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; // 0x1 - field public static final int ERROR_DOMAIN_SET_ID_NULL = 2; // 0x2 - field public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; // 0x3 - field public static final int ERROR_INVALID_STATE_CODE = 6; // 0x6 - field public static final int ERROR_UNABLE_TO_APPROVE = 5; // 0x5 - field public static final int ERROR_UNKNOWN_DOMAIN = 4; // 0x4 + field public static final int ERROR_UNABLE_TO_APPROVE = 3; // 0x3 + field public static final int ERROR_UNKNOWN_DOMAIN = 2; // 0x2 field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST"; field public static final int STATUS_OK = 0; // 0x0 } @@ -4447,6 +4460,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(); @@ -4462,6 +4476,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); @@ -4820,7 +4835,7 @@ package android.location { public class Location implements android.os.Parcelable { method public boolean isComplete(); method public void makeComplete(); - method public void setIsFromMockProvider(boolean); + method @Deprecated public void setIsFromMockProvider(boolean); field @Deprecated public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; } @@ -5251,6 +5266,10 @@ package android.media { method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void ensureDefaultRingtones(@NonNull android.content.Context); } + public final class RouteDiscoveryPreference implements android.os.Parcelable { + field public static final android.media.RouteDiscoveryPreference EMPTY; + } + } package android.media.audiofx { @@ -5429,6 +5448,8 @@ package android.media.session { public final class MediaSessionManager { method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener); method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener); + method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.media.session.MediaSession.Token getMediaKeyEventSession(); + method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public String getMediaKeyEventSessionPackageName(); method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener); method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener); method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler); @@ -7567,9 +7588,11 @@ package android.net.netstats.provider { method public void notifyAlertReached(); method public void notifyLimitReached(); method public void notifyStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats); + method public void notifyWarningReached(); method public abstract void onRequestStatsUpdate(int); method public abstract void onSetAlert(long); method public abstract void onSetLimit(@NonNull String, long); + method public void onSetWarningAndLimit(@NonNull String, long, long); field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff } @@ -7777,6 +7800,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); @@ -7789,6 +7813,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 @@ -7799,6 +7824,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; @@ -8497,6 +8526,7 @@ package android.os { method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser(); @@ -8510,6 +8540,7 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean sharesMediaWithParent(); field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED"; field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock"; field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background"; @@ -8523,6 +8554,7 @@ package android.os { field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2 field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; + field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS"; } @@ -8670,9 +8702,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 @@ -8919,6 +8957,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"; @@ -8935,6 +8974,7 @@ package android.provider { field public static final String NAMESPACE_GAME_DRIVER = "game_driver"; field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot"; field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention"; + field public static final String NAMESPACE_MEDIA = "media"; field public static final String NAMESPACE_MEDIA_NATIVE = "media_native"; field public static final String NAMESPACE_NETD_NATIVE = "netd_native"; field public static final String NAMESPACE_OTA = "ota"; @@ -8996,6 +9036,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"; } @@ -10322,9 +10364,9 @@ package android.service.voice { method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int); method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int); method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition(); + method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory); field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 - field public static final int HOTWORD_DETECTION_FALSE_ALERT = 0; // 0x0 field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 @@ -10347,11 +10389,13 @@ package android.service.voice { method public abstract void onError(); method public abstract void onRecognitionPaused(); method public abstract void onRecognitionResumed(); - method public void onRejected(int); + method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult); } public static class AlwaysOnHotwordDetector.EventPayload { + method @Nullable public android.os.ParcelFileDescriptor getAudioStream(); method @Nullable public android.media.AudioFormat getCaptureAudioFormat(); + method @Nullable public android.service.voice.HotwordDetectedResult getHotwordDetectedResult(); method @Nullable public byte[] getTriggerAudio(); } @@ -10390,13 +10434,13 @@ package android.service.voice { ctor public HotwordDetectionService(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public void onDetectFromDspSource(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, long, @NonNull android.service.voice.HotwordDetectionService.DspHotwordDetectionCallback); - method public void onUpdateState(@Nullable android.os.Bundle, @Nullable android.os.SharedMemory); + method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory); field public static final String SERVICE_INTERFACE = "android.service.voice.HotwordDetectionService"; } public static final class HotwordDetectionService.DspHotwordDetectionCallback { method public void onDetected(); - method public void onRejected(); + method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult); } public interface HotwordDetector { @@ -10415,7 +10459,7 @@ package android.service.voice { public class VoiceInteractionService extends android.app.Service { method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); - method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.Bundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); } @@ -11255,12 +11299,11 @@ package android.telephony { public final class PhoneCapability implements android.os.Parcelable { method public int describeContents(); - method public int getDeviceNrCapabilityBitmask(); - method @IntRange(from=1) public int getMaxActiveInternetData(); - method @IntRange(from=1) public int getMaxActivePacketSwitchedVoiceCalls(); + method @NonNull public int[] getDeviceNrCapabilities(); + method @IntRange(from=1) public int getMaxActiveDataSubscriptions(); + method @IntRange(from=1) public int getMaxActiveVoiceSubscriptions(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PhoneCapability> CREATOR; - field public static final int DEVICE_NR_CAPABILITY_NONE = 0; // 0x0 field public static final int DEVICE_NR_CAPABILITY_NSA = 1; // 0x1 field public static final int DEVICE_NR_CAPABILITY_SA = 2; // 0x2 } @@ -12277,6 +12320,20 @@ package android.telephony.data { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR; } + public final class NrQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes { + method public int describeContents(); + method public int get5Qi(); + method public long getAveragingWindow(); + method public long getGuaranteedDownlinkBitRate(); + method public long getGuaranteedUplinkBitRate(); + method public long getMaxDownlinkBitRate(); + method public long getMaxUplinkBitRate(); + method public int getQfi(); + method @NonNull public java.util.List<java.net.InetSocketAddress> getRemoteAddresses(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.NrQosSessionAttributes> CREATOR; + } + public abstract class QualifiedNetworksService extends android.app.Service { ctor public QualifiedNetworksService(); method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int); @@ -14024,7 +14081,7 @@ package android.util { package android.uwb { public final class AngleMeasurement implements android.os.Parcelable { - ctor public AngleMeasurement(double, double, double); + ctor public AngleMeasurement(@FloatRange(from=-3.141592653589793, to=3.141592653589793) double, @FloatRange(from=0.0, to=3.141592653589793) double, @FloatRange(from=0.0, to=1.0) double); method public int describeContents(); method @FloatRange(from=0.0, to=1.0) public double getConfidenceLevel(); method @FloatRange(from=0.0, to=3.141592653589793) public double getErrorRadians(); @@ -14273,13 +14330,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..b50b8dd3fdb4 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -48,6 +48,14 @@ package android.app.prediction { } +package android.bluetooth { + + public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPriority(android.bluetooth.BluetoothDevice, int); + } + +} + package android.content { public class Intent implements java.lang.Cloneable android.os.Parcelable { @@ -181,3 +189,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 ae1cbf77dd8a..97ad48c96829 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -34,6 +34,7 @@ package android { field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; + field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"; field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; @@ -232,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 @@ -817,6 +820,7 @@ package android.content.pm { method public int describeContents(); method public android.os.UserHandle getUserHandle(); method public boolean isAdmin(); + method public boolean isCloneProfile(); method public boolean isDemo(); method public boolean isEnabled(); method public boolean isEphemeral(); @@ -1476,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); } } @@ -1702,6 +1706,7 @@ package android.os { method public static android.os.VibrationEffect get(int, boolean); method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context); method public abstract long getDuration(); + method @NonNull public static android.os.VibrationEffect.WaveformBuilder startWaveform(); field public static final int EFFECT_POP = 4; // 0x4 field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0 field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1 @@ -1711,35 +1716,30 @@ package android.os { field public static final int[] RINGTONES; } - public static class VibrationEffect.OneShot extends android.os.VibrationEffect implements android.os.Parcelable { - ctor public VibrationEffect.OneShot(android.os.Parcel); - ctor public VibrationEffect.OneShot(long, int); - method public int getAmplitude(); + public static final class VibrationEffect.Composed extends android.os.VibrationEffect { + method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int); method public long getDuration(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.OneShot> CREATOR; + method public int getRepeatIndex(); + method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments(); + method @NonNull public android.os.VibrationEffect.Composed resolve(int); + method @NonNull public android.os.VibrationEffect.Composed scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR; } - public static class VibrationEffect.Prebaked extends android.os.VibrationEffect implements android.os.Parcelable { - ctor public VibrationEffect.Prebaked(android.os.Parcel); - ctor public VibrationEffect.Prebaked(int, boolean, int); - method public long getDuration(); - method public int getEffectStrength(); - method public int getId(); - method public boolean shouldFallback(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Prebaked> CREATOR; + public static final class VibrationEffect.Composition { + method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect); + method @NonNull public android.os.VibrationEffect.Composition addEffect(@NonNull android.os.VibrationEffect, @IntRange(from=0) int); } - public static class VibrationEffect.Waveform extends android.os.VibrationEffect implements android.os.Parcelable { - ctor public VibrationEffect.Waveform(android.os.Parcel); - ctor public VibrationEffect.Waveform(long[], int[], int); - method public int[] getAmplitudes(); - method public long getDuration(); - method public int getRepeatIndex(); - method public long[] getTimings(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Waveform> CREATOR; + public static final class VibrationEffect.WaveformBuilder { + method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect.WaveformBuilder addRamp(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect.WaveformBuilder addStep(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=-1.0F, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect build(); + method @NonNull public android.os.VibrationEffect build(int); } public class VintfObject { @@ -1840,6 +1840,7 @@ package android.os.storage { public class StorageManager { method @NonNull public static java.util.UUID convert(@NonNull String); method @NonNull public static String convert(@NonNull java.util.UUID); + method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int); method public static boolean isUserKeyUnlocked(int); } @@ -1856,8 +1857,93 @@ package android.os.strictmode { } +package android.os.vibrator { + + public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int); + method public int describeContents(); + method public long getDuration(); + method public int getEffectId(); + method public int getEffectStrength(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.PrebakedSegment resolve(int); + method @NonNull public android.os.vibrator.PrebakedSegment scale(float); + method public boolean shouldFallback(); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR; + } + + public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int); + method public int describeContents(); + method public int getDelay(); + method public long getDuration(); + method public int getPrimitiveId(); + method public float getScale(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int); + method @NonNull public android.os.vibrator.PrimitiveSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR; + } + + public final class RampSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int); + method public int describeContents(); + method public long getDuration(); + method public float getEndAmplitude(); + method public float getEndFrequency(); + method public float getStartAmplitude(); + method public float getStartFrequency(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.RampSegment resolve(int); + method @NonNull public android.os.vibrator.RampSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR; + } + + public final class StepSegment extends android.os.vibrator.VibrationEffectSegment { + method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int); + method public int describeContents(); + method public float getAmplitude(); + method public long getDuration(); + method public float getFrequency(); + method public boolean hasNonZeroAmplitude(); + method @NonNull public android.os.vibrator.StepSegment resolve(int); + method @NonNull public android.os.vibrator.StepSegment scale(float); + method public void validate(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR; + } + + public abstract class VibrationEffectSegment implements android.os.Parcelable { + method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int); + method public abstract long getDuration(); + method public abstract boolean hasNonZeroAmplitude(); + method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int); + method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float); + method public abstract void validate(); + field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR; + } + +} + 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); @@ -1872,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 { @@ -2260,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/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index a536efb2b488..1833ed50ce4f 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -1515,30 +1515,6 @@ MissingNullability: android.os.VibrationEffect#get(int): MissingNullability: android.os.VibrationEffect#get(int, boolean): -MissingNullability: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel) parameter #0: - -MissingNullability: android.os.VibrationEffect.OneShot#scale(float, int): - -MissingNullability: android.os.VibrationEffect.OneShot#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel) parameter #0: - -MissingNullability: android.os.VibrationEffect.Prebaked#writeToParcel(android.os.Parcel, int) parameter #0: - -MissingNullability: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel) parameter #0: - -MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #0: - -MissingNullability: android.os.VibrationEffect.Waveform#Waveform(long[], int[], int) parameter #1: - -MissingNullability: android.os.VibrationEffect.Waveform#getAmplitudes(): - -MissingNullability: android.os.VibrationEffect.Waveform#getTimings(): - -MissingNullability: android.os.VibrationEffect.Waveform#scale(float, int): - -MissingNullability: android.os.VibrationEffect.Waveform#writeToParcel(android.os.Parcel, int) parameter #0: - MissingNullability: android.os.VintfObject#getHalNamesAndVersions(): MissingNullability: android.os.VintfObject#getSepolicyVersion(): @@ -2739,12 +2715,6 @@ ParcelConstructor: android.os.IncidentReportArgs#IncidentReportArgs(android.os.P ParcelConstructor: android.os.StrictMode.ViolationInfo#ViolationInfo(android.os.Parcel): -ParcelConstructor: android.os.VibrationEffect.OneShot#OneShot(android.os.Parcel): - -ParcelConstructor: android.os.VibrationEffect.Prebaked#Prebaked(android.os.Parcel): - -ParcelConstructor: android.os.VibrationEffect.Waveform#Waveform(android.os.Parcel): - ParcelConstructor: android.os.health.HealthStatsParceler#HealthStatsParceler(android.os.Parcel): ParcelConstructor: android.service.notification.SnoozeCriterion#SnoozeCriterion(android.os.Parcel): @@ -2773,12 +2743,6 @@ ParcelCreator: android.net.metrics.RaEvent: ParcelCreator: android.net.metrics.ValidationProbeEvent: -ParcelCreator: android.os.VibrationEffect.OneShot: - -ParcelCreator: android.os.VibrationEffect.Prebaked: - -ParcelCreator: android.os.VibrationEffect.Waveform: - ParcelCreator: android.service.autofill.InternalOnClickAction: ParcelCreator: android.service.autofill.InternalSanitizer: @@ -2797,12 +2761,6 @@ ParcelNotFinal: android.net.metrics.IpConnectivityLog.Event: ParcelNotFinal: android.os.IncidentManager.IncidentReport: -ParcelNotFinal: android.os.VibrationEffect.OneShot: - -ParcelNotFinal: android.os.VibrationEffect.Prebaked: - -ParcelNotFinal: android.os.VibrationEffect.Waveform: - ParcelNotFinal: android.os.health.HealthStatsParceler: ParcelNotFinal: android.service.autofill.InternalOnClickAction: 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 27b19bcd31a1..7e4af1ad7952 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1073,6 +1073,8 @@ public class AppOpsManager { /** @hide */ @UnsupportedAppUsage public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN; + /** @hide */ + public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT; /** @hide Use the BiometricPrompt/BiometricManager APIs. */ public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC; /** @hide Physical activity recognition. */ @@ -1221,7 +1223,7 @@ public class AppOpsManager { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 111; + public static final int _NUM_OP = 112; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1465,6 +1467,8 @@ public class AppOpsManager { public static final String OPSTR_START_FOREGROUND = "android:start_foreground"; /** @hide */ public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan"; + /** @hide */ + public static final String OPSTR_BLUETOOTH_CONNECT = "android:bluetooth_connect"; /** @hide Use the BiometricPrompt/BiometricManager APIs. */ public static final String OPSTR_USE_BIOMETRIC = "android:use_biometric"; @@ -1557,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"; /** @@ -1696,6 +1702,9 @@ public class AppOpsManager { OP_WRITE_MEDIA_VIDEO, OP_READ_MEDIA_IMAGES, OP_WRITE_MEDIA_IMAGES, + // Nearby devices + OP_BLUETOOTH_SCAN, + OP_BLUETOOTH_CONNECT, // APPOP PERMISSIONS OP_ACCESS_NOTIFICATIONS, @@ -1801,7 +1810,7 @@ public class AppOpsManager { OP_ACCEPT_HANDOVER, // ACCEPT_HANDOVER OP_MANAGE_IPSEC_TUNNELS, // MANAGE_IPSEC_HANDOVERS OP_START_FOREGROUND, // START_FOREGROUND - OP_COARSE_LOCATION, // BLUETOOTH_SCAN + OP_BLUETOOTH_SCAN, // BLUETOOTH_SCAN OP_USE_BIOMETRIC, // BIOMETRIC OP_ACTIVITY_RECOGNITION, // ACTIVITY_RECOGNITION OP_SMS_FINANCIAL_TRANSACTIONS, // SMS_FINANCIAL_TRANSACTIONS @@ -1835,6 +1844,7 @@ public class AppOpsManager { OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE OP_MANAGE_MEDIA, // MANAGE_MEDIA + OP_BLUETOOTH_CONNECT, // OP_BLUETOOTH_CONNECT }; /** @@ -1952,6 +1962,7 @@ public class AppOpsManager { OPSTR_FINE_LOCATION_SOURCE, OPSTR_COARSE_LOCATION_SOURCE, OPSTR_MANAGE_MEDIA, + OPSTR_BLUETOOTH_CONNECT, }; /** @@ -2070,6 +2081,7 @@ public class AppOpsManager { "FINE_LOCATION_SOURCE", "COARSE_LOCATION_SOURCE", "MANAGE_MEDIA", + "BLUETOOTH_CONNECT", }; /** @@ -2155,7 +2167,7 @@ public class AppOpsManager { Manifest.permission.ACCEPT_HANDOVER, Manifest.permission.MANAGE_IPSEC_TUNNELS, Manifest.permission.FOREGROUND_SERVICE, - null, // no permission for OP_BLUETOOTH_SCAN + Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.USE_BIOMETRIC, Manifest.permission.ACTIVITY_RECOGNITION, Manifest.permission.SMS_FINANCIAL_TRANSACTIONS, @@ -2189,6 +2201,7 @@ public class AppOpsManager { null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE, null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE, Manifest.permission.MANAGE_MEDIA, + Manifest.permission.BLUETOOTH_CONNECT, }; /** @@ -2308,6 +2321,7 @@ public class AppOpsManager { null, // ACCESS_FINE_LOCATION_SOURCE null, // ACCESS_COARSE_LOCATION_SOURCE null, // MANAGE_MEDIA + null, // BLUETOOTH_CONNECT }; /** @@ -2426,6 +2440,7 @@ public class AppOpsManager { null, // ACCESS_FINE_LOCATION_SOURCE null, // ACCESS_COARSE_LOCATION_SOURCE null, // MANAGE_MEDIA + null, // BLUETOOTH_CONNECT }; /** @@ -2543,6 +2558,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA + AppOpsManager.MODE_ALLOWED, // BLUETOOTH_CONNECT }; /** @@ -2664,6 +2680,7 @@ public class AppOpsManager { false, // ACCESS_FINE_LOCATION_SOURCE false, // ACCESS_COARSE_LOCATION_SOURCE false, // MANAGE_MEDIA + false, // BLUETOOTH_CONNECT }; /** 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/GameManager.java b/core/java/android/app/GameManager.java index 47de04078b45..5964f71d28db 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -135,4 +135,20 @@ public final class GameManager { throw e.rethrowFromSystemServer(); } } + /** + * Returns a list of supported game modes for a given package. + * <p> + * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public @GameMode int[] getAvailableGameModes(@NonNull String packageName) { + try { + return mService.getAvailableGameModes(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index c8e1478a9930..4bf8a3f77bca 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -22,4 +22,5 @@ package android.app; interface IGameManagerService { int getGameMode(String packageName, int userId); void setGameMode(String packageName, int gameMode, int userId); + int[] getAvailableGameModes(String packageName); }
\ No newline at end of file diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index b6d25cfb26ce..4326c2d85500 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -133,6 +133,42 @@ public class KeyguardManager { */ public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm"; + /** + * + * Password lock type, see {@link #setLock} + * + * @hide + */ + @SystemApi + public static final int PASSWORD = 0; + + /** + * + * Pin lock type, see {@link #setLock} + * + * @hide + */ + @SystemApi + public static final int PIN = 1; + + /** + * + * Pattern lock type, see {@link #setLock} + * + * @hide + */ + @SystemApi + public static final int PATTERN = 2; + + /** + * Available lock types + */ + @IntDef({ + PASSWORD, + PIN, + PATTERN + }) + @interface LockTypes {} /** * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics @@ -695,7 +731,7 @@ public class KeyguardManager { PasswordMetrics adminMetrics = devicePolicyManager.getPasswordMinimumMetrics(mContext.getUserId()); // Check if the password fits the mold of a pin or pattern. - boolean isPinOrPattern = lockType != LockTypes.PASSWORD; + boolean isPinOrPattern = lockType != PASSWORD; return PasswordMetrics.validatePassword( adminMetrics, complexity, isPinOrPattern, password).size() == 0; @@ -759,7 +795,7 @@ public class KeyguardManager { boolean success = false; try { switch (lockType) { - case LockTypes.PASSWORD: + case PASSWORD: CharSequence passwordStr = new String(password, Charset.forName("UTF-8")); lockPatternUtils.setLockCredential( LockscreenCredential.createPassword(passwordStr), @@ -767,7 +803,7 @@ public class KeyguardManager { userId); success = true; break; - case LockTypes.PIN: + case PIN: CharSequence pinStr = new String(password); lockPatternUtils.setLockCredential( LockscreenCredential.createPin(pinStr), @@ -775,7 +811,7 @@ public class KeyguardManager { userId); success = true; break; - case LockTypes.PATTERN: + case PATTERN: List<LockPatternView.Cell> pattern = LockPatternUtils.byteArrayToPattern(password); lockPatternUtils.setLockCredential( @@ -796,18 +832,4 @@ public class KeyguardManager { } return success; } - - /** - * Available lock types - */ - @IntDef({ - LockTypes.PASSWORD, - LockTypes.PIN, - LockTypes.PATTERN - }) - @interface LockTypes { - int PASSWORD = 0; - int PIN = 1; - int PATTERN = 2; - } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4eda6fefd188..420ec0846fda 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -39,7 +39,6 @@ import android.annotation.StringRes; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -5217,8 +5216,7 @@ public class Notification implements Parcelable R.dimen.notification_right_icon_size) / density; float viewWidthDp = viewHeightDp; // icons are 1:1 by default if (largeIconShown && (p.mPromotePicture - || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S - || DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()))) { + || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) { Drawable drawable = mN.mLargeIcon.loadDrawable(mContext); if (drawable != null) { int iconWidth = drawable.getIntrinsicWidth(); @@ -5285,8 +5283,7 @@ public class Notification implements Parcelable contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); // Use different highlighted colors except when low-priority mode prevents that if (!p.mReduceHighlights) { - textColor = getBackgroundColor(p); - pillColor = getAccentColor(p); + pillColor = getAccentTertiaryColor(p); } contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); @@ -5662,55 +5659,45 @@ public class Notification implements Parcelable if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { return false; } - final ContentResolver contentResolver = mContext.getContentResolver(); - final int decorationType = DevFlags.getFullyCustomViewNotifDecoration(contentResolver); - return decorationType != DevFlags.DECORATION_NONE - && (DevFlags.shouldBackportSNotifRules(contentResolver) - || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S); + return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S; } private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) { - int decorationType = - DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver()); StandardTemplateParams p = mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) - .decorationType(decorationType) + .decorationType(StandardTemplateParams.DECORATION_MINIMAL) .fillTextsFrom(this); TemplateBindResult result = new TemplateBindResult(); RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result); buildCustomContentIntoTemplate(mContext, standard, customContent, - p, result, decorationType); + p, result); return standard; } private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) { - int decorationType = - DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver()); StandardTemplateParams p = mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_BIG) - .decorationType(decorationType) + .decorationType(StandardTemplateParams.DECORATION_MINIMAL) .fillTextsFrom(this); TemplateBindResult result = new TemplateBindResult(); RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, result); buildCustomContentIntoTemplate(mContext, standard, customContent, - p, result, decorationType); + p, result); return standard; } private RemoteViews minimallyDecoratedHeadsUpContentView( @NonNull RemoteViews customContent) { - int decorationType = - DevFlags.getFullyCustomViewNotifDecoration(mContext.getContentResolver()); StandardTemplateParams p = mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) - .decorationType(decorationType) + .decorationType(StandardTemplateParams.DECORATION_MINIMAL) .fillTextsFrom(this); TemplateBindResult result = new TemplateBindResult(); RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, result); buildCustomContentIntoTemplate(mContext, standard, customContent, - p, result, decorationType); + p, result); return standard; } @@ -5769,12 +5756,6 @@ public class Notification implements Parcelable .fillTextsFrom(this); result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, null /* result */); - } else if (DevFlags.shouldBackportSNotifRules(mContext.getContentResolver()) - && useExistingRemoteView() - && fullyCustomViewRequiresDecoration(false /* fromStyle */)) { - // This "backport" logic is a special case to handle the UNDO style of notif - // so that we can see what that will look like when the app targets S. - result = minimallyDecoratedBigContentView(mN.contentView); } } makeHeaderExpanded(result); @@ -6237,6 +6218,25 @@ public class Notification implements Parcelable } /** + * Gets the tertiary accent color for colored UI elements. If we're tinting with the theme + * accent, this comes from the tertiary system accent palette, otherwise this would be + * identical to {@link #getSmallIconColor(StandardTemplateParams)}. + */ + private @ColorInt int getAccentTertiaryColor(StandardTemplateParams p) { + if (isColorized(p)) { + return getPrimaryTextColor(p); + } + if (mTintWithThemeAccent) { + int color = obtainThemeColor(com.android.internal.R.attr.colorAccentTertiary, + COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; + } + } + return getContrastColor(p); + } + + /** * Gets the theme's error color, or the primary text color for colorized notifications. */ private @ColorInt int getErrorColor(StandardTemplateParams p) { @@ -6864,24 +6864,18 @@ public class Notification implements Parcelable private static void buildCustomContentIntoTemplate(@NonNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, - @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result, - int decorationType) { + @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) { int childIndex = -1; if (customContent != null) { // Need to clone customContent before adding, because otherwise it can no longer be // parceled independently of remoteViews. customContent = customContent.clone(); if (p.mHeaderless) { - if (decorationType <= DevFlags.DECORATION_PARTIAL) { - template.removeFromParent(R.id.notification_top_line); - } - // The vertical margins are bigger in the "two-line" scenario than the "one-line" - // scenario, but the 'compatible' decoration state is intended to have 3 lines, - // (1 for the top line views and 2 for the custom views), so in that one case we - // use the smaller 1-line margins. This gives the compatible case 88-16*2=56 dp of - // height, 24dp of which goes to the top line, leaving 32dp for the custom view. - boolean hasSecondLine = decorationType != DevFlags.DECORATION_FULL_COMPATIBLE; - Builder.setHeaderlessVerticalMargins(template, p, hasSecondLine); + template.removeFromParent(R.id.notification_top_line); + // We do not know how many lines ar emote view has, so we presume it has 2; this + // ensures that we don't under-pad the content, which could lead to abuse, at the + // cost of making single-line custom content over-padded. + Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */); } else { // also update the end margin to account for the large icon or expander Resources resources = context.getResources(); @@ -9042,8 +9036,7 @@ public class Notification implements Parcelable template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE); // Add custom view if provided by subclass. - buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result, - DevFlags.DECORATION_PARTIAL); + buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); return template; } @@ -9066,8 +9059,7 @@ public class Notification implements Parcelable template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); } } - buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result, - DevFlags.DECORATION_PARTIAL); + buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); return template; } } @@ -9644,16 +9636,15 @@ public class Notification implements Parcelable if (mBuilder.mActions.size() == 0) { return makeStandardTemplateWithCustomContent(headsUpContentView); } - int decorationType = getDecorationType(); TemplateBindResult result = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) - .decorationType(decorationType) + .decorationType(StandardTemplateParams.DECORATION_PARTIAL) .fillTextsFrom(mBuilder); RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( mBuilder.getHeadsUpBaseLayoutResource(), p, result); buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView, - p, result, decorationType); + p, result); return remoteViews; } @@ -9661,16 +9652,15 @@ public class Notification implements Parcelable if (customContent == null) { return null; // no custom view; use the default behavior } - int decorationType = getDecorationType(); TemplateBindResult result = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) - .decorationType(decorationType) + .decorationType(StandardTemplateParams.DECORATION_PARTIAL) .fillTextsFrom(mBuilder); RemoteViews remoteViews = mBuilder.applyStandardTemplate( mBuilder.getBaseLayoutResource(), p, result); buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent, - p, result, decorationType); + p, result); return remoteViews; } @@ -9681,24 +9671,18 @@ public class Notification implements Parcelable if (bigContentView == null) { return null; // no custom view; use the default behavior } - int decorationType = getDecorationType(); TemplateBindResult result = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .viewType(StandardTemplateParams.VIEW_TYPE_BIG) - .decorationType(decorationType) + .decorationType(StandardTemplateParams.DECORATION_PARTIAL) .fillTextsFrom(mBuilder); RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( mBuilder.getBigBaseLayoutResource(), p, result); buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView, - p, result, decorationType); + p, result); return remoteViews; } - private int getDecorationType() { - ContentResolver contentResolver = mBuilder.mContext.getContentResolver(); - return DevFlags.getDecoratedCustomViewNotifDecoration(contentResolver); - } - /** * @hide */ @@ -12091,6 +12075,22 @@ public class Notification implements Parcelable } private static class StandardTemplateParams { + /** + * Notifications will be minimally decorated with ONLY an icon and expander: + * <li>A large icon is never shown. + * <li>A progress bar is never shown. + * <li>The expanded and heads up states do not show actions, even if provided. + */ + public static final int DECORATION_MINIMAL = 1; + + /** + * Notifications will be partially decorated with AT LEAST an icon and expander: + * <li>A large icon is shown if provided. + * <li>A progress bar is shown if provided and enough space remains below the content. + * <li>Actions are shown in the expanded and heads up states. + */ + public static final int DECORATION_PARTIAL = 2; + public static int VIEW_TYPE_UNSPECIFIED = 0; public static int VIEW_TYPE_NORMAL = 1; public static int VIEW_TYPE_BIG = 2; @@ -12276,112 +12276,20 @@ public class Notification implements Parcelable } public StandardTemplateParams decorationType(int decorationType) { - boolean hideTitle = decorationType <= DevFlags.DECORATION_FULL_COMPATIBLE; - boolean hideOtherFields = decorationType <= DevFlags.DECORATION_MINIMAL; - hideTitle(hideTitle); + // These fields are removed by the decoration process, and thus would not show anyway; + // hiding them is a minimal time/space optimization. + hideAppName(true); + hideTitle(true); + hideSubText(true); + hideTime(true); + // Minimally decorated custom views do not show certain pieces of chrome that have + // always been shown when using DecoratedCustomViewStyle. + boolean hideOtherFields = decorationType <= DECORATION_MINIMAL; hideLargeIcon(hideOtherFields); hideProgress(hideOtherFields); hideActions(hideOtherFields); + hideSnoozeButton(hideOtherFields); return this; } } - - /** - * A class to centrally access various developer flags related to notifications. - * This class is a non-final wrapper around Settings.Global which allows mocking for unit tests. - * TODO(b/176239013): Try to remove this before shipping S - * @hide - */ - public static class DevFlags { - - /** - * Notifications will not be decorated. The custom content will be shown as-is. - * - * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle, - * as that API contract includes decorations that this does not provide. - */ - public static final int DECORATION_NONE = 0; - - /** - * Notifications will be minimally decorated with ONLY an icon and expander as follows: - * <li>A large icon is never shown. - * <li>A progress bar is never shown. - * <li>The expanded and heads up states do not show actions, even if provided. - * <li>The collapsed state gives the app's custom content 48dp of vertical space. - * <li>The collapsed state does NOT include the top line of views, - * like the alerted icon or work profile badge. - * - * <p>NOTE: This option is not available for notifications with DecoratedCustomViewStyle, - * as that API contract includes decorations that this does not provide. - */ - public static final int DECORATION_MINIMAL = 1; - - /** - * Notifications will be partially decorated with AT LEAST an icon and expander as follows: - * <li>A large icon is shown if provided. - * <li>A progress bar is shown if provided and enough space remains below the content. - * <li>Actions are shown in the expanded and heads up states. - * <li>The collapsed state gives the app's custom content 48dp of vertical space. - * <li>The collapsed state does NOT include the top line of views, - * like the alerted icon or work profile badge. - */ - public static final int DECORATION_PARTIAL = 2; - - /** - * Notifications will be fully decorated as follows: - * <li>A large icon is shown if provided. - * <li>A progress bar is shown if provided and enough space remains below the content. - * <li>Actions are shown in the expanded and heads up states. - * <li>The collapsed state gives the app's custom content 40dp of vertical space. - * <li>The collapsed state DOES include the top line of views, - * like the alerted icon or work profile badge. - * <li>The collapsed state's top line views will never show the title text. - */ - public static final int DECORATION_FULL_COMPATIBLE = 3; - - /** - * Notifications will be fully decorated as follows: - * <li>A large icon is shown if provided. - * <li>A progress bar is shown if provided and enough space remains below the content. - * <li>Actions are shown in the expanded and heads up states. - * <li>The collapsed state gives the app's custom content 20dp of vertical space. - * <li>The collapsed state DOES include the top line of views - * like the alerted icon or work profile badge. - * <li>The collapsed state's top line views will contain the title text if provided. - */ - public static final int DECORATION_FULL_CONSTRAINED = 4; - - /** - * Used by unit tests to force that this class returns its default values, which is required - * in cases where the ContentResolver instance is a mock. - * @hide - */ - public static boolean sForceDefaults; - - /** - * @return if the S notification rules should be backported to apps not yet targeting S - * @hide - */ - public static boolean shouldBackportSNotifRules(@NonNull ContentResolver contentResolver) { - return false; - } - - /** - * @return the decoration type to be applied to notifications with fully custom view. - * @hide - */ - public static int getFullyCustomViewNotifDecoration( - @NonNull ContentResolver contentResolver) { - return DECORATION_MINIMAL; - } - - /** - * @return the decoration type to be applied to notifications with DecoratedCustomViewStyle. - * @hide - */ - public static int getDecoratedCustomViewNotifDecoration( - @NonNull ContentResolver contentResolver) { - return DECORATION_PARTIAL; - } - } } 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/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 3ef6757ade60..6a71c92fbc37 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1962,14 +1962,20 @@ public class WallpaperManager { } /** - * Set the current zoom out level of the wallpaper + * Set the current zoom out level of the wallpaper. + * + * @param windowToken window requesting wallpaper zoom. Zoom level will only be applier while + * such window is visible. * @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in * * @hide */ - public void setWallpaperZoomOut(IBinder windowToken, float zoom) { + public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) { if (zoom < 0 || zoom > 1f) { - throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom); + throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom); + } + if (windowToken == null) { + throw new IllegalArgumentException("windowToken must not be null"); } try { WindowManagerGlobal.getWindowSession().setWallpaperZoomOut(windowToken, zoom); diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 747a2de80db0..da64dcd5b5f5 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -524,6 +524,16 @@ public class DeviceAdminReceiver extends BroadcastReceiver { "android.app.action.OPERATION_SAFETY_STATE_CHANGED"; /** + * Broadcast action: notify the profile owner on an organization-owned device that it needs to + * acknowledge device compliance. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED = + "android.app.action.COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED"; + + /** * An {@code int} extra specifying an {@link OperationSafetyReason}. * * @hide @@ -1116,6 +1126,29 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onOperationSafetyStateChanged(context, reason, isSafe); } + /** + * Called to notify a profile owner of an organization-owned device that it needs to acknowledge + * device compliance to allow the user to turn the profile off if needed according to the + * maximum profile time off policy. + * + * Default implementation acknowledges compliance immediately. DPC may prefer to override this + * implementation to delay acknowledgement until a successful policy sync. Until compliance is + * acknowledged the user is still free to turn the profile off, but the timer won't be reset, + * so personal apps will be suspended sooner. This callback is delivered using a foreground + * broadcast and should be handled quickly. + * + * @param context the running context as per {@link #onReceive} + * @param intent The received intent as per {@link #onReceive}. + * + * @see DevicePolicyManager#acknowledgeDeviceCompliant() + * @see DevicePolicyManager#isComplianceAcknowledgementRequired() + * @see DevicePolicyManager#setManagedProfileMaximumTimeOff(ComponentName, long) + */ + public void onComplianceAcknowledgementRequired( + @NonNull Context context, @NonNull Intent intent) { + getManager(context).acknowledgeDeviceCompliant(); + } + private boolean hasRequiredExtra(Intent intent, String extra) { if (intent.hasExtra(extra)) return true; @@ -1204,6 +1237,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver { intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) { onOperationSafetyStateChanged(context, intent); + } else if (ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED.equals(action)) { + onComplianceAcknowledgementRequired(context, intent); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 426159f43095..26e67411f174 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 */ @@ -3307,6 +3306,41 @@ public class DevicePolicyManager { "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED"; /** + * A {@code boolean} metadata to be included in a mainline module's {@code <application>} + * manifest element, which declares that the module should be considered a required app for + * managed users. + * <p>Being declared as a required app prevents removal of this package during the + * provisioning process. + * @hide + */ + @SystemApi + public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER"; + + /** + * A {@code boolean} metadata to be included in a mainline module's {@code <application>} + * manifest element, which declares that the module should be considered a required app for + * managed devices. + * <p>Being declared as a required app prevents removal of this package during the + * provisioning process. + * @hide + */ + @SystemApi + public static final String REQUIRED_APP_MANAGED_DEVICE = + "android.app.REQUIRED_APP_MANAGED_DEVICE"; + + /** + * A {@code boolean} metadata to be included in a mainline module's {@code <application>} + * manifest element, which declares that the module should be considered a required app for + * managed profiles. + * <p>Being declared as a required app prevents removal of this package during the + * provisioning process. + * @hide + */ + @SystemApi + public static final String REQUIRED_APP_MANAGED_PROFILE = + "android.app.REQUIRED_APP_MANAGED_PROFILE"; + + /** * Called by an application that is administering the device to set the password restrictions it * is imposing. After setting this, the user will not be able to enter a new password that is * not at least as restrictive as what has been set. Note that the current password will remain @@ -9990,26 +10024,24 @@ public class DevicePolicyManager { } /** - * Sets whether 5g slicing is enabled on the work profile. + * Sets whether enterprise network preference is enabled on the work profile. * - * Slicing allows operators to virtually divide their networks in portions and use different - * portions for specific use cases; for example, a corporation can have a deal/agreement with - * a carrier that all of its employees’ devices use data on a slice dedicated for enterprise - * use. + * For example, a corporation can have a deal/agreement with a carrier that all of its + * employees’ devices use data on a network preference dedicated for enterprise use. * - * By default, 5g slicing is enabled on the work profile on supported carriers and devices. - * Admins can explicitly disable it with this API. + * By default, enterprise network preference is enabled on the work profile on supported + * carriers and devices. Admins can explicitly disable it with this API. * * <p>This method can only be called by the profile owner of a managed profile. * - * @param enabled whether 5g Slice should be enabled. + * @param enabled whether enterprise network preference should be enabled. * @throws SecurityException if the caller is not the profile owner. **/ - public void setNetworkSlicingEnabled(boolean enabled) { - throwIfParentInstance("setNetworkSlicingEnabled"); + public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) { + throwIfParentInstance("setEnterpriseNetworkPreferenceEnabled"); if (mService != null) { try { - mService.setNetworkSlicingEnabled(enabled); + mService.setEnterpriseNetworkPreferenceEnabled(enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -10017,20 +10049,20 @@ public class DevicePolicyManager { } /** - * Indicates whether 5g slicing is enabled. + * Indicates whether whether enterprise network preference is enabled. * * <p>This method can be called by the profile owner of a managed profile. * - * @return whether 5g Slice is enabled. + * @return whether whether enterprise network preference is enabled. * @throws SecurityException if the caller is not the profile owner. */ - public boolean isNetworkSlicingEnabled() { - throwIfParentInstance("isNetworkSlicingEnabled"); + public boolean isEnterpriseNetworkPreferenceEnabled() { + throwIfParentInstance("isEnterpriseNetworkPreferenceEnabled"); if (mService == null) { return false; } try { - return mService.isNetworkSlicingEnabled(myUserId()); + return mService.isEnterpriseNetworkPreferenceEnabled(myUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -13377,6 +13409,63 @@ public class DevicePolicyManager { } /** + * Called by a profile owner of an organization-owned managed profile to acknowledge that the + * device is compliant and the user can turn the profile off if needed according to the maximum + * time off policy. + * + * This method should be called when the device is deemed compliant after getting + * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)} callback in + * case it is overridden. Before this method is called the user is still free to turn the + * profile off, but the timer won't be reset, so personal apps will be suspended sooner. + * + * DPCs only need acknowledging device compliance if they override + * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)}, otherwise + * compliance is acknowledged automatically. + * + * @throws IllegalStateException if the user isn't unlocked + * @see #isComplianceAcknowledgementRequired() + * @see #setManagedProfileMaximumTimeOff(ComponentName, long) + * @see DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent) + */ + public void acknowledgeDeviceCompliant() { + throwIfParentInstance("acknowledgeDeviceCompliant"); + if (mService != null) { + try { + mService.acknowledgeDeviceCompliant(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } + + /** + * Called by a profile owner of an organization-owned managed profile to query whether it needs + * to acknowledge device compliance to allow the user to turn the profile off if needed + * according to the maximum profile time off policy. + * + * Normally when acknowledgement is needed the DPC gets a + * {@link DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent)} callback. + * But if the callback was not delivered or handled for some reason, this method can be used to + * verify if acknowledgement is needed. + * + * @throws IllegalStateException if the user isn't unlocked + * @see #acknowledgeDeviceCompliant() + * @see #setManagedProfileMaximumTimeOff(ComponentName, long) + * @see DeviceAdminReceiver#onComplianceAcknowledgementRequired(Context, Intent) + */ + public boolean isComplianceAcknowledgementRequired() { + throwIfParentInstance("isComplianceAcknowledgementRequired"); + if (mService != null) { + try { + return mService.isComplianceAcknowledgementRequired(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Returns {@code true} when {@code userId} has a profile owner that is capable of resetting * password in RUNNING_LOCKED state. For that it should have at least one direct boot aware * component and have an active password reset token. Can only be called by the system. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 7901791fc7d4..5e49a985c781 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -268,8 +268,8 @@ interface IDevicePolicyManager { void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled); boolean isSecondaryLockscreenEnabled(in UserHandle userHandle); - void setNetworkSlicingEnabled(in boolean enabled); - boolean isNetworkSlicingEnabled(int userHandle); + void setEnterpriseNetworkPreferenceEnabled(in boolean enabled); + boolean isEnterpriseNetworkPreferenceEnabled(int userHandle); void setLockTaskPackages(in ComponentName who, in String[] packages); String[] getLockTaskPackages(in ComponentName who); @@ -495,6 +495,10 @@ interface IDevicePolicyManager { long getManagedProfileMaximumTimeOff(in ComponentName admin); void setManagedProfileMaximumTimeOff(in ComponentName admin, long timeoutMs); + + void acknowledgeDeviceCompliant(); + boolean isComplianceAcknowledgementRequired(); + boolean canProfileOwnerResetPasswordWhenLocked(int userId); void setNextOperationSafety(int operation, int reason); diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java index dd2ba7db03ae..e645831a31b1 100644 --- a/core/java/android/app/people/PeopleSpaceTile.java +++ b/core/java/android/app/people/PeopleSpaceTile.java @@ -56,6 +56,7 @@ public class PeopleSpaceTile implements Parcelable { private CharSequence mNotificationContent; private String mNotificationCategory; private Uri mNotificationDataUri; + private int mMessagesCount; private Intent mIntent; private long mNotificationTimestamp; private List<ConversationStatus> mStatuses; @@ -74,6 +75,7 @@ public class PeopleSpaceTile implements Parcelable { mNotificationContent = b.mNotificationContent; mNotificationCategory = b.mNotificationCategory; mNotificationDataUri = b.mNotificationDataUri; + mMessagesCount = b.mMessagesCount; mIntent = b.mIntent; mNotificationTimestamp = b.mNotificationTimestamp; mStatuses = b.mStatuses; @@ -140,6 +142,10 @@ public class PeopleSpaceTile implements Parcelable { return mNotificationDataUri; } + public int getMessagesCount() { + return mMessagesCount; + } + /** * Provides an intent to launch. If present, we should manually launch the intent on tile * click, rather than calling {@link android.content.pm.LauncherApps} to launch the shortcut ID. @@ -175,6 +181,7 @@ public class PeopleSpaceTile implements Parcelable { builder.setNotificationContent(mNotificationContent); builder.setNotificationCategory(mNotificationCategory); builder.setNotificationDataUri(mNotificationDataUri); + builder.setMessagesCount(mMessagesCount); builder.setIntent(mIntent); builder.setNotificationTimestamp(mNotificationTimestamp); builder.setStatuses(mStatuses); @@ -196,6 +203,7 @@ public class PeopleSpaceTile implements Parcelable { private CharSequence mNotificationContent; private String mNotificationCategory; private Uri mNotificationDataUri; + private int mMessagesCount; private Intent mIntent; private long mNotificationTimestamp; private List<ConversationStatus> mStatuses; @@ -320,6 +328,12 @@ public class PeopleSpaceTile implements Parcelable { return this; } + /** Sets the number of messages associated with the Tile. */ + public Builder setMessagesCount(int messagesCount) { + mMessagesCount = messagesCount; + return this; + } + /** Sets an intent to launch on click. */ public Builder setIntent(Intent intent) { mIntent = intent; @@ -359,6 +373,7 @@ public class PeopleSpaceTile implements Parcelable { mNotificationContent = in.readCharSequence(); mNotificationCategory = in.readString(); mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader()); + mMessagesCount = in.readInt(); mIntent = in.readParcelable(Intent.class.getClassLoader()); mNotificationTimestamp = in.readLong(); mStatuses = new ArrayList<>(); @@ -385,6 +400,7 @@ public class PeopleSpaceTile implements Parcelable { dest.writeCharSequence(mNotificationContent); dest.writeString(mNotificationCategory); dest.writeParcelable(mNotificationDataUri, flags); + dest.writeInt(mMessagesCount); dest.writeParcelable(mIntent, flags); dest.writeLong(mNotificationTimestamp); dest.writeParcelableList(mStatuses, flags); diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java index c8fa5c8f28e2..c71badb0d484 100644 --- a/core/java/android/app/time/TimeManager.java +++ b/core/java/android/app/time/TimeManager.java @@ -264,7 +264,7 @@ public final class TimeManager { * See {@link ExternalTimeSuggestion} for more details. * {@hide} */ - @RequiresPermission(android.Manifest.permission.SET_TIME) + @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME) public void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) { if (DEBUG) { Log.d(TAG, "suggestExternalTime called: " + timeSuggestion); 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/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 89086497a446..a96c14f216f3 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -26,6 +26,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.PropertyInvalidatedCache; +import android.companion.AssociationRequest; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; @@ -1231,8 +1232,7 @@ public final class BluetoothDevice implements Parcelable { } /** - * Get the Bluetooth alias of the remote device. - * <p>Alias is the locally modified name of a remote device. + * Get the locally modifiable name (alias) of the remote Bluetooth device. * * @return the Bluetooth alias, the friendly device name if no alias, or * null if there was a problem @@ -1258,25 +1258,35 @@ public final class BluetoothDevice implements Parcelable { } /** - * Set the Bluetooth alias of the remote device. - * <p>Alias is the locally modified name of a remote device. - * <p>This methoid overwrites the alias. The changed - * alias is saved in the local storage so that the change - * is preserved over power cycle. + * Sets the locally modifiable name (alias) of the remote Bluetooth device. This method + * overwrites the previously stored alias. The new alias is saved in local + * storage so that the change is preserved over power cycles. * - * @return true on success, false on error - * @hide + * <p>This method requires the calling app to be associated with Companion Device Manager (see + * {@link android.companion.CompanionDeviceManager#associate(AssociationRequest, + * android.companion.CompanionDeviceManager.Callback, Handler)}) and have the {@link + * android.Manifest.permission#BLUETOOTH} permission. Alternatively, if the caller has the + * {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission, they can bypass the + * Companion Device Manager association requirement. + * + * @param alias is the new locally modifiable name for the remote Bluetooth device which must be + * non-null and not the empty string. + * @return {@code true} if the alias is successfully set, {@code false} on error + * @throws IllegalArgumentException if the alias is {@code null} or the empty string */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @RequiresPermission(Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String alias) { + if (alias == null || alias.isEmpty()) { + throw new IllegalArgumentException("Cannot set the alias to null or the empty string"); + } final IBluetooth service = sService; if (service == null) { Log.e(TAG, "BT not enabled. Cannot set Remote Device name"); return false; } try { - return service.setRemoteAlias(this, alias); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + return service.setRemoteAlias(this, alias, adapter.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "", e); } diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 4fb557780d04..632572dea3c6 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -567,6 +567,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @return true if priority is set, false on error * @hide * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)} + * @removed */ @Deprecated @SystemApi diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java index 7459f871dba7..27c579b3cdfe 100644 --- a/core/java/android/bluetooth/le/ScanFilter.java +++ b/core/java/android/bluetooth/le/ScanFilter.java @@ -587,7 +587,7 @@ public final class ScanFilter implements Parcelable { * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. */ public Builder setDeviceAddress(String deviceAddress) { - return setDeviceAddress(mDeviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC); + return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC); } /** diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index b441b364cec8..2ce715630c93 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemService; import android.app.Activity; import android.app.Application; import android.app.PendingIntent; +import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; @@ -331,6 +332,33 @@ public final class CompanionDeviceManager { } /** + * Checks whether the bluetooth device represented by the mac address was recently associated + * with the companion app. This allows these devices to skip the Bluetooth pairing dialog if + * their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}. + * + * @param packageName the package name of the calling app + * @param deviceMacAddress the bluetooth device's mac address + * @param userId the calling user's identifier + * @return true if it was recently associated and we can bypass the dialog, false otherwise + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public boolean canPairWithoutPrompt(@NonNull String packageName, + @NonNull String deviceMacAddress, int userId) { + if (!checkFeaturePresent()) { + return false; + } + Objects.requireNonNull(packageName, "package name cannot be null"); + Objects.requireNonNull(deviceMacAddress, "device mac address cannot be null"); + try { + return mService.canPairWithoutPrompt(packageName, deviceMacAddress, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Register to receive callbacks whenever the associated device comes in and out of range. * * The provided device must be {@link #associate associated} with the calling app before @@ -397,6 +425,32 @@ public final class CompanionDeviceManager { mContext.getPackageName(), deviceAddress); } catch (RemoteException e) { ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); + } + } + + /** + * Associates given device with given app for the given user directly, without UI prompt. + * + * @return whether successful + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) + public boolean associate( + @NonNull String packageName, + @NonNull MacAddress macAddress) { + if (!checkFeaturePresent()) { + return false; + } + Objects.requireNonNull(packageName, "package name cannot be null"); + Objects.requireNonNull(macAddress, "mac address cannot be null"); + + UserHandle user = android.os.Process.myUserHandle(); + try { + return mService.createAssociation( + packageName, macAddress.toString(), user.getIdentifier()); + } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 95d3515abb80..83db35872f74 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -51,4 +51,6 @@ interface ICompanionDeviceManager { void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress); boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId); + + boolean createAssociation(in String packageName, in String macAddress, int userId); } 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/AppSearchPerson.java b/core/java/android/content/pm/AppSearchPerson.java index 66295eb513d8..9283e5fd3cbd 100644 --- a/core/java/android/content/pm/AppSearchPerson.java +++ b/core/java/android/content/pm/AppSearchPerson.java @@ -107,7 +107,7 @@ public class AppSearchPerson extends GenericDocument { public static class Builder extends GenericDocument.Builder<Builder> { public Builder(@NonNull final String id) { - super(id, SCHEMA_TYPE); + super(/*namespace=*/ "", id, SCHEMA_TYPE); } /** @hide */ diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java index c04d3bef3af3..b2478ca12191 100644 --- a/core/java/android/content/pm/AppSearchShortcutInfo.java +++ b/core/java/android/content/pm/AppSearchShortcutInfo.java @@ -217,9 +217,8 @@ public class AppSearchShortcutInfo extends GenericDocument { @NonNull public static AppSearchShortcutInfo instance(@NonNull final ShortcutInfo shortcutInfo) { Objects.requireNonNull(shortcutInfo); - return new Builder(shortcutInfo.getId()) + return new Builder(shortcutInfo.getPackage(), shortcutInfo.getId()) .setActivity(shortcutInfo.getActivity()) - .setNamespace(shortcutInfo.getPackage()) .setShortLabel(shortcutInfo.getShortLabel()) .setShortLabelResId(shortcutInfo.getShortLabelResourceId()) .setShortLabelResName(shortcutInfo.getTitleResName()) @@ -345,8 +344,8 @@ public class AppSearchShortcutInfo extends GenericDocument { @VisibleForTesting public static class Builder extends GenericDocument.Builder<Builder> { - public Builder(String id) { - super(id, SCHEMA_TYPE); + public Builder(String packageName, String id) { + super(/*namespace=*/ packageName, id, SCHEMA_TYPE); } /** @@ -574,16 +573,6 @@ public class AppSearchShortcutInfo extends GenericDocument { /** * @hide */ - public Builder setPackageName(@Nullable final String packageName) { - if (!TextUtils.isEmpty(packageName)) { - setNamespace(packageName); - } - return this; - } - - /** - * @hide - */ public Builder setFlags(@ShortcutInfo.ShortcutFlags final int flags) { setPropertyLong(KEY_FLAGS, flattenFlags(flags)); return this; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 1a5dad5f7596..5f3ec36438a3 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1363,7 +1363,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Indicates if the application has requested GWP-ASan to be enabled, disabled, or left * unspecified. Processes can override this setting. */ - private @GwpAsanMode int gwpAsanMode; + private @GwpAsanMode int gwpAsanMode = GWP_ASAN_DEFAULT; /** * Default (unspecified) setting of Memtag. @@ -1402,13 +1402,45 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Indicates if the application has requested Memtag to be enabled, disabled, or left * unspecified. Processes can override this setting. */ - private @MemtagMode int memtagMode; + private @MemtagMode int memtagMode = MEMTAG_DEFAULT; + + /** + * Default (unspecified) setting of nativeHeapZeroInitialized. + */ + public static final int ZEROINIT_DEFAULT = -1; + + /** + * Disable zero-initialization of the native heap in this application or process. + */ + public static final int ZEROINIT_DISABLED = 0; + + /** + * Enable zero-initialization of the native heap in this application or process. + */ + public static final int ZEROINIT_ENABLED = 1; + + /** + * @hide + */ + @IntDef(prefix = {"ZEROINIT_"}, value = { + ZEROINIT_DEFAULT, + ZEROINIT_DISABLED, + ZEROINIT_ENABLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NativeHeapZeroInitialized {} /** * Enable automatic zero-initialization of native heap memory allocations. */ + private @NativeHeapZeroInitialized int nativeHeapZeroInitialized = ZEROINIT_DEFAULT; + + /** + * If {@code true} this app requests optimized external storage access. + * The request may not be honored due to policy or other reasons. + */ @Nullable - private Boolean nativeHeapZeroInit; + private Boolean requestOptimizedExternalStorageAccess; /** * Represents the default policy. The actual policy used will depend on other properties of @@ -1563,8 +1595,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { if (memtagMode != MEMTAG_DEFAULT) { pw.println(prefix + "memtagMode=" + memtagMode); } - if (nativeHeapZeroInit != null) { - pw.println(prefix + "nativeHeapZeroInit=" + nativeHeapZeroInit); + if (nativeHeapZeroInitialized != ZEROINIT_DEFAULT) { + pw.println(prefix + "nativeHeapZeroInitialized=" + nativeHeapZeroInitialized); + } + if (requestOptimizedExternalStorageAccess != null) { + pw.println(prefix + "requestOptimizedExternalStorageAccess=" + + requestOptimizedExternalStorageAccess); } } super.dumpBack(pw, prefix); @@ -1675,8 +1711,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { if (memtagMode != MEMTAG_DEFAULT) { proto.write(ApplicationInfoProto.Detail.ENABLE_MEMTAG, memtagMode); } - if (nativeHeapZeroInit != null) { - proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT, nativeHeapZeroInit); + if (nativeHeapZeroInitialized != ZEROINIT_DEFAULT) { + proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT, + nativeHeapZeroInitialized); } proto.end(detailToken); } @@ -1791,7 +1828,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { zygotePreloadName = orig.zygotePreloadName; gwpAsanMode = orig.gwpAsanMode; memtagMode = orig.memtagMode; - nativeHeapZeroInit = orig.nativeHeapZeroInit; + nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized; + requestOptimizedExternalStorageAccess = orig.requestOptimizedExternalStorageAccess; } public String toString() { @@ -1879,7 +1917,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString8(zygotePreloadName); dest.writeInt(gwpAsanMode); dest.writeInt(memtagMode); - sForBoolean.parcel(nativeHeapZeroInit, dest, parcelableFlags); + dest.writeInt(nativeHeapZeroInitialized); + sForBoolean.parcel(requestOptimizedExternalStorageAccess, dest, parcelableFlags); } public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR @@ -1964,7 +2003,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { zygotePreloadName = source.readString8(); gwpAsanMode = source.readInt(); memtagMode = source.readInt(); - nativeHeapZeroInit = sForBoolean.unparcel(source); + nativeHeapZeroInitialized = source.readInt(); + requestOptimizedExternalStorageAccess = sForBoolean.unparcel(source); } /** @@ -2079,6 +2119,24 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** + * @return + * <ul> + * <li>{@code true} if this app requested optimized external storage access + * <li>{@code false} if this app requests to disable optimized external storage access. + * <li>{@code null} if the app didn't specify + * {@link android.R.styleable#AndroidManifestApplication_requestOptimizedExternalStorageAccess} + * in its manifest file. + * </ul> + * + * @hide + */ + @SystemApi + @Nullable + public Boolean hasRequestOptimizedExternalStorageAccess() { + return requestOptimizedExternalStorageAccess; + } + + /** * If {@code true} this app allows heap pointer tagging. * * @hide @@ -2350,7 +2408,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** {@hide} */ public void setSplitResourcePaths(String[] splitResourcePaths) { splitPublicSourceDirs = splitResourcePaths; } /** {@hide} */ public void setGwpAsanMode(@GwpAsanMode int value) { gwpAsanMode = value; } /** {@hide} */ public void setMemtagMode(@MemtagMode int value) { memtagMode = value; } - /** {@hide} */ public void setNativeHeapZeroInit(@Nullable Boolean value) { nativeHeapZeroInit = value; } + /** {@hide} */ public void setNativeHeapZeroInitialized(@NativeHeapZeroInitialized int value) { + nativeHeapZeroInitialized = value; + } + /** {@hide} */ + public void setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) { + requestOptimizedExternalStorageAccess = value; + } /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -2364,8 +2428,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** {@hide} */ public String[] getSplitResourcePaths() { return splitPublicSourceDirs; } @GwpAsanMode public int getGwpAsanMode() { return gwpAsanMode; } + + /** + * Returns whether the application has requested Memtag to be enabled, disabled, or left + * unspecified. Processes can override this setting. + */ @MemtagMode - public int getMemtagMode() { return memtagMode; } - @Nullable - public Boolean isNativeHeapZeroInit() { return nativeHeapZeroInit; } + public int getMemtagMode() { + return memtagMode; + } + + /** + * Returns whether the application has requested automatic zero-initialization of native heap + * memory allocations to be enabled or disabled. + */ + @NativeHeapZeroInitialized + public int getNativeHeapZeroInitialized() { + return nativeHeapZeroInitialized; + } } diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java index 3dd5ee102090..632c0f54375c 100644 --- a/core/java/android/content/pm/ProcessInfo.java +++ b/core/java/android/content/pm/ProcessInfo.java @@ -62,8 +62,7 @@ public class ProcessInfo implements Parcelable { /** * Enable automatic zero-initialization of native heap memory allocations. */ - @Nullable - public Boolean nativeHeapZeroInit; + public @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized; @Deprecated public ProcessInfo(@NonNull ProcessInfo orig) { @@ -71,7 +70,7 @@ public class ProcessInfo implements Parcelable { this.deniedPermissions = orig.deniedPermissions; this.gwpAsanMode = orig.gwpAsanMode; this.memtagMode = orig.memtagMode; - this.nativeHeapZeroInit = orig.nativeHeapZeroInit; + this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized; } @@ -101,7 +100,7 @@ public class ProcessInfo implements Parcelable { * @param memtagMode * Indicates if the process has requested Memtag to be enabled (in sync or async mode), * disabled, or left unspecified. - * @param nativeHeapZeroInit + * @param nativeHeapZeroInitialized * Enable automatic zero-initialization of native heap memory allocations. */ @DataClass.Generated.Member @@ -110,7 +109,7 @@ public class ProcessInfo implements Parcelable { @Nullable ArraySet<String> deniedPermissions, @ApplicationInfo.GwpAsanMode int gwpAsanMode, @ApplicationInfo.MemtagMode int memtagMode, - @Nullable Boolean nativeHeapZeroInit) { + @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) { this.name = name; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, name); @@ -121,7 +120,9 @@ public class ProcessInfo implements Parcelable { this.memtagMode = memtagMode; com.android.internal.util.AnnotationValidations.validate( ApplicationInfo.MemtagMode.class, null, memtagMode); - this.nativeHeapZeroInit = nativeHeapZeroInit; + this.nativeHeapZeroInitialized = nativeHeapZeroInitialized; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized); // onConstructed(); // You can define this method to get a callback } @@ -145,13 +146,12 @@ public class ProcessInfo implements Parcelable { byte flg = 0; if (deniedPermissions != null) flg |= 0x2; - if (nativeHeapZeroInit != null) flg |= 0x10; dest.writeByte(flg); dest.writeString(name); sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags); dest.writeInt(gwpAsanMode); dest.writeInt(memtagMode); - if (nativeHeapZeroInit != null) dest.writeBoolean(nativeHeapZeroInit); + dest.writeInt(nativeHeapZeroInitialized); } @Override @@ -170,7 +170,7 @@ public class ProcessInfo implements Parcelable { ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in); int _gwpAsanMode = in.readInt(); int _memtagMode = in.readInt(); - Boolean _nativeHeapZeroInit = (flg & 0x10) == 0 ? null : (Boolean) in.readBoolean(); + int _nativeHeapZeroInitialized = in.readInt(); this.name = _name; com.android.internal.util.AnnotationValidations.validate( @@ -182,7 +182,9 @@ public class ProcessInfo implements Parcelable { this.memtagMode = _memtagMode; com.android.internal.util.AnnotationValidations.validate( ApplicationInfo.MemtagMode.class, null, memtagMode); - this.nativeHeapZeroInit = _nativeHeapZeroInit; + this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized); // onConstructed(); // You can define this method to get a callback } @@ -202,10 +204,10 @@ public class ProcessInfo implements Parcelable { }; @DataClass.Generated( - time = 1611614699049L, + time = 1615850184524L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java", - inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.annotation.Nullable java.lang.Boolean nativeHeapZeroInit\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)") + inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/SuspendDialogInfo.java b/core/java/android/content/pm/SuspendDialogInfo.java index 60f321883e98..23945eebf546 100644 --- a/core/java/android/content/pm/SuspendDialogInfo.java +++ b/core/java/android/content/pm/SuspendDialogInfo.java @@ -65,16 +65,20 @@ public final class SuspendDialogInfo implements Parcelable { private static final String TAG = SuspendDialogInfo.class.getSimpleName(); private static final String XML_ATTR_ICON_RES_ID = "iconResId"; private static final String XML_ATTR_TITLE_RES_ID = "titleResId"; + private static final String XML_ATTR_TITLE = "title"; private static final String XML_ATTR_DIALOG_MESSAGE_RES_ID = "dialogMessageResId"; private static final String XML_ATTR_DIALOG_MESSAGE = "dialogMessage"; private static final String XML_ATTR_BUTTON_TEXT_RES_ID = "buttonTextResId"; + private static final String XML_ATTR_BUTTON_TEXT = "buttonText"; private static final String XML_ATTR_BUTTON_ACTION = "buttonAction"; private final int mIconResId; private final int mTitleResId; + private final String mTitle; private final int mDialogMessageResId; private final String mDialogMessage; private final int mNeutralButtonTextResId; + private final String mNeutralButtonText; private final int mNeutralButtonAction; /** @@ -129,6 +133,16 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * @return the title to be shown on the dialog. Returns {@code null} if {@link #getTitleResId()} + * returns a valid resource id + * @hide + */ + @Nullable + public String getTitle() { + return mTitle; + } + + /** * @return the resource id of the text to be shown in the dialog's body * @hide */ @@ -148,7 +162,7 @@ public final class SuspendDialogInfo implements Parcelable { } /** - * @return the text to be shown + * @return the text to be shown on the neutral button * @hide */ @StringRes @@ -157,6 +171,16 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * @return the text to be shown on the neutral button. Returns {@code null} if + * {@link #getNeutralButtonTextResId()} returns a valid resource id + * @hide + */ + @Nullable + public String getNeutralButtonText() { + return mNeutralButtonText; + } + + /** * @return The {@link ButtonAction} that happens on tapping this button * @hide */ @@ -174,6 +198,8 @@ public final class SuspendDialogInfo implements Parcelable { } if (mTitleResId != ID_NULL) { out.attributeInt(null, XML_ATTR_TITLE_RES_ID, mTitleResId); + } else { + XmlUtils.writeStringAttribute(out, XML_ATTR_TITLE, mTitle); } if (mDialogMessageResId != ID_NULL) { out.attributeInt(null, XML_ATTR_DIALOG_MESSAGE_RES_ID, mDialogMessageResId); @@ -182,6 +208,8 @@ public final class SuspendDialogInfo implements Parcelable { } if (mNeutralButtonTextResId != ID_NULL) { out.attributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, mNeutralButtonTextResId); + } else { + XmlUtils.writeStringAttribute(out, XML_ATTR_BUTTON_TEXT, mNeutralButtonText); } out.attributeInt(null, XML_ATTR_BUTTON_ACTION, mNeutralButtonAction); } @@ -194,8 +222,10 @@ public final class SuspendDialogInfo implements Parcelable { try { final int iconId = in.getAttributeInt(null, XML_ATTR_ICON_RES_ID, ID_NULL); final int titleId = in.getAttributeInt(null, XML_ATTR_TITLE_RES_ID, ID_NULL); + final String title = XmlUtils.readStringAttribute(in, XML_ATTR_TITLE); final int buttonTextId = in.getAttributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, ID_NULL); + final String buttonText = XmlUtils.readStringAttribute(in, XML_ATTR_BUTTON_TEXT); final int buttonAction = in.getAttributeInt(null, XML_ATTR_BUTTON_ACTION, BUTTON_ACTION_MORE_DETAILS); final int dialogMessageResId = @@ -207,9 +237,13 @@ public final class SuspendDialogInfo implements Parcelable { } if (titleId != ID_NULL) { dialogInfoBuilder.setTitle(titleId); + } else if (title != null) { + dialogInfoBuilder.setTitle(title); } if (buttonTextId != ID_NULL) { dialogInfoBuilder.setNeutralButtonText(buttonTextId); + } else if (buttonText != null) { + dialogInfoBuilder.setNeutralButtonText(buttonText); } if (dialogMessageResId != ID_NULL) { dialogInfoBuilder.setMessage(dialogMessageResId); @@ -227,7 +261,9 @@ public final class SuspendDialogInfo implements Parcelable { public int hashCode() { int hashCode = mIconResId; hashCode = 31 * hashCode + mTitleResId; + hashCode = 31 * hashCode + Objects.hashCode(mTitle); hashCode = 31 * hashCode + mNeutralButtonTextResId; + hashCode = 31 * hashCode + Objects.hashCode(mNeutralButtonText); hashCode = 31 * hashCode + mDialogMessageResId; hashCode = 31 * hashCode + Objects.hashCode(mDialogMessage); hashCode = 31 * hashCode + mNeutralButtonAction; @@ -245,10 +281,12 @@ public final class SuspendDialogInfo implements Parcelable { final SuspendDialogInfo otherDialogInfo = (SuspendDialogInfo) obj; return mIconResId == otherDialogInfo.mIconResId && mTitleResId == otherDialogInfo.mTitleResId + && Objects.equals(mTitle, otherDialogInfo.mTitle) && mDialogMessageResId == otherDialogInfo.mDialogMessageResId + && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage) && mNeutralButtonTextResId == otherDialogInfo.mNeutralButtonTextResId - && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction - && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage); + && Objects.equals(mNeutralButtonText, otherDialogInfo.mNeutralButtonText) + && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction; } @NonNull @@ -264,11 +302,19 @@ public final class SuspendDialogInfo implements Parcelable { builder.append("mTitleResId = 0x"); builder.append(Integer.toHexString(mTitleResId)); builder.append(" "); + } else if (mTitle != null) { + builder.append("mTitle = \""); + builder.append(mTitle); + builder.append("\""); } if (mNeutralButtonTextResId != ID_NULL) { builder.append("mNeutralButtonTextResId = 0x"); builder.append(Integer.toHexString(mNeutralButtonTextResId)); builder.append(" "); + } else if (mNeutralButtonText != null) { + builder.append("mNeutralButtonText = \""); + builder.append(mNeutralButtonText); + builder.append("\""); } if (mDialogMessageResId != ID_NULL) { builder.append("mDialogMessageResId = 0x"); @@ -294,27 +340,33 @@ public final class SuspendDialogInfo implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(mIconResId); dest.writeInt(mTitleResId); + dest.writeString(mTitle); dest.writeInt(mDialogMessageResId); dest.writeString(mDialogMessage); dest.writeInt(mNeutralButtonTextResId); + dest.writeString(mNeutralButtonText); dest.writeInt(mNeutralButtonAction); } private SuspendDialogInfo(Parcel source) { mIconResId = source.readInt(); mTitleResId = source.readInt(); + mTitle = source.readString(); mDialogMessageResId = source.readInt(); mDialogMessage = source.readString(); mNeutralButtonTextResId = source.readInt(); + mNeutralButtonText = source.readString(); mNeutralButtonAction = source.readInt(); } SuspendDialogInfo(Builder b) { mIconResId = b.mIconResId; mTitleResId = b.mTitleResId; + mTitle = (mTitleResId == ID_NULL) ? b.mTitle : null; mDialogMessageResId = b.mDialogMessageResId; mDialogMessage = (mDialogMessageResId == ID_NULL) ? b.mDialogMessage : null; mNeutralButtonTextResId = b.mNeutralButtonTextResId; + mNeutralButtonText = (mNeutralButtonTextResId == ID_NULL) ? b.mNeutralButtonText : null; mNeutralButtonAction = b.mNeutralButtonAction; } @@ -338,8 +390,10 @@ public final class SuspendDialogInfo implements Parcelable { private int mDialogMessageResId = ID_NULL; private String mDialogMessage; private int mTitleResId = ID_NULL; + private String mTitle; private int mIconResId = ID_NULL; private int mNeutralButtonTextResId = ID_NULL; + private String mNeutralButtonText; private int mNeutralButtonAction = BUTTON_ACTION_MORE_DETAILS; /** @@ -370,6 +424,21 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * Set the title text of the dialog. Ignored if a resource id is set via + * {@link #setTitle(int)} + * + * @param title The title of the dialog. + * @return this builder object. + * @see #setTitle(int) + */ + @NonNull + public Builder setTitle(@NonNull String title) { + Preconditions.checkStringNotEmpty(title, "Title cannot be null or empty"); + mTitle = title; + return this; + } + + /** * Set the text to show in the body of the dialog. Ignored if a resource id is set via * {@link #setMessage(int)}. * <p> @@ -427,6 +496,22 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * Set the text to be shown on the neutral button. Ignored if a resource id is set via + * {@link #setNeutralButtonText(int)} + * + * @param neutralButtonText The title of the dialog. + * @return this builder object. + * @see #setNeutralButtonText(int) + */ + @NonNull + public Builder setNeutralButtonText(@NonNull String neutralButtonText) { + Preconditions.checkStringNotEmpty(neutralButtonText, + "Button text cannot be null or empty"); + mNeutralButtonText = neutralButtonText; + return this; + } + + /** * Set the action expected to happen on neutral button tap. Defaults to * {@link #BUTTON_ACTION_MORE_DETAILS} if this is not provided. * diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index cfb6e1b572aa..5a89708b5883 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -321,6 +321,10 @@ public class UserInfo implements Parcelable { return UserManager.isUserTypeManagedProfile(userType); } + public boolean isCloneProfile() { + return UserManager.isUserTypeCloneProfile(userType); + } + @UnsupportedAppUsage public boolean isEnabled() { return (flags & FLAG_DISABLED) != FLAG_DISABLED; diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index ba6416d0a396..1c65e0014635 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -20,6 +20,7 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; @@ -251,11 +252,15 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setEnabled(boolean enabled); - ParsingPackage setGwpAsanMode(int gwpAsanMode); + ParsingPackage setGwpAsanMode(@ApplicationInfo.GwpAsanMode int gwpAsanMode); - ParsingPackage setMemtagMode(int memtagMode); + ParsingPackage setMemtagMode(@ApplicationInfo.MemtagMode int memtagMode); - ParsingPackage setNativeHeapZeroInit(@Nullable Boolean nativeHeapZeroInit); + ParsingPackage setNativeHeapZeroInitialized( + @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized); + + ParsingPackage setRequestOptimizedExternalStorageAccess( + @Nullable Boolean requestOptimizedExternalStorageAccess); ParsingPackage setCrossProfile(boolean crossProfile); diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index b3c26abc57dc..60aac76fa8a5 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -382,12 +382,18 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { private int autoRevokePermissions; - protected int gwpAsanMode; - protected int memtagMode; + @ApplicationInfo.GwpAsanMode + private int gwpAsanMode; + + @ApplicationInfo.MemtagMode + private int memtagMode; + + @ApplicationInfo.NativeHeapZeroInitialized + private int nativeHeapZeroInitialized; @Nullable @DataClass.ParcelWith(ForBoolean.class) - private Boolean nativeHeapZeroInit; + private Boolean requestOptimizedExternalStorageAccess; // TODO(chiuwinson): Non-null @Nullable @@ -1067,7 +1073,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { appInfo.zygotePreloadName = zygotePreloadName; appInfo.setGwpAsanMode(gwpAsanMode); appInfo.setMemtagMode(memtagMode); - appInfo.setNativeHeapZeroInit(nativeHeapZeroInit); + appInfo.setNativeHeapZeroInitialized(nativeHeapZeroInitialized); + appInfo.setRequestOptimizedExternalStorageAccess(requestOptimizedExternalStorageAccess); appInfo.setBaseCodePath(mBaseApkPath); appInfo.setBaseResourcePath(mBaseApkPath); appInfo.setCodePath(mPath); @@ -1202,7 +1209,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { dest.writeLong(this.mBooleans); dest.writeMap(this.mProperties); dest.writeInt(this.memtagMode); - sForBoolean.parcel(this.nativeHeapZeroInit, dest, flags); + dest.writeInt(this.nativeHeapZeroInitialized); + sForBoolean.parcel(this.requestOptimizedExternalStorageAccess, dest, flags); } public ParsingPackageImpl(Parcel in) { @@ -1325,7 +1333,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { this.mBooleans = in.readLong(); this.mProperties = in.createTypedArrayMap(Property.CREATOR); this.memtagMode = in.readInt(); - this.nativeHeapZeroInit = sForBoolean.unparcel(in); + this.nativeHeapZeroInitialized = in.readInt(); + this.requestOptimizedExternalStorageAccess = sForBoolean.unparcel(in); assignDerivedFields(); } @@ -2089,20 +2098,28 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { return getBoolean(Booleans.DIRECT_BOOT_AWARE); } + @ApplicationInfo.GwpAsanMode @Override public int getGwpAsanMode() { return gwpAsanMode; } + @ApplicationInfo.MemtagMode @Override public int getMemtagMode() { return memtagMode; } + @ApplicationInfo.NativeHeapZeroInitialized + @Override + public int getNativeHeapZeroInitialized() { + return nativeHeapZeroInitialized; + } + @Nullable @Override - public Boolean isNativeHeapZeroInit() { - return nativeHeapZeroInit; + public Boolean hasRequestOptimizedExternalStorageAccess() { + return requestOptimizedExternalStorageAccess; } @Override @@ -2537,24 +2554,30 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override - public ParsingPackageImpl setGwpAsanMode(int value) { + public ParsingPackageImpl setGwpAsanMode(@ApplicationInfo.GwpAsanMode int value) { gwpAsanMode = value; return this; } @Override - public ParsingPackageImpl setMemtagMode(int value) { + public ParsingPackageImpl setMemtagMode(@ApplicationInfo.MemtagMode int value) { memtagMode = value; return this; } @Override - public ParsingPackageImpl setNativeHeapZeroInit(@Nullable Boolean value) { - nativeHeapZeroInit = value; + public ParsingPackageImpl setNativeHeapZeroInitialized( + @ApplicationInfo.NativeHeapZeroInitialized int value) { + nativeHeapZeroInitialized = value; return this; } @Override + public ParsingPackageImpl setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) { + requestOptimizedExternalStorageAccess = value; + return this; + } + @Override public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) { return setBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE, value); } diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java index 9f5218371393..cfd828ec7d3e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -887,20 +887,25 @@ public interface ParsingPackageRead extends Parcelable { * @see ApplicationInfo#gwpAsanMode * @see R.styleable#AndroidManifest_gwpAsanMode */ + @ApplicationInfo.GwpAsanMode int getGwpAsanMode(); /** * @see ApplicationInfo#memtagMode * @see R.styleable#AndroidManifest_memtagMode */ + @ApplicationInfo.MemtagMode int getMemtagMode(); - /** - * @see ApplicationInfo#nativeHeapZeroInit - * @see R.styleable#AndroidManifest_nativeHeapZeroInit + /** + * @see ApplicationInfo#nativeHeapZeroInitialized + * @see R.styleable#AndroidManifest_nativeHeapZeroInitialized */ + @ApplicationInfo.NativeHeapZeroInitialized + int getNativeHeapZeroInitialized(); + @Nullable - Boolean isNativeHeapZeroInit(); + Boolean hasRequestOptimizedExternalStorageAccess(); // TODO(b/135203078): Hide and enforce going through PackageInfoUtils ApplicationInfo toAppInfoWithoutState(); diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 2be0157836ae..9f69d0c0afd3 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -2008,9 +2008,17 @@ public class ParsingPackageUtils { pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1)); pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1)); - if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInit)) { - pkg.setNativeHeapZeroInit(sa.getBoolean( - R.styleable.AndroidManifestApplication_nativeHeapZeroInit, false)); + if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized)) { + Boolean v = sa.getBoolean( + R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized, false); + pkg.setNativeHeapZeroInitialized( + v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED); + } + if (sa.hasValue( + R.styleable.AndroidManifestApplication_requestOptimizedExternalStorageAccess)) { + pkg.setRequestOptimizedExternalStorageAccess(sa.getBoolean(R.styleable + .AndroidManifestApplication_requestOptimizedExternalStorageAccess, + false)); } } finally { sa.recycle(); 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/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java index 89fef9d8e0dd..54a60d349331 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcess.java +++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java @@ -42,10 +42,12 @@ public class ParsedProcess implements Parcelable { @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class) protected Set<String> deniedPermissions = emptySet(); + @ApplicationInfo.GwpAsanMode protected int gwpAsanMode = ApplicationInfo.GWP_ASAN_DEFAULT; + @ApplicationInfo.MemtagMode protected int memtagMode = ApplicationInfo.MEMTAG_DEFAULT; - @Nullable - protected Boolean nativeHeapZeroInit = null; + @ApplicationInfo.NativeHeapZeroInitialized + protected int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT; public ParsedProcess() { } @@ -78,9 +80,9 @@ public class ParsedProcess implements Parcelable { public ParsedProcess( @NonNull String name, @NonNull Set<String> deniedPermissions, - int gwpAsanMode, - int memtagMode, - @Nullable Boolean nativeHeapZeroInit) { + @ApplicationInfo.GwpAsanMode int gwpAsanMode, + @ApplicationInfo.MemtagMode int memtagMode, + @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) { this.name = name; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, name); @@ -88,8 +90,14 @@ public class ParsedProcess implements Parcelable { com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, deniedPermissions); this.gwpAsanMode = gwpAsanMode; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode); this.memtagMode = memtagMode; - this.nativeHeapZeroInit = nativeHeapZeroInit; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.MemtagMode.class, null, memtagMode); + this.nativeHeapZeroInitialized = nativeHeapZeroInitialized; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized); // onConstructed(); // You can define this method to get a callback } @@ -105,18 +113,18 @@ public class ParsedProcess implements Parcelable { } @DataClass.Generated.Member - public int getGwpAsanMode() { + public @ApplicationInfo.GwpAsanMode int getGwpAsanMode() { return gwpAsanMode; } @DataClass.Generated.Member - public int getMemtagMode() { + public @ApplicationInfo.MemtagMode int getMemtagMode() { return memtagMode; } @DataClass.Generated.Member - public @Nullable Boolean getNativeHeapZeroInit() { - return nativeHeapZeroInit; + public @ApplicationInfo.NativeHeapZeroInitialized int getNativeHeapZeroInitialized() { + return nativeHeapZeroInitialized; } @DataClass.Generated.Member @@ -136,14 +144,11 @@ public class ParsedProcess implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - byte flg = 0; - if (nativeHeapZeroInit != null) flg |= 0x10; - dest.writeByte(flg); dest.writeString(name); sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags); dest.writeInt(gwpAsanMode); dest.writeInt(memtagMode); - if (nativeHeapZeroInit != null) dest.writeBoolean(nativeHeapZeroInit); + dest.writeInt(nativeHeapZeroInitialized); } @Override @@ -157,12 +162,11 @@ public class ParsedProcess implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - byte flg = in.readByte(); String _name = in.readString(); Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in); int _gwpAsanMode = in.readInt(); int _memtagMode = in.readInt(); - Boolean _nativeHeapZeroInit = (flg & 0x10) == 0 ? null : (Boolean) in.readBoolean(); + int _nativeHeapZeroInitialized = in.readInt(); this.name = _name; com.android.internal.util.AnnotationValidations.validate( @@ -171,8 +175,14 @@ public class ParsedProcess implements Parcelable { com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, deniedPermissions); this.gwpAsanMode = _gwpAsanMode; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode); this.memtagMode = _memtagMode; - this.nativeHeapZeroInit = _nativeHeapZeroInit; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.MemtagMode.class, null, memtagMode); + this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized; + com.android.internal.util.AnnotationValidations.validate( + ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized); // onConstructed(); // You can define this method to get a callback } @@ -192,10 +202,10 @@ public class ParsedProcess implements Parcelable { }; @DataClass.Generated( - time = 1611615591258L, + time = 1615850515058L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java", - inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprotected int gwpAsanMode\nprotected int memtagMode\nprotected @android.annotation.Nullable java.lang.Boolean nativeHeapZeroInit\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)") + inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprotected @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprotected @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprotected @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java index 257977467312..e417e7407fbb 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java @@ -17,6 +17,7 @@ package android.content.pm.parsing.component; import android.annotation.NonNull; +import android.content.pm.ApplicationInfo; import android.content.pm.parsing.ParsingPackage; import android.content.pm.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; @@ -104,9 +105,11 @@ public class ParsedProcessUtils { proc.gwpAsanMode = sa.getInt(R.styleable.AndroidManifestProcess_gwpAsanMode, -1); proc.memtagMode = sa.getInt(R.styleable.AndroidManifestProcess_memtagMode, -1); - if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInit)) { - proc.nativeHeapZeroInit = - sa.getBoolean(R.styleable.AndroidManifestProcess_nativeHeapZeroInit, false); + if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized)) { + Boolean v = sa.getBoolean( + R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized, false); + proc.nativeHeapZeroInitialized = + v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED; } } finally { sa.recycle(); diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index d187f60ecd44..33920c676170 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -16,6 +16,7 @@ package android.content.pm.verify.domain; +import android.annotation.CheckResult; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,6 +30,8 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; +import com.android.internal.util.CollectionUtils; + import java.util.List; import java.util.Set; import java.util.UUID; @@ -80,22 +83,6 @@ public final class DomainVerificationManager { public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; /** - * The provided domain set ID was null. This is a developer error. - * - * @hide - */ - @SystemApi - public static final int ERROR_DOMAIN_SET_ID_NULL = 2; - - /** - * The provided set of domains was null or empty. This is a developer error. - * - * @hide - */ - @SystemApi - public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; - - /** * The provided set of domains contains a domain not declared by the target package. This * usually means the work being processed by the verification agent is outdated and a new * request should be scheduled, which should already be in progress as part of the @@ -104,7 +91,7 @@ public final class DomainVerificationManager { * @hide */ @SystemApi - public static final int ERROR_UNKNOWN_DOMAIN = 4; + public static final int ERROR_UNKNOWN_DOMAIN = 2; /** * The system was unable to select the domain for approval. This indicates another application @@ -114,17 +101,7 @@ public final class DomainVerificationManager { * @hide */ @SystemApi - public static final int ERROR_UNABLE_TO_APPROVE = 5; - - /** - * The provided state code is incorrect. The domain verification agent is only allowed to - * assign {@link DomainVerificationInfo#STATE_SUCCESS} or error codes equal to or greater than - * {@link DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED}. - * - * @hide - */ - @SystemApi - public static final int ERROR_INVALID_STATE_CODE = 6; + public static final int ERROR_UNABLE_TO_APPROVE = 3; /** * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API. @@ -138,11 +115,8 @@ public final class DomainVerificationManager { */ @IntDef(prefix = {"ERROR_"}, value = { ERROR_DOMAIN_SET_ID_INVALID, - ERROR_DOMAIN_SET_ID_NULL, - ERROR_DOMAIN_SET_NULL_OR_EMPTY, ERROR_UNKNOWN_DOMAIN, ERROR_UNABLE_TO_APPROVE, - ERROR_INVALID_STATE_CODE }) public @interface Error { } @@ -205,10 +179,7 @@ public final class DomainVerificationManager { */ @SystemApi @Nullable - @RequiresPermission(anyOf = { - android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, - android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION - }) + @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) throws NameNotFoundException { try { @@ -232,19 +203,21 @@ public final class DomainVerificationManager { * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains List of host names to change the state of. * @param state See {@link DomainVerificationInfo#getHostToStateMap()}. - * @throws NameNotFoundException If the ID is known to be good, but the package is - * unavailable. This may be because the package is installed on - * a volume that is no longer mounted. This error is - * unrecoverable until the package is available again, and - * should not be re-tried except on a time scheduled basis. * @return error code or {@link #STATUS_OK} if successful - * + * @throws NameNotFoundException If the ID is known to be good, but the package is + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. * @hide */ + @CheckResult @SystemApi @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains, int state) throws NameNotFoundException { + validateInput(domainSetId, domains); + try { return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), new DomainSet(domains), state); @@ -312,19 +285,21 @@ public final class DomainVerificationManager { * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains The domains to toggle the state of. * @param enabled Whether or not the app should automatically open the domains specified. - * @throws NameNotFoundException If the ID is known to be good, but the package is - * unavailable. This may be because the package is installed on - * a volume that is no longer mounted. This error is - * unrecoverable until the package is available again, and - * should not be re-tried except on a time scheduled basis. * @return error code or {@link #STATUS_OK} if successful - * + * @throws NameNotFoundException If the ID is known to be good, but the package is + * unavailable. This may be because the package is installed on + * a volume that is no longer mounted. This error is + * unrecoverable until the package is available again, and + * should not be re-tried except on a time scheduled basis. * @hide */ + @CheckResult @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException { + validateInput(domainSetId, domains); + try { return mDomainVerificationManager.setDomainVerificationUserSelection( domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId()); @@ -405,4 +380,12 @@ public final class DomainVerificationManager { return exception; } } + + private void validateInput(@Nullable UUID domainSetId, @Nullable Set<String> domains) { + if (domainSetId == null) { + throw new IllegalArgumentException("domainSetId cannot be null"); + } else if (CollectionUtils.isEmpty(domains)) { + throw new IllegalArgumentException("Provided domain set cannot be empty"); + } + } } 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/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 5eaa7662b250..2a349e9a4600 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -46,7 +46,7 @@ import java.util.Arrays; * The indices used to retrieve values from this structure correspond to * the positions of the attributes given to obtainStyledAttributes. */ -public class TypedArray { +public class TypedArray implements AutoCloseable { static TypedArray obtain(Resources res, int len) { TypedArray attrs = res.mTypedArrayPool.acquire(); @@ -1253,6 +1253,17 @@ public class TypedArray { } /** + * Recycles the TypedArray, to be re-used by a later caller. After calling + * this function you must not ever touch the typed array again. + * + * @see #recycle() + * @throws RuntimeException if the TypedArray has already been recycled. + */ + public void close() { + recycle(); + } + + /** * Extracts theme attributes from a typed array for later resolution using * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}. * Removes the entries from the typed array so that subsequent calls to typed diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 07ebbaff67ea..6654c2c71049 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1210,6 +1210,25 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.util.Range<Float>>("android.control.zoomRatioRange", new TypeReference<android.util.Range<Float>>() {{ }}); /** + * <p>List of available high speed video size, fps range and max batch size configurations + * supported by the camera device, in the format of + * (width, height, fps_min, fps_max, batch_size_max), + * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.control.availableHighSpeedVideoConfigurations, for configurations + * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Range of valid values:</b><br></p> + * <p>For each configuration, the fps_max >= 120fps.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.HighSpeedVideoConfiguration[]> CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.HighSpeedVideoConfiguration[]>("android.control.availableHighSpeedVideoConfigurationsMaximumResolution", android.hardware.camera2.params.HighSpeedVideoConfiguration[].class); + + /** * <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera * device.</p> * <p>Full-capability camera devices must always support OFF; camera devices that support @@ -1770,6 +1789,48 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<float[]>("android.lens.distortion", float[].class); /** + * <p>The correction coefficients to correct for this camera device's + * radial and tangential lens distortion for a + * CaptureRequest with {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to {@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Units</b>: + * Unitless coefficients.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p> + * + * @see CameraCharacteristics#LENS_DISTORTION + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + @PublicKey + @NonNull + public static final Key<float[]> LENS_DISTORTION_MAXIMUM_RESOLUTION = + new Key<float[]>("android.lens.distortionMaximumResolution", float[].class); + + /** + * <p>The parameters for this camera device's intrinsic + * calibration when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Units</b>: + * Pixels in the + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} + * coordinate system.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p> + * + * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + @PublicKey + @NonNull + public static final Key<float[]> LENS_INTRINSIC_CALIBRATION_MAXIMUM_RESOLUTION = + new Key<float[]>("android.lens.intrinsicCalibrationMaximumResolution", float[].class); + + /** * <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported * by this camera device.</p> * <p>Full-capability camera devices will always support OFF and FAST.</p> @@ -2056,6 +2117,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR ULTRA_HIGH_RESOLUTION_SENSOR}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING REMOSAIC_REPROCESSING}</li> * </ul> * * <p>This key is available on all devices.</p> @@ -2077,6 +2140,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA * @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA * @see #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING + * @see #REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR + * @see #REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING */ @PublicKey @NonNull @@ -2535,8 +2600,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * set to either OFF or FAST.</p> * <p>When multiple streams are used in a request, the minimum frame * duration will be max(individual stream min durations).</p> - * <p>The minimum frame duration of a stream (of a particular format, size) - * is the same regardless of whether the stream is input or output.</p> * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and * android.scaler.availableStallDurations for more details about * calculating the max frame rate.</p> @@ -2916,10 +2979,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * configurations which belong to this physical camera, and it will advertise and will only * advertise the maximum supported resolutions for a particular format.</p> * <p>If this camera device isn't a physical camera device constituting a logical camera, - * but a standalone ULTRA_HIGH_RESOLUTION_SENSOR camera, this field represents the - * multi-resolution input/output stream configurations of default mode and max resolution - * modes. The sizes will be the maximum resolution of a particular format for default mode - * and max resolution mode.</p> + * but a standalone {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * camera, this field represents the multi-resolution input/output stream configurations of + * default mode and max resolution modes. The sizes will be the maximum resolution of a + * particular format for default mode and max resolution mode.</p> * <p>This field will only be advertised if the device is a physical camera of a * logical multi-camera device or an ultra high resolution sensor camera. For a logical * multi-camera, the camera API will derive the logical camera’s multi-resolution stream @@ -2977,6 +3040,132 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.MultiResolutionStreamConfigurationMap>("android.scaler.multiResolutionStreamConfigurationMap", android.hardware.camera2.params.MultiResolutionStreamConfigurationMap.class); /** + * <p>The available stream configurations that this + * camera device supports (i.e. format, width, height, output/input stream) for a + * CaptureRequest with {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.scaler.availableStreamConfigurations, for configurations + * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Not all output formats may be supported in a configuration with + * an input stream of a particular format. For more details, see + * android.scaler.availableInputOutputFormatsMapMaximumResolution.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.scaler.availableStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination when the camera device is sent a CaptureRequest with + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.scaler.availableMinFrameDurations, for configurations + * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>When multiple streams are used in a request (if supported, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} + * is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }), the + * minimum frame duration will be max(individual stream min durations).</p> + * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and + * android.scaler.availableStallDurationsMaximumResolution for more details about + * calculating the max frame rate.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> SCALER_AVAILABLE_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.scaler.availableMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination when CaptureRequests are submitted with + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }</p> + * <p>Analogous to android.scaler.availableMinFrameDurations, for configurations + * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> SCALER_AVAILABLE_STALL_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.scaler.availableStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>The available stream configurations that this + * camera device supports when given a CaptureRequest with {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} + * set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }; + * also includes the minimum frame durations + * and the stall durations for each format/size combination.</p> + * <p>Analogous to {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap} for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.StreamConfigurationMap> SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationMap>("android.scaler.streamConfigurationMapMaximumResolution", android.hardware.camera2.params.StreamConfigurationMap.class); + + /** + * <p>The mapping of image formats that are supported by this + * camera device for input streams, to their corresponding output formats, when + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.scaler.availableInputOutputFormatsMap for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.ReprocessFormatsMap> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.ReprocessFormatsMap>("android.scaler.availableInputOutputFormatsMapMaximumResolution", android.hardware.camera2.params.ReprocessFormatsMap.class); + + /** + * <p>An array of mandatory stream combinations which are applicable when + * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set + * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }. + * This is an app-readable conversion of the maximum resolution mandatory stream combination + * {@link android.hardware.camera2.CameraDevice#createCaptureSession tables}.</p> + * <p>The array of + * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is + * generated according to the documented + * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline} for each + * device which has the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability. + * Clients can use the array as a quick reference to find an appropriate camera stream + * combination. + * The mandatory stream combination array will be {@code null} in case the device is not an + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * device.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.MandatoryStreamCombination[]> SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS = + new Key<android.hardware.camera2.params.MandatoryStreamCombination[]>("android.scaler.mandatoryMaximumResolutionStreamCombinations", android.hardware.camera2.params.MandatoryStreamCombination[].class); + + /** * <p>The area of the image sensor which corresponds to active pixels after any geometric * distortion correction has been applied.</p> * <p>This is the rectangle representing the size of the active region of the sensor (i.e. @@ -3292,6 +3481,101 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.graphics.Rect>("android.sensor.info.preCorrectionActiveArraySize", android.graphics.Rect.class); /** + * <p>The area of the image sensor which corresponds to active pixels after any geometric + * distortion correction has been applied, when the sensor runs in maximum resolution mode.</p> + * <p>Analogous to {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} + * is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }. + * Refer to {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} for details, with sensor array related keys + * replaced with their + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } + * counterparts. + * This key will only be present for devices which advertise the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability.</p> + * <p><b>Units</b>: Pixel coordinates on the image sensor</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + @PublicKey + @NonNull + public static final Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION = + new Key<android.graphics.Rect>("android.sensor.info.activeArraySizeMaximumResolution", android.graphics.Rect.class); + + /** + * <p>Dimensions of the full pixel array, possibly + * including black calibration pixels, when the sensor runs in maximum resolution mode. + * Analogous to {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize}, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is + * set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>The pixel count of the full pixel array of the image sensor, which covers + * {@link CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE android.sensor.info.physicalSize} area. This represents the full pixel dimensions of + * the raw buffers produced by this sensor, when it runs in maximum resolution mode. That + * is, when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }. + * This key will only be present for devices which advertise the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability.</p> + * <p><b>Units</b>: Pixels</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + @PublicKey + @NonNull + public static final Key<android.util.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION = + new Key<android.util.Size>("android.sensor.info.pixelArraySizeMaximumResolution", android.util.Size.class); + + /** + * <p>The area of the image sensor which corresponds to active pixels prior to the + * application of any geometric distortion correction, when the sensor runs in maximum + * resolution mode. This key must be used for crop / metering regions, only when + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, + * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }. + * This key will only be present for devices which advertise the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability.</p> + * <p><b>Units</b>: Pixel coordinates on the image sensor</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + @PublicKey + @NonNull + public static final Key<android.graphics.Rect> SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION = + new Key<android.graphics.Rect>("android.sensor.info.preCorrectionActiveArraySizeMaximumResolution", android.graphics.Rect.class); + + /** + * <p>Dimensions of the group of pixels which are under the same color filter. + * This specifies the width and height (pair of integers) of the group of pixels which fall + * under the same color filter for ULTRA_HIGH_RESOLUTION sensors.</p> + * <p>Sensors can have pixels grouped together under the same color filter in order + * to improve various aspects of imaging such as noise reduction, low light + * performance etc. These groups can be of various sizes such as 2X2 (quad bayer), + * 3X3 (nona-bayer). This key specifies the length and width of the pixels grouped under + * the same color filter.</p> + * <p>This key will not be present if REMOSAIC_REPROCESSING is not supported, since RAW images + * will have a regular bayer pattern.</p> + * <p>This key will not be present for sensors which don't have the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability.</p> + * <p><b>Units</b>: Pixels</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + public static final Key<android.util.Size> SENSOR_INFO_BINNING_FACTOR = + new Key<android.util.Size>("android.sensor.info.binningFactor", android.util.Size.class); + + /** * <p>The standard reference illuminant used as the scene light source when * calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1}, * {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and @@ -4150,6 +4434,111 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); /** + * <p>The available depth dataspace stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream) when a CaptureRequest is submitted with + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.depth.availableDepthStreamConfigurations, for configurations which + * are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDepthStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for depth output formats when a CaptureRequest is submitted with + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.depth.availableDepthMinFrameDurations, for configurations which + * are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and + * android.scaler.availableStallDurationsMaximumResolution for more details about + * calculating the max frame rate.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_FRAME_DURATION + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for depth streams for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.depth.availableDepthStallDurations, for configurations which + * are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>The available dynamic depth dataspace stream + * configurations that this camera device supports (i.e. format, width, height, + * output/input stream) for CaptureRequests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.depth.availableDynamicDepthStreamConfigurations, for configurations + * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDynamicDepthStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for dynamic depth output streams for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.depth.availableDynamicDepthMinFrameDurations, for configurations + * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for dynamic depth streams for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Analogous to android.depth.availableDynamicDepthStallDurations, for configurations + * which are applicable when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** * <p>String containing the ids of the underlying physical cameras.</p> * <p>For a logical camera, this is concatenation of all underlying physical camera IDs. * The null terminator for physical camera ID must be preserved so that the whole string @@ -4286,6 +4675,47 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_STALL_DURATIONS = new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class); + /** + * <p>The available HEIC (ISO/IEC 23008-12) stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).</p> + * <p>Refer to android.heic.availableHeicStreamConfigurations for details.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class); + + /** + * <p>This lists the minimum frame duration for each + * format/size combination for HEIC output formats for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicMinFrameDurations for details.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for HEIC streams for CaptureRequests where + * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> + * <p>Refer to android.heic.availableHeicStallDurations for details.</p> + * <p><b>Units</b>: (format, width, height, ns) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @hide + */ + public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION = + new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index b7b1a147c822..726bca4d1cd2 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -507,6 +507,18 @@ public final class CameraManager { return new CameraExtensionCharacteristics(mContext, cameraId, chars); } + private Map<String, CameraCharacteristics> getPhysicalIdToCharsMap( + CameraCharacteristics chars) throws CameraAccessException { + HashMap<String, CameraCharacteristics> physicalIdsToChars = + new HashMap<String, CameraCharacteristics>(); + Set<String> physicalCameraIds = chars.getPhysicalCameraIds(); + for (String physicalCameraId : physicalCameraIds) { + CameraCharacteristics physicalChars = getCameraCharacteristics(physicalCameraId); + physicalIdsToChars.put(physicalCameraId, physicalChars); + } + return physicalIdsToChars; + } + /** * Helper for opening a connection to a camera with the given ID. * @@ -535,17 +547,18 @@ public final class CameraManager { throws CameraAccessException { CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; - + Map<String, CameraCharacteristics> physicalIdsToChars = + getPhysicalIdToCharsMap(characteristics); synchronized (mLock) { ICameraDeviceUser cameraUser = null; - android.hardware.camera2.impl.CameraDeviceImpl deviceImpl = new android.hardware.camera2.impl.CameraDeviceImpl( cameraId, callback, executor, characteristics, + physicalIdsToChars, mContext.getApplicationInfo().targetSdkVersion, mContext); diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 924dcee31a33..d4da3b9526c3 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -586,7 +586,7 @@ public abstract class CameraMetadata<TKey> { * that is, {@link android.graphics.ImageFormat#PRIVATE } is included in the lists of * formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li> * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput } - * returns non empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li> + * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li> * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.PRIVATE)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.PRIVATE)}</li> * <li>Using {@link android.graphics.ImageFormat#PRIVATE } does not cause a frame rate drop * relative to the sensor's maximum capture rate (at that resolution).</li> @@ -1114,6 +1114,63 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15; + /** + * <p>This camera device is capable of producing ultra high resolution images in + * addition to the image sizes described in the + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}. + * It can operate in 'default' mode and 'max resolution' mode. It generally does this + * by binning pixels in 'default' mode and not binning them in 'max resolution' mode. + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code> describes the streams supported in 'default' + * mode. + * The stream configurations supported in 'max resolution' mode are described by + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR = 16; + + /** + * <p>The device supports reprocessing from the <code>RAW_SENSOR</code> format with a bayer pattern + * given by {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor} (m x n group of pixels with the same + * color filter) to a remosaiced regular bayer pattern.</p> + * <p>This capability will only be present for devices with + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability. When + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * devices do not advertise this capability, + * {@link android.graphics.ImageFormat#RAW_SENSOR } images will already have a + * regular bayer pattern.</p> + * <p>If a <code>RAW_SENSOR</code> stream is requested along with another non-RAW stream in a + * {@link android.hardware.camera2.CaptureRequest } (if multiple streams are supported + * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }), + * the <code>RAW_SENSOR</code> stream will have a regular bayer pattern.</p> + * <p>This capability requires the camera device to support the following : + * * The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below + * refers to the one, described by + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>. + * * One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>. + * * {@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input + * format, that is, {@link android.graphics.ImageFormat#RAW_SENSOR } is included in the + * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }. + * * {@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput } + * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }. + * * Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)} + * * Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate + * drop relative to the sensor's maximum capture rate (at that resolution). + * * No CaptureRequest controls will be applicable when a request has an input target + * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</p> + * + * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION + * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR + * @see CaptureRequest#SENSOR_PIXEL_MODE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING = 17; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // @@ -2955,6 +3012,27 @@ public abstract class CameraMetadata<TKey> { public static final int SENSOR_TEST_PATTERN_MODE_CUSTOM1 = 256; // + // Enumeration values for CaptureRequest#SENSOR_PIXEL_MODE + // + + /** + * <p>This is the default sensor pixel mode. This is the only sensor pixel mode + * supported unless a camera device advertises + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }.</p> + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + public static final int SENSOR_PIXEL_MODE_DEFAULT = 0; + + /** + * <p>This sensor pixel mode is offered by devices with capability + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }. + * In this mode, sensors typically do not bin pixels, as a result can offer larger + * image sizes.</p> + * @see CaptureRequest#SENSOR_PIXEL_MODE + */ + public static final int SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION = 1; + + // // Enumeration values for CaptureRequest#SHADING_MODE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 9ac2ff57ad7b..906256d70197 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1391,6 +1391,13 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction * mode.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on * distortion correction capability and mode</p> @@ -1405,7 +1412,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -1603,6 +1613,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction * mode.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on * distortion correction capability and mode</p> @@ -1617,7 +1633,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -1808,6 +1827,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * the scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction * mode.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on * distortion correction capability and mode</p> @@ -1822,7 +1847,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -2896,6 +2924,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * coordinate system is post-zoom, meaning that the activeArraySize or * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates relative to * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction @@ -2908,7 +2942,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM * @see CameraCharacteristics#SCALER_CROPPING_TYPE * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -3214,6 +3251,53 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> new Key<Integer>("android.sensor.testPatternMode", int.class); /** + * <p>Switches sensor pixel mode between maximum resolution mode and default mode.</p> + * <p>This key controls whether the camera sensor operates in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } + * mode or not. By default, all camera devices operate in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. + * When operating in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors + * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability would typically perform pixel binning in order to improve low light + * performance, noise reduction etc. However, in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } + * mode (supported only + * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * sensors), sensors typically operate in unbinned mode allowing for a larger image size. + * The stream configurations supported in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } + * mode are also different from those of + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. + * They can be queried through + * {@link android.hardware.camera2.CameraCharacteristics#get } with + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }. + * Unless reported by both + * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code> + * must not be mixed in the same CaptureRequest. In other words, these outputs are + * exclusive to each other. + * This key does not need to be set for reprocess requests.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li> + * <li>{@link #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION MAXIMUM_RESOLUTION}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION + * @see #SENSOR_PIXEL_MODE_DEFAULT + * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION + */ + @PublicKey + @NonNull + public static final Key<Integer> SENSOR_PIXEL_MODE = + new Key<Integer>("android.sensor.pixelMode", int.class); + + /** * <p>Quality of lens shading correction applied * to the image data.</p> * <p>When set to OFF mode, no lens shading correction will be applied by the diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index e7457e714f43..6ff68c11a974 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -812,6 +812,13 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction * mode.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on * distortion correction capability and mode</p> @@ -826,7 +833,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -1274,6 +1284,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction * mode.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on * distortion correction capability and mode</p> @@ -1288,7 +1304,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -1890,6 +1909,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * the scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction * mode.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates within {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on * distortion correction capability and mode</p> @@ -1904,7 +1929,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureRequest#DISTORTION_CORRECTION_MODE * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -3565,6 +3593,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * coordinate system is post-zoom, meaning that the activeArraySize or * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p> + * <p>For camera devices with the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability, {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} / + * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the + * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p> * <p><b>Units</b>: Pixel coordinates relative to * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction @@ -3577,7 +3611,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM * @see CameraCharacteristics#SCALER_CROPPING_TYPE * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CaptureRequest#SENSOR_PIXEL_MODE */ @PublicKey @NonNull @@ -4107,6 +4144,69 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.sensor.dynamicWhiteLevel", int.class); /** + * <p>Switches sensor pixel mode between maximum resolution mode and default mode.</p> + * <p>This key controls whether the camera sensor operates in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } + * mode or not. By default, all camera devices operate in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. + * When operating in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode, sensors + * with {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability would typically perform pixel binning in order to improve low light + * performance, noise reduction etc. However, in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } + * mode (supported only + * by {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * sensors), sensors typically operate in unbinned mode allowing for a larger image size. + * The stream configurations supported in + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } + * mode are also different from those of + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. + * They can be queried through + * {@link android.hardware.camera2.CameraCharacteristics#get } with + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }. + * Unless reported by both + * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</code> + * must not be mixed in the same CaptureRequest. In other words, these outputs are + * exclusive to each other. + * This key does not need to be set for reprocess requests.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #SENSOR_PIXEL_MODE_DEFAULT DEFAULT}</li> + * <li>{@link #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION MAXIMUM_RESOLUTION}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION + * @see #SENSOR_PIXEL_MODE_DEFAULT + * @see #SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION + */ + @PublicKey + @NonNull + public static final Key<Integer> SENSOR_PIXEL_MODE = + new Key<Integer>("android.sensor.pixelMode", int.class); + + /** + * <p>Whether <code>RAW</code> images requested have their bayer pattern as described by + * {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p> + * <p>This key will only be present in devices advertisting the + * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } + * capability which also advertise <code>REMOSAIC_REPROCESSING</code> capability. On all other devices + * RAW targets will have a regular bayer pattern.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR + */ + @PublicKey + @NonNull + public static final Key<Boolean> SENSOR_RAW_BINNING_FACTOR_USED = + new Key<Boolean>("android.sensor.rawBinningFactorUsed", boolean.class); + + /** * <p>Quality of lens shading correction applied * to the image data.</p> * <p>When set to OFF mode, no lens shading correction will be applied by the diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java index 0a4298162027..2920e670f15b 100644 --- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java @@ -20,6 +20,7 @@ import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraOfflineSession; import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback; import android.hardware.camera2.CaptureRequest; @@ -81,11 +82,17 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl if (request == null) { throw new IllegalArgumentException("Input capture request must not be null"); } + CameraCharacteristics.Key<StreamConfigurationMap> ck = + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP; + Integer sensorPixelMode = request.get(CaptureRequest.SENSOR_PIXEL_MODE); + if (sensorPixelMode != null && sensorPixelMode == + CameraMetadata.SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION) { + ck = CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION; + } Collection<Surface> outputSurfaces = request.getTargets(); Range<Integer> fpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); - StreamConfigurationMap config = - mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + StreamConfigurationMap config = mCharacteristics.get(ck); SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputSurfaces, fpsRange, config); // Request list size: to limit the preview to 30fps, need use maxFps/30; to maximize diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 4defd23231b8..b578bf80c231 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainRunna import android.annotation.NonNull; import android.content.Context; +import android.graphics.ImageFormat; import android.hardware.ICameraService; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; @@ -62,6 +63,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.HashMap; +import java.util.Map; import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -114,6 +117,7 @@ public class CameraDeviceImpl extends CameraDevice private final String mCameraId; private final CameraCharacteristics mCharacteristics; + private final Map<String, CameraCharacteristics> mPhysicalIdsToChars; private final int mTotalPartialCount; private final Context mContext; @@ -257,7 +261,9 @@ public class CameraDeviceImpl extends CameraDevice }; public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor, - CameraCharacteristics characteristics, int appTargetSdkVersion, + CameraCharacteristics characteristics, + Map<String, CameraCharacteristics> physicalIdsToChars, + int appTargetSdkVersion, Context ctx) { if (cameraId == null || callback == null || executor == null || characteristics == null) { throw new IllegalArgumentException("Null argument given"); @@ -266,6 +272,7 @@ public class CameraDeviceImpl extends CameraDevice mDeviceCallback = callback; mDeviceExecutor = executor; mCharacteristics = characteristics; + mPhysicalIdsToChars = physicalIdsToChars; mAppTargetSdkVersion = appTargetSdkVersion; mContext = ctx; @@ -1357,11 +1364,71 @@ public class CameraDeviceImpl extends CameraDevice } } + private boolean checkInputConfigurationWithStreamConfigurationsAs( + InputConfiguration inputConfig, StreamConfigurationMap configMap) { + int[] inputFormats = configMap.getInputFormats(); + boolean validFormat = false; + int inputFormat = inputConfig.getFormat(); + for (int format : inputFormats) { + if (format == inputFormat) { + validFormat = true; + } + } + + if (validFormat == false) { + return false; + } + + boolean validSize = false; + Size[] inputSizes = configMap.getInputSizes(inputFormat); + for (Size s : inputSizes) { + if (inputConfig.getWidth() == s.getWidth() && + inputConfig.getHeight() == s.getHeight()) { + validSize = true; + } + } + + if (validSize == false) { + return false; + } + return true; + } + + private boolean checkInputConfigurationWithStreamConfigurations( + InputConfiguration inputConfig, boolean maxResolution) { + // Check if either this logical camera or any of its physical cameras support the + // input config. If they do, the input config is valid. + CameraCharacteristics.Key<StreamConfigurationMap> ck = + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP; + + if (maxResolution) { + ck = CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION; + } + + StreamConfigurationMap configMap = mCharacteristics.get(ck); + + if (configMap != null && + checkInputConfigurationWithStreamConfigurationsAs(inputConfig, configMap)) { + return true; + } + + for (Map.Entry<String, CameraCharacteristics> entry : mPhysicalIdsToChars.entrySet()) { + configMap = entry.getValue().get(ck); + + if (configMap != null && + checkInputConfigurationWithStreamConfigurationsAs(inputConfig, configMap)) { + // Input config supported. + return true; + } + } + return false; + } + private void checkInputConfiguration(InputConfiguration inputConfig) { if (inputConfig == null) { return; } - + int inputFormat = inputConfig.getFormat(); if (inputConfig.isMultiResolution()) { MultiResolutionStreamConfigurationMap configMap = mCharacteristics.get( CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP); @@ -1369,19 +1436,19 @@ public class CameraDeviceImpl extends CameraDevice int[] inputFormats = configMap.getInputFormats(); boolean validFormat = false; for (int format : inputFormats) { - if (format == inputConfig.getFormat()) { + if (format == inputFormat) { validFormat = true; } } if (validFormat == false) { throw new IllegalArgumentException("multi-resolution input format " + - inputConfig.getFormat() + " is not valid"); + inputFormat + " is not valid"); } boolean validSize = false; Collection<MultiResolutionStreamInfo> inputStreamInfo = - configMap.getInputInfo(inputConfig.getFormat()); + configMap.getInputInfo(inputFormat); for (MultiResolutionStreamInfo info : inputStreamInfo) { if (inputConfig.getWidth() == info.getWidth() && inputConfig.getHeight() == info.getHeight()) { @@ -1394,34 +1461,11 @@ public class CameraDeviceImpl extends CameraDevice inputConfig.getWidth() + "x" + inputConfig.getHeight() + " is not valid"); } } else { - StreamConfigurationMap configMap = mCharacteristics.get( - CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - - int[] inputFormats = configMap.getInputFormats(); - boolean validFormat = false; - for (int format : inputFormats) { - if (format == inputConfig.getFormat()) { - validFormat = true; - } - } - - if (validFormat == false) { - throw new IllegalArgumentException("input format " + inputConfig.getFormat() + - " is not valid"); - } - - boolean validSize = false; - Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat()); - for (Size s : inputSizes) { - if (inputConfig.getWidth() == s.getWidth() && - inputConfig.getHeight() == s.getHeight()) { - validSize = true; - } - } - - if (validSize == false) { - throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" + - inputConfig.getHeight() + " is not valid"); + if (!checkInputConfigurationWithStreamConfigurations(inputConfig, /*maxRes*/false) && + !checkInputConfigurationWithStreamConfigurations(inputConfig, /*maxRes*/true)) { + throw new IllegalArgumentException("Input config with format " + + inputFormat + " and size " + inputConfig.getWidth() + "x" + + inputConfig.getHeight() + " not supported by camera id " + mCameraId); } } } diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 0cdf744ecd68..aa84b024f69d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -327,6 +327,10 @@ public class CameraMetadataNative implements Parcelable { private static final String GPS_PROCESS = "GPS"; private static final int FACE_LANDMARK_SIZE = 6; + private static final int MANDATORY_STREAM_CONFIGURATIONS_DEFAULT = 0; + private static final int MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION = 1; + private static final int MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT = 2; + private static String translateLocationProviderToProcess(final String provider) { if (provider == null) { return null; @@ -644,6 +648,15 @@ public class CameraMetadataNative implements Parcelable { return (T) metadata.getStreamConfigurationMap(); } }); + sGetCommandMap.put( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getStreamConfigurationMapMaximumResolution(); + } + }); sGetCommandMap.put( CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getNativeKey(), new GetCommand() { @@ -664,6 +677,16 @@ public class CameraMetadataNative implements Parcelable { }); sGetCommandMap.put( + CameraCharacteristics.SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getMandatoryMaximumResolutionStreamCombinations(); + } + }); + + sGetCommandMap.put( CameraCharacteristics.CONTROL_MAX_REGIONS_AE.getNativeKey(), new GetCommand() { @Override @SuppressWarnings("unchecked") @@ -1285,12 +1308,12 @@ public class CameraMetadataNative implements Parcelable { return recommendedConfigurations; } - private boolean isBurstSupported() { + private boolean isCapabilitySupported(int capabilityRequested) { boolean ret = false; int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); for (int capability : capabilities) { - if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE) { + if (capabilityRequested == capability) { ret = true; break; } @@ -1299,8 +1322,18 @@ public class CameraMetadataNative implements Parcelable { return ret; } + private boolean isUltraHighResolutionSensor() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR); + + } + private boolean isBurstSupported() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE); + } + private MandatoryStreamCombination[] getMandatoryStreamCombinationsHelper( - boolean getConcurrent) { + int mandatoryStreamsType) { int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); ArrayList<Integer> caps = new ArrayList<Integer>(); caps.ensureCapacity(capabilities.length); @@ -1309,20 +1342,25 @@ public class CameraMetadataNative implements Parcelable { } int hwLevel = getBase(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); MandatoryStreamCombination.Builder build = new MandatoryStreamCombination.Builder( - mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap()); + mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap(), + getStreamConfigurationMapMaximumResolution()); List<MandatoryStreamCombination> combs = null; - if (getConcurrent) { - combs = build.getAvailableMandatoryConcurrentStreamCombinations(); - } else { - combs = build.getAvailableMandatoryStreamCombinations(); + switch (mandatoryStreamsType) { + case MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT: + combs = build.getAvailableMandatoryConcurrentStreamCombinations(); + break; + case MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION: + combs = build.getAvailableMandatoryMaximumResolutionStreamCombinations(); + break; + default: + combs = build.getAvailableMandatoryStreamCombinations(); } if ((combs != null) && (!combs.isEmpty())) { MandatoryStreamCombination[] combArray = new MandatoryStreamCombination[combs.size()]; combArray = combs.toArray(combArray); return combArray; } - return null; } @@ -1330,11 +1368,18 @@ public class CameraMetadataNative implements Parcelable { if (!mHasMandatoryConcurrentStreams) { return null; } - return getMandatoryStreamCombinationsHelper(true); + return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_CONCURRENT); + } + + private MandatoryStreamCombination[] getMandatoryMaximumResolutionStreamCombinations() { + if (!isUltraHighResolutionSensor()) { + return null; + } + return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_MAX_RESOLUTION); } private MandatoryStreamCombination[] getMandatoryStreamCombinations() { - return getMandatoryStreamCombinationsHelper(false); + return getMandatoryStreamCombinationsHelper(MANDATORY_STREAM_CONFIGURATIONS_DEFAULT); } private StreamConfigurationMap getStreamConfigurationMap() { @@ -1377,6 +1422,50 @@ public class CameraMetadataNative implements Parcelable { listHighResolution); } + private StreamConfigurationMap getStreamConfigurationMapMaximumResolution() { + if (!isUltraHighResolutionSensor()) { + return null; + } + StreamConfiguration[] configurations = getBase( + CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] minFrameDurations = getBase( + CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] stallDurations = getBase( + CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfiguration[] depthConfigurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] depthMinFrameDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] depthStallDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfiguration[] dynamicDepthConfigurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] dynamicDepthMinFrameDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] dynamicDepthStallDurations = getBase( + CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfiguration[] heicConfigurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] heicMinFrameDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION); + StreamConfigurationDuration[] heicStallDurations = getBase( + CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION); + HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase( + CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION); + ReprocessFormatsMap inputOutputFormatsMap = getBase( + CameraCharacteristics.SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP_MAXIMUM_RESOLUTION); + // TODO: Is this correct, burst capability shouldn't necessarily correspond to max res mode + boolean listHighResolution = isBurstSupported(); + return new StreamConfigurationMap( + configurations, minFrameDurations, stallDurations, + depthConfigurations, depthMinFrameDurations, depthStallDurations, + dynamicDepthConfigurations, dynamicDepthMinFrameDurations, + dynamicDepthStallDurations, heicConfigurations, + heicMinFrameDurations, heicStallDurations, + highSpeedVideoConfigurations, inputOutputFormatsMap, + listHighResolution, false); + } + private <T> Integer getMaxRegions(Key<T> key) { final int AE = 0; final int AWB = 1; diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 8a0172ee8018..34116aae8648 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -267,8 +267,8 @@ public final class MandatoryStreamCombination { mStreamsInformation.hashCode()); } - private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM, s720p, s1440p } - private static enum ReprocessType { NONE, PRIVATE, YUV } + private static enum SizeThreshold { VGA, PREVIEW, RECORD, MAXIMUM, s720p, s1440p, FULL_RES } + private static enum ReprocessType { NONE, PRIVATE, YUV, REMOSAIC } private static final class StreamTemplate { public int mFormat; public SizeThreshold mSizeThreshold; @@ -691,6 +691,18 @@ public final class MandatoryStreamCombination { "Depth capture for mesh based object rendering"), }; + private static StreamCombinationTemplate sUltraHighResolutionStreamCombinations[] = { + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.FULL_RES) }, + "Full res YUV image capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.FULL_RES) }, + "Full res RAW capture"), + new StreamCombinationTemplate(new StreamTemplate [] { + new StreamTemplate(ImageFormat.JPEG, SizeThreshold.FULL_RES) }, + "Full res JPEG still image capture"), + }; + /** * Helper builder class to generate a list of available mandatory stream combinations. * @hide @@ -700,6 +712,7 @@ public final class MandatoryStreamCombination { private List<Integer> mCapabilities; private int mHwLevel, mCameraId; private StreamConfigurationMap mStreamConfigMap; + private StreamConfigurationMap mStreamConfigMapMaximumResolution; private boolean mIsHiddenPhysicalCamera; private final Size kPreviewSizeBound = new Size(1920, 1088); @@ -712,13 +725,17 @@ public final class MandatoryStreamCombination { * @param displaySize The device display size. * @param capabilities The camera device capabilities. * @param sm The camera device stream configuration map. + * @param smMaxResolution The camera device stream configuration map when it runs in max + * resolution mode. */ public Builder(int cameraId, int hwLevel, @NonNull Size displaySize, - @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm) { + @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm, + StreamConfigurationMap smMaxResolution) { mCameraId = cameraId; mDisplaySize = displaySize; mCapabilities = capabilities; mStreamConfigMap = sm; + mStreamConfigMapMaximumResolution = smMaxResolution; mHwLevel = hwLevel; mIsHiddenPhysicalCamera = CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId)); @@ -797,6 +814,97 @@ public final class MandatoryStreamCombination { } /** + * Retrieve a list of all available mandatory stream combinations supported when + * {@link CaptureRequest#ANDROID_SENSOR_PIXEL_MODE} is set to + * {@link CameraMetadata#ANDROID_SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION}. + * + * @return a non-modifiable list of supported mandatory stream combinations or + * null in case device is not backward compatible or the method encounters + * an error. + */ + public @NonNull List<MandatoryStreamCombination> + getAvailableMandatoryMaximumResolutionStreamCombinations() { + + ArrayList<StreamCombinationTemplate> chosenStreamCombinations = + new ArrayList<StreamCombinationTemplate>(); + + chosenStreamCombinations.addAll(Arrays.asList(sUltraHighResolutionStreamCombinations)); + + ArrayList<MandatoryStreamCombination> availableStreamCombinations = + new ArrayList<MandatoryStreamCombination>(); + boolean addRemosaicReprocessing = isRemosaicReprocessingSupported(); + int remosaicSize = 0; + if (addRemosaicReprocessing) { + remosaicSize = 1; + } + availableStreamCombinations.ensureCapacity( + chosenStreamCombinations.size() + remosaicSize); + fillMandatoryOutputStreamCombinations(availableStreamCombinations, + chosenStreamCombinations, mStreamConfigMapMaximumResolution); + if (isRemosaicReprocessingSupported()) { + // Add reprocess mandatory streams + ArrayList<MandatoryStreamInformation> streamsInfo = + new ArrayList<MandatoryStreamInformation>(); + + ArrayList<Size> inputSize = new ArrayList<Size>(); + Size maxRawInputSize = getMaxSize(mStreamConfigMapMaximumResolution.getInputSizes( + ImageFormat.RAW_SENSOR)); + inputSize.add(maxRawInputSize); + + streamsInfo.add(new MandatoryStreamInformation(inputSize, + ImageFormat.RAW_SENSOR, /*isInput*/true)); + streamsInfo.add(new MandatoryStreamInformation(inputSize, + ImageFormat.RAW_SENSOR)); + MandatoryStreamCombination streamCombination; + streamCombination = new MandatoryStreamCombination(streamsInfo, + "Remosaic reprocessing", true); + availableStreamCombinations.add(streamCombination); + } + return Collections.unmodifiableList(availableStreamCombinations); + } + + private void fillMandatoryOutputStreamCombinations( + ArrayList<MandatoryStreamCombination> availableStreamCombinations, + ArrayList<StreamCombinationTemplate> chosenStreamCombinations, + StreamConfigurationMap streamConfigMap) { + + for (StreamCombinationTemplate combTemplate : chosenStreamCombinations) { + ArrayList<MandatoryStreamInformation> streamsInfo = + new ArrayList<MandatoryStreamInformation>(); + streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length); + + for (StreamTemplate template : combTemplate.mStreamTemplates) { + MandatoryStreamInformation streamInfo; + List<Size> sizes = new ArrayList<Size>(); + Size sizeChosen = + getMaxSize(streamConfigMap.getOutputSizes( + template.mFormat)); + sizes.add(sizeChosen); + try { + streamInfo = new MandatoryStreamInformation(sizes, template.mFormat); + } catch (IllegalArgumentException e) { + String cause = "No available sizes found for format: " + template.mFormat + + " size threshold: " + template.mSizeThreshold + " combination: " + + combTemplate.mDescription; + throw new RuntimeException(cause, e); + } + streamsInfo.add(streamInfo); + } + + MandatoryStreamCombination streamCombination; + try { + streamCombination = new MandatoryStreamCombination(streamsInfo, + combTemplate.mDescription, /*isReprocess*/false); + } catch (IllegalArgumentException e) { + String cause = "No stream information for mandatory combination: " + + combTemplate.mDescription; + throw new RuntimeException(cause, e); + } + availableStreamCombinations.add(streamCombination); + } + } + + /** * Retrieve a list of all available mandatory stream combinations. * * @return a non-modifiable list of supported mandatory stream combinations or @@ -948,7 +1056,6 @@ public final class MandatoryStreamCombination { inputSize.add(maxYUVInputSize); format = ImageFormat.YUV_420_888; } - streamsInfo.add(new MandatoryStreamInformation(inputSize, format, /*isInput*/true)); streamsInfo.add(new MandatoryStreamInformation(inputSize, format)); @@ -974,7 +1081,6 @@ public final class MandatoryStreamCombination { combTemplate.mDescription); return null; } - streamsInfo.add(streamInfo); } @@ -1220,6 +1326,14 @@ public final class MandatoryStreamCombination { } /** + * Check whether the current device supports YUV reprocessing. + */ + private boolean isRemosaicReprocessingSupported() { + return isCapabilitySupported( + CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING); + } + + /** * Return the maximum supported video size using the camcorder profile information. * * @return Maximum supported video size. diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index e31bd601fc03..84736dc73ad6 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -19,6 +19,7 @@ package android.hardware.camera2.params; import static com.android.internal.util.Preconditions.*; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -26,6 +27,7 @@ import android.graphics.ImageFormat; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.MultiResolutionImageReader; import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.hardware.camera2.utils.HashCodeHelpers; @@ -33,10 +35,13 @@ import android.hardware.camera2.utils.SurfaceUtils; import android.media.ImageReader; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArraySet; import android.util.Log; import android.util.Size; import android.view.Surface; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -145,6 +150,13 @@ public final class OutputConfiguration implements Parcelable { */ public static final int SURFACE_GROUP_ID_NONE = -1; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SENSOR_PIXEL_MODE_"}, value = + {CameraMetadata.SENSOR_PIXEL_MODE_DEFAULT, + CameraMetadata.SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION}) + public @interface SensorPixelMode {}; + /** * Create a new {@link OutputConfiguration} instance with a {@link Surface}. * @@ -306,6 +318,7 @@ public final class OutputConfiguration implements Parcelable { mIsShared = false; mPhysicalCameraId = null; mIsMultiResolution = false; + mSensorPixelModesUsed = new ArrayList<Integer>(); } /** @@ -399,6 +412,7 @@ public final class OutputConfiguration implements Parcelable { mIsShared = false; mPhysicalCameraId = null; mIsMultiResolution = false; + mSensorPixelModesUsed = new ArrayList<Integer>(); } /** @@ -485,6 +499,81 @@ public final class OutputConfiguration implements Parcelable { } /** + * Add a sensor pixel mode that this OutputConfiguration will be used in. + * + * <p> In the case that this output stream configuration (format, width, height) is + * available through {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP} + * configurations and + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION}, + * configurations, the camera sub-system will assume that this {@link OutputConfiguration} will + * be used only with {@link android.hardware.camera2.CaptureRequest}s which has + * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT}. + * In such cases, if clients intend to use the + * {@link OutputConfiguration}(s) in a {@link android.hardware.camera2.CaptureRequest} with + * other sensor pixel modes, they must specify which + * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE}(s) they will use this + * {@link OutputConfiguration} with, by calling this method. + * + * In case this output stream configuration (format, width, height) is only in + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION}, + * configurations, this output target must only be used with + * {@link android.hardware.camera2.CaptureRequest}s which has + * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION} and that + * is what the camera sub-system will assume. If clients add + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT} in this + * case, session configuration will fail, if this {@link OutputConfiguration} is included. + * + * In case this output stream configuration (format, width, height) is only in + * {@link android.hardware.camera2.CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP}, + * configurations, this output target must only be used with + * {@link android.hardware.camera2.CaptureRequest}s which has + * {@link android.hardware.camera2.CaptureRequest#SENSOR_PIXEL_MODE} set to + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT} and that is what + * the camera sub-system will assume. If clients add + * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION} in this + * case, session configuration will fail, if this {@link OutputConfiguration} is included. + * + * @param sensorPixelModeUsed The sensor pixel mode this OutputConfiguration will be used with + * </p> + * + */ + public void addSensorPixelModeUsed(@SensorPixelMode int sensorPixelModeUsed) { + // Verify that the values are in range. + if (sensorPixelModeUsed != CameraMetadata.SENSOR_PIXEL_MODE_DEFAULT && + sensorPixelModeUsed != CameraMetadata.SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION) { + throw new IllegalArgumentException("Not a valid sensor pixel mode " + + sensorPixelModeUsed); + } + + if (mSensorPixelModesUsed.contains(sensorPixelModeUsed)) { + // Already added, ignore; + return; + } + mSensorPixelModesUsed.add(sensorPixelModeUsed); + } + + /** + * Remove a sensor pixel mode, previously added through addSensorPixelModeUsed, from this + * OutputConfiguration. + * + * <p> Sensor pixel modes added via calls to {@link #addSensorPixelModeUsed} can also be removed + * from the OutputConfiguration.</p> + * + * @param sensorPixelModeUsed The sensor pixel mode to be removed. + * + * @throws IllegalArgumentException If the sensor pixel mode wasn't previously added + * through {@link #addSensorPixelModeUsed}. + */ + public void removeSensorPixelModeUsed(@SensorPixelMode int sensorPixelModeUsed) { + if (!mSensorPixelModesUsed.remove(Integer.valueOf(sensorPixelModeUsed))) { + throw new IllegalArgumentException("sensorPixelMode " + sensorPixelModeUsed + + "is not part of this output configuration"); + } + } + + /** * Check if this configuration is for a physical camera. * * <p>This returns true if the output configuration was for a physical camera making up a @@ -625,6 +714,7 @@ public final class OutputConfiguration implements Parcelable { this.mIsShared = other.mIsShared; this.mPhysicalCameraId = other.mPhysicalCameraId; this.mIsMultiResolution = other.mIsMultiResolution; + this.mSensorPixelModesUsed = other.mSensorPixelModesUsed; } /** @@ -642,7 +732,8 @@ public final class OutputConfiguration implements Parcelable { source.readTypedList(surfaces, Surface.CREATOR); String physicalCameraId = source.readString(); boolean isMultiResolutionOutput = source.readInt() == 1; - + ArrayList<Integer> sensorPixelModesUsed = new ArrayList<Integer>(); + source.readList(sensorPixelModesUsed, Integer.class.getClassLoader()); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); mSurfaceGroupId = surfaceSetId; @@ -666,6 +757,7 @@ public final class OutputConfiguration implements Parcelable { } mPhysicalCameraId = physicalCameraId; mIsMultiResolution = isMultiResolutionOutput; + mSensorPixelModesUsed = sensorPixelModesUsed; } /** @@ -766,6 +858,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeTypedList(mSurfaces); dest.writeString(mPhysicalCameraId); dest.writeInt(mIsMultiResolution ? 1 : 0); + dest.writeList(mSensorPixelModesUsed); } /** @@ -798,7 +891,14 @@ public final class OutputConfiguration implements Parcelable { !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) || mIsMultiResolution != other.mIsMultiResolution) return false; - + if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) { + return false; + } + for (int j = 0; j < mSensorPixelModesUsed.size(); j++) { + if (mSensorPixelModesUsed.get(j) != other.mSensorPixelModesUsed.get(j)) { + return false; + } + } int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size()); for (int i = 0; i < minLen; i++) { if (mSurfaces.get(i) != other.mSurfaces.get(i)) @@ -823,7 +923,7 @@ public final class OutputConfiguration implements Parcelable { mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), - mIsMultiResolution ? 1 : 0); + mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode()); } return HashCodeHelpers.hashCode( @@ -831,7 +931,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), - mIsMultiResolution ? 1 : 0); + mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode()); } private static final String TAG = "OutputConfiguration"; @@ -861,4 +961,6 @@ public final class OutputConfiguration implements Parcelable { // Flag indicating if this config is for a multi-resolution output with a // MultiResolutionImageReader private boolean mIsMultiResolution; + // The sensor pixel modes that this OutputConfiguration will use + private ArrayList<Integer> mSensorPixelModesUsed; } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 6dd67447c321..2430c09d93f4 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -19,6 +19,7 @@ package android.hardware.display; import static android.view.Display.DEFAULT_DISPLAY; import android.Manifest; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.NonNull; @@ -921,6 +922,43 @@ public final class DisplayManager { mGlobal.setTemporaryBrightness(displayId, brightness); } + + /** + * Sets the brightness of the specified display. + * <p> + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} + * permission. + * </p> + * + * @param displayId the logical display id + * @param brightness The brightness value from 0.0f to 1.0f. + * + * @hide + */ + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS) + public void setBrightness(int displayId, @FloatRange(from = 0f, to = 1f) float brightness) { + mGlobal.setBrightness(displayId, brightness); + } + + + /** + * Gets the brightness of the specified display. + * <p> + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} + * permission. + * </p> + * + * @param displayId The display of which brightness value to get from. + * + * @hide + */ + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS) + @FloatRange(from = 0f, to = 1f) + public float getBrightness(int displayId) { + return mGlobal.getBrightness(displayId); + } + + /** * Temporarily sets the auto brightness adjustment factor. * <p> diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index fd0431c5bc3f..06efc4fc263e 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -690,6 +690,37 @@ public final class DisplayManagerGlobal { } } + + /** + * Sets the brightness of the display. + * + * @param brightness The brightness value from 0.0f to 1.0f. + * + * @hide + */ + public void setBrightness(int displayId, float brightness) { + try { + mDm.setBrightness(displayId, brightness); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Gets the brightness of the display. + * + * @param displayId The display from which to get the brightness + * + * @hide + */ + public float getBrightness(int displayId) { + try { + return mDm.getBrightness(displayId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + /** * Temporarily sets the auto brightness adjustment factor. * <p> diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index dee91445c224..3538ff17a5a2 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -119,6 +119,12 @@ interface IDisplayManager { // Temporarily sets the display brightness. void setTemporaryBrightness(int displayId, float brightness); + // Saves the display brightness. + void setBrightness(int displayId, float brightness); + + // Retrieves the display brightness. + float getBrightness(int displayId); + // Temporarily sets the auto brightness adjustment factor. void setTemporaryAutoBrightnessAdjustment(float adjustment); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 01fd3964df1d..c83ccfa81f95 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -223,6 +223,14 @@ public final class InputManager { public static final long BLOCK_FLAG_SLIPPERY = android.os.IInputConstants.BLOCK_FLAG_SLIPPERY; /** + * Check whether apps are using MotionEvent.getRawX/Y. This is implementation-specific, and + * thus undefined for most 3p app usages. + * @hide + */ + @ChangeId + public static final long APP_USES_RAW_INPUT_COORDS = 179274888L; + + /** * Input Event Injection Synchronization Mode: None. * Never blocks. Injection is asynchronous and is assumed always to be successful. * @hide 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..23fc06927ef9 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,25 @@ 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. + */ + 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(); } @@ -183,6 +198,28 @@ public abstract class NetworkStatsProvider { 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. + */ + 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 b73fdbff8ef3..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) { @@ -439,7 +439,7 @@ public class VcnManager { * @param statusCode the code for the status change encountered by this {@link * VcnStatusCallback}'s subscription group. */ - public abstract void onVcnStatusChanged(@VcnStatusCode int statusCode); + public abstract void onStatusChanged(@VcnStatusCode int statusCode); /** * Invoked when a VCN Gateway Connection corresponding to this callback's subscription group @@ -476,7 +476,7 @@ public class VcnManager { * and there is a VCN active for its specified subscription group (this may happen after the * callback is registered). * - * <p>{@link VcnStatusCallback#onVcnStatusChanged(int)} will be invoked on registration with the + * <p>{@link VcnStatusCallback#onStatusChanged(int)} will be invoked on registration with the * current status for the specified subscription group's VCN. If the registrant is not * privileged for this subscription group, {@link #VCN_STATUS_CODE_NOT_CONFIGURED} will be * returned. @@ -580,7 +580,7 @@ public class VcnManager { @Override public void onVcnStatusChanged(@VcnStatusCode int statusCode) { Binder.withCleanCallingIdentity( - () -> mExecutor.execute(() -> mCallback.onVcnStatusChanged(statusCode))); + () -> mExecutor.execute(() -> mCallback.onStatusChanged(statusCode))); } // TODO(b/180521637): use ServiceSpecificException for safer Exception 'parceling' diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 4674aa2d120e..c47fc576ebe9 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1004,6 +1004,24 @@ public abstract class BatteryStats implements Parcelable { public abstract long getCpuMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's GNSS usage, derived from + * on device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getGnssMeasuredBatteryConsumptionUC(); + + /** + * Returns the battery consumption (in microcoulombs) of the uid's radio usage, derived from + * on device power measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getMobileRadioMeasuredBatteryConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) of the screen while on and uid active, * derived from on device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. @@ -2548,6 +2566,24 @@ public abstract class BatteryStats implements Parcelable { public abstract long getCpuMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the GNSS, derived from on device power + * measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getGnssMeasuredBatteryConsumptionUC(); + + /** + * Returns the battery consumption (in microcoulombs) of the radio, derived from on device power + * measurement data. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getMobileRadioMeasuredBatteryConsumptionUC(); + + /** * Returns the battery consumption (in microcoulombs) of the screen while on, derived from on * device power measurement data. * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index f12eb8853c55..a0721c32fc2a 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -34,7 +34,7 @@ import java.util.List; public final class BatteryUsageStats implements Parcelable { private final double mConsumedPower; private final int mDischargePercentage; - private final long mStatsStartRealtimeMs; + private final long mStatsStartTimestampMs; private final double mDischargedPowerLowerBound; private final double mDischargedPowerUpperBound; private final long mBatteryTimeRemainingMs; @@ -46,7 +46,7 @@ public final class BatteryUsageStats implements Parcelable { private final List<BatteryStats.HistoryTag> mHistoryTagPool; private BatteryUsageStats(@NonNull Builder builder) { - mStatsStartRealtimeMs = builder.mStatsStartRealtimeMs; + mStatsStartTimestampMs = builder.mStatsStartTimestampMs; mDischargePercentage = builder.mDischargePercentage; mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah; mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah; @@ -91,10 +91,11 @@ public final class BatteryUsageStats implements Parcelable { } /** - * Timestamp of the latest battery stats reset, in milliseconds. + * Timestamp (as returned by System.currentTimeMillis()) of the latest battery stats reset, in + * milliseconds. */ - public long getStatsStartRealtime() { - return mStatsStartRealtimeMs; + public long getStatsStartTimestamp() { + return mStatsStartTimestampMs; } /** @@ -174,7 +175,7 @@ public final class BatteryUsageStats implements Parcelable { } private BatteryUsageStats(@NonNull Parcel source) { - mStatsStartRealtimeMs = source.readLong(); + mStatsStartTimestampMs = source.readLong(); mConsumedPower = source.readDouble(); mDischargePercentage = source.readInt(); mDischargedPowerLowerBound = source.readDouble(); @@ -214,7 +215,7 @@ public final class BatteryUsageStats implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeLong(mStatsStartRealtimeMs); + dest.writeLong(mStatsStartTimestampMs); dest.writeDouble(mConsumedPower); dest.writeInt(mDischargePercentage); dest.writeDouble(mDischargedPowerLowerBound); @@ -260,7 +261,7 @@ public final class BatteryUsageStats implements Parcelable { public static final class Builder { private final int mCustomPowerComponentCount; private final int mCustomTimeComponentCount; - private long mStatsStartRealtimeMs; + private long mStatsStartTimestampMs; private int mDischargePercentage; private double mDischargedPowerLowerBoundMah; private double mDischargedPowerUpperBoundMah; @@ -291,8 +292,8 @@ public final class BatteryUsageStats implements Parcelable { /** * Sets the timestamp of the latest battery stats reset, in milliseconds. */ - public Builder setStatsStartRealtime(long statsStartRealtimeMs) { - mStatsStartRealtimeMs = statsStartRealtimeMs; + public Builder setStatsStartTimestamp(long statsStartTimestampMs) { + mStatsStartTimestampMs = statsStartTimestampMs; return this; } diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 9518bf197fa0..85861bc1d2aa 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -60,14 +60,18 @@ public final class BatteryUsageStatsQuery implements Parcelable { */ public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 2; + private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000; + private final int mFlags; @NonNull private final int[] mUserIds; + private final long mMaxStatsAgeMs; private BatteryUsageStatsQuery(@NonNull Builder builder) { mFlags = builder.mFlags; mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray() : new int[]{UserHandle.USER_ALL}; + mMaxStatsAgeMs = builder.mMaxStatsAgeMs; } @BatteryUsageStatsFlags @@ -94,10 +98,19 @@ public final class BatteryUsageStatsQuery implements Parcelable { return (mFlags & FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0; } + /** + * Returns the client's tolerance for stale battery stats. The data is allowed to be up to + * this many milliseconds out-of-date. + */ + public long getMaxStatsAge() { + return mMaxStatsAgeMs; + } + private BatteryUsageStatsQuery(Parcel in) { mFlags = in.readInt(); mUserIds = new int[in.readInt()]; in.readIntArray(mUserIds); + mMaxStatsAgeMs = in.readLong(); } @Override @@ -105,6 +118,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { dest.writeInt(mFlags); dest.writeInt(mUserIds.length); dest.writeIntArray(mUserIds); + dest.writeLong(mMaxStatsAgeMs); } @Override @@ -132,6 +146,7 @@ public final class BatteryUsageStatsQuery implements Parcelable { public static final class Builder { private int mFlags; private IntArray mUserIds; + private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS; /** * Builds a read-only BatteryUsageStatsQuery object. @@ -170,5 +185,14 @@ public final class BatteryUsageStatsQuery implements Parcelable { mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL; return this; } + + /** + * Set the client's tolerance for stale battery stats. The data may be up to + * this many milliseconds out-of-date. + */ + public Builder setMaxStatsAgeMs(long maxStatsAgeMs) { + mMaxStatsAgeMs = maxStatsAgeMs; + return this; + } } } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 087568defb27..34f2c103f866 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -99,6 +99,8 @@ interface IUserManager { boolean someUserHasSeedAccount(in String accountName, in String accountType); boolean isProfile(int userId); boolean isManagedProfile(int userId); + boolean isCloneProfile(int userId); + boolean sharesMediaWithParent(int userId); boolean isDemoUser(int userId); boolean isPreCreated(int userId); UserInfo createProfileForUserEvenWhenDisallowedWithThrow(in String name, in String userType, int flags, diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index b42a495ece56..219912c24e70 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -218,7 +218,7 @@ public class SystemVibrator extends Vibrator { @Override public boolean[] arePrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { boolean[] supported = new boolean[primitiveIds.length]; if (mVibratorManager == null) { Log.w(TAG, "Failed to check supported primitives; no vibrator manager."); diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index b528eb157e36..841aad556d6a 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -211,7 +211,7 @@ public class SystemVibratorManager extends VibratorManager { @Override public boolean[] arePrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { boolean[] supported = new boolean[primitiveIds.length]; for (int i = 0; i < primitiveIds.length; i++) { supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 5069e0319119..d4de4fa65526 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.StringDef; +import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -136,6 +137,16 @@ public class UserManager { public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; /** + * User type representing a clone profile. Clone profile is a user profile type used to run + * second instance of an otherwise single user App (eg, messengers). Only the primary user + * is allowed to have a clone profile. + * + * @hide + */ + @SystemApi + public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; + + /** * User type representing a generic profile for testing purposes. Only on debuggable builds. * @hide */ @@ -1984,6 +1995,14 @@ public class UserManager { } /** + * Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}. + * @hide + */ + public static boolean isUserTypeCloneProfile(String userType) { + return USER_TYPE_PROFILE_CLONE.equals(userType); + } + + /** * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the * user type. * @hide @@ -2233,6 +2252,31 @@ public class UserManager { } /** + * Checks if the context user is a clone profile. + * + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller + * must be in the same profile group of the user. + * + * @return whether the context user is a clone profile. + * + * @see android.os.UserManager#USER_TYPE_PROFILE_CLONE + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) + @UserHandleAware + @SuppressAutoDoc + public boolean isCloneProfile() { + try { + return mService.isCloneProfile(mUserId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Checks if the calling app is running as an ephemeral user. * * @return whether the caller is an ephemeral user. @@ -4064,6 +4108,31 @@ public class UserManager { } /** + * If the user is a {@link UserManager#isProfile profile}, checks if the user + * shares media with its parent user (the user that created this profile). + * Returns false for any other type of user. + * + * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the + * caller must be in the same profile group as the user. + * + * @return true if the user shares media with its parent user, false otherwise. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) + @UserHandleAware + @SuppressAutoDoc + public boolean sharesMediaWithParent() { + try { + return mService.sharesMediaWithParent(mUserId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Removes a user and all associated data. * @param userId the integer handle of the user. * @hide diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 217f1785bcd6..cec323f8b423 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -21,6 +21,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.media.AudioAttributes; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; import java.lang.annotation.Retention; @@ -330,9 +332,9 @@ public final class VibrationAttributes implements Parcelable { private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) { if (effect != null) { - if (mUsage == USAGE_UNKNOWN && effect instanceof VibrationEffect.Prebaked) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - switch (prebaked.getId()) { + PrebakedSegment prebaked = extractPrebakedSegment(effect); + if (mUsage == USAGE_UNKNOWN && prebaked != null) { + switch (prebaked.getEffectId()) { case VibrationEffect.EFFECT_CLICK: case VibrationEffect.EFFECT_DOUBLE_CLICK: case VibrationEffect.EFFECT_HEAVY_CLICK: @@ -355,6 +357,20 @@ public final class VibrationAttributes implements Parcelable { } } + @Nullable + private PrebakedSegment extractPrebakedSegment(VibrationEffect effect) { + if (effect instanceof VibrationEffect.Composed) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + if (composed.getSegments().size() == 1) { + VibrationEffectSegment segment = composed.getSegments().get(0); + if (segment instanceof PrebakedSegment) { + return (PrebakedSegment) segment; + } + } + } + return null; + } + private void setUsage(@NonNull AudioAttributes audio) { mOriginalAudioUsage = audio.getUsage(); switch (audio.getUsage()) { diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl index 89478fac2f1a..6311760a4bfc 100644 --- a/core/java/android/os/VibrationEffect.aidl +++ b/core/java/android/os/VibrationEffect.aidl @@ -17,4 +17,3 @@ package android.os; parcelable VibrationEffect; -parcelable VibrationEffect.Composition.PrimitiveEffect;
\ No newline at end of file diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 0199fad067cb..c78bf8c04c91 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; @@ -28,6 +29,11 @@ import android.content.Context; import android.hardware.vibrator.V1_0.EffectStrength; import android.hardware.vibrator.V1_3.Effect; import android.net.Uri; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.MathUtils; import com.android.internal.util.Preconditions; @@ -45,11 +51,6 @@ import java.util.Objects; * These effects may be any number of things, from single shot vibrations to complex waveforms. */ public abstract class VibrationEffect implements Parcelable { - private static final int PARCEL_TOKEN_ONE_SHOT = 1; - private static final int PARCEL_TOKEN_WAVEFORM = 2; - private static final int PARCEL_TOKEN_EFFECT = 3; - private static final int PARCEL_TOKEN_COMPOSITION = 4; - // Stevens' coefficient to scale the perceived vibration intensity. private static final float SCALE_GAMMA = 0.65f; @@ -181,9 +182,7 @@ public abstract class VibrationEffect implements Parcelable { * @return The desired effect. */ public static VibrationEffect createOneShot(long milliseconds, int amplitude) { - VibrationEffect effect = new OneShot(milliseconds, amplitude); - effect.validate(); - return effect; + return createWaveform(new long[]{milliseconds}, new int[]{amplitude}, -1 /* repeat */); } /** @@ -243,7 +242,19 @@ public abstract class VibrationEffect implements Parcelable { * @return The desired effect. */ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { - VibrationEffect effect = new Waveform(timings, amplitudes, repeat); + if (timings.length != amplitudes.length) { + throw new IllegalArgumentException( + "timing and amplitude arrays must be of equal length" + + " (timings.length=" + timings.length + + ", amplitudes.length=" + amplitudes.length + ")"); + } + List<StepSegment> segments = new ArrayList<>(); + for (int i = 0; i < timings.length; i++) { + float parsedAmplitude = amplitudes[i] == DEFAULT_AMPLITUDE + ? DEFAULT_AMPLITUDE : (float) amplitudes[i] / MAX_AMPLITUDE; + segments.add(new StepSegment(parsedAmplitude, /* frequency= */ 0, (int) timings[i])); + } + VibrationEffect effect = new Composed(segments, repeat); effect.validate(); return effect; } @@ -317,7 +328,8 @@ public abstract class VibrationEffect implements Parcelable { */ @TestApi public static VibrationEffect get(int effectId, boolean fallback) { - VibrationEffect effect = new Prebaked(effectId, fallback, EffectStrength.MEDIUM); + VibrationEffect effect = new Composed( + new PrebakedSegment(effectId, fallback, EffectStrength.MEDIUM)); effect.validate(); return effect; } @@ -379,8 +391,26 @@ public abstract class VibrationEffect implements Parcelable { * @see VibrationEffect.Composition */ @NonNull - public static VibrationEffect.Composition startComposition() { - return new VibrationEffect.Composition(); + public static Composition startComposition() { + return new Composition(); + } + + /** + * Start building a waveform vibration. + * + * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing + * control over vibration frequency and ramping up or down the vibration amplitude, frequency or + * both. + * + * <p>For simpler waveform patterns see {@link #createWaveform} methods. + * + * @hide + * @see VibrationEffect.WaveformBuilder + */ + @TestApi + @NonNull + public static WaveformBuilder startWaveform() { + return new WaveformBuilder(); } @Override @@ -428,32 +458,28 @@ public abstract class VibrationEffect implements Parcelable { public abstract <T extends VibrationEffect> T scale(float scaleFactor); /** - * Scale given vibration intensity by the given factor. - * - * @param amplitude amplitude of the effect, must be between 0 and MAX_AMPLITUDE - * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will - * scale down the intensity, values larger than 1 will scale up + * Applies given effect strength to prebaked effects represented by one of + * VibrationEffect.EFFECT_*. * + * @param effectStrength new effect strength to be applied, one of + * VibrationEffect.EFFECT_STRENGTH_*. + * @return this if there is no change to this effect, or a copy of this effect with applied + * effect strength otherwise. * @hide */ - protected static int scale(int amplitude, float scaleFactor) { - if (amplitude == 0) { - return 0; - } - int scaled = (int) (scale((float) amplitude / MAX_AMPLITUDE, scaleFactor) * MAX_AMPLITUDE); - return MathUtils.constrain(scaled, 1, MAX_AMPLITUDE); + public <T extends VibrationEffect> T applyEffectStrength(int effectStrength) { + return (T) this; } /** * Scale given vibration intensity by the given factor. * - * @param intensity relative intensity of the effect, must be between 0 and 1 + * @param intensity relative intensity of the effect, must be between 0 and 1 * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will * scale down the intensity, values larger than 1 will scale up - * * @hide */ - protected static float scale(float intensity, float scaleFactor) { + public static float scale(float intensity, float scaleFactor) { // Applying gamma correction to the scale factor, which is the same as encoding the input // value, scaling it, then decoding the scaled value. float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA); @@ -516,545 +542,152 @@ public abstract class VibrationEffect implements Parcelable { } } - /** @hide */ + /** + * Implementation of {@link VibrationEffect} described by a composition of one or more + * {@link VibrationEffectSegment}, with an optional index to represent repeating effects. + * + * @hide + */ @TestApi - public static class OneShot extends VibrationEffect implements Parcelable { - private final long mDuration; - private final int mAmplitude; - - public OneShot(Parcel in) { - mDuration = in.readLong(); - mAmplitude = in.readInt(); - } + public static final class Composed extends VibrationEffect { + private final ArrayList<VibrationEffectSegment> mSegments; + private final int mRepeatIndex; - public OneShot(long milliseconds, int amplitude) { - mDuration = milliseconds; - mAmplitude = amplitude; + Composed(@NonNull Parcel in) { + this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt()); } - @Override - public long getDuration() { - return mDuration; - } - - public int getAmplitude() { - return mAmplitude; - } - - /** @hide */ - @Override - public OneShot scale(float scaleFactor) { - if (scaleFactor == 1f || mAmplitude == DEFAULT_AMPLITUDE) { - // Just return this if there's no scaling to be done or if amplitude is not yet set. - return this; - } - return new OneShot(mDuration, scale(mAmplitude, scaleFactor)); + Composed(@NonNull VibrationEffectSegment segment) { + this(Arrays.asList(segment), /* repeatIndex= */ -1); } /** @hide */ - @Override - public OneShot resolve(int defaultAmplitude) { - if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude <= 0) { - throw new IllegalArgumentException( - "amplitude must be between 1 and 255 inclusive (amplitude=" - + defaultAmplitude + ")"); - } - if (mAmplitude == DEFAULT_AMPLITUDE) { - return new OneShot(mDuration, defaultAmplitude); - } - return this; + public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) { + super(); + mSegments = new ArrayList<>(segments); + mRepeatIndex = repeatIndex; } - /** @hide */ - @Override - public void validate() { - if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) { - throw new IllegalArgumentException( - "amplitude must either be DEFAULT_AMPLITUDE, " - + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")"); - } - if (mDuration <= 0) { - throw new IllegalArgumentException( - "duration must be positive (duration=" + mDuration + ")"); - } - } - - @Override - public boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.OneShot)) { - return false; - } - VibrationEffect.OneShot other = (VibrationEffect.OneShot) o; - return other.mDuration == mDuration && other.mAmplitude == mAmplitude; - } - - @Override - public int hashCode() { - int result = 17; - result += 37 * (int) mDuration; - result += 37 * mAmplitude; - return result; - } - - @Override - public String toString() { - return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}"; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_ONE_SHOT); - out.writeLong(mDuration); - out.writeInt(mAmplitude); - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final @android.annotation.NonNull Parcelable.Creator<OneShot> CREATOR = - new Parcelable.Creator<OneShot>() { - @Override - public OneShot createFromParcel(Parcel in) { - // Skip the type token - in.readInt(); - return new OneShot(in); - } - @Override - public OneShot[] newArray(int size) { - return new OneShot[size]; - } - }; - } - - /** @hide */ - @TestApi - public static class Waveform extends VibrationEffect implements Parcelable { - private final long[] mTimings; - private final int[] mAmplitudes; - private final int mRepeat; - - public Waveform(Parcel in) { - this(in.createLongArray(), in.createIntArray(), in.readInt()); - } - - public Waveform(long[] timings, int[] amplitudes, int repeat) { - mTimings = new long[timings.length]; - System.arraycopy(timings, 0, mTimings, 0, timings.length); - mAmplitudes = new int[amplitudes.length]; - System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length); - mRepeat = repeat; - } - - public long[] getTimings() { - return mTimings; - } - - public int[] getAmplitudes() { - return mAmplitudes; + @NonNull + public List<VibrationEffectSegment> getSegments() { + return mSegments; } public int getRepeatIndex() { - return mRepeat; + return mRepeatIndex; } @Override - public long getDuration() { - if (mRepeat >= 0) { - return Long.MAX_VALUE; - } - long duration = 0; - for (long d : mTimings) { - duration += d; - } - return duration; - } - - /** @hide */ - @Override - public Waveform scale(float scaleFactor) { - if (scaleFactor == 1f) { - // Just return this if there's no scaling to be done. - return this; - } - boolean scaled = false; - int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); - for (int i = 0; i < scaledAmplitudes.length; i++) { - if (scaledAmplitudes[i] == DEFAULT_AMPLITUDE) { - // Skip amplitudes that are not set. - continue; - } - scaled = true; - scaledAmplitudes[i] = scale(scaledAmplitudes[i], scaleFactor); - } - if (!scaled) { - // Just return this if no scaling was done. - return this; - } - return new Waveform(mTimings, scaledAmplitudes, mRepeat); - } - - /** @hide */ - @Override - public Waveform resolve(int defaultAmplitude) { - if (defaultAmplitude > MAX_AMPLITUDE || defaultAmplitude < 0) { - throw new IllegalArgumentException( - "Amplitude is negative or greater than MAX_AMPLITUDE"); - } - boolean resolved = false; - int[] resolvedAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length); - for (int i = 0; i < resolvedAmplitudes.length; i++) { - if (resolvedAmplitudes[i] == DEFAULT_AMPLITUDE) { - resolvedAmplitudes[i] = defaultAmplitude; - resolved = true; - } - } - if (!resolved) { - return this; - } - return new Waveform(mTimings, resolvedAmplitudes, mRepeat); - } - - /** @hide */ - @Override public void validate() { - if (mTimings.length != mAmplitudes.length) { - throw new IllegalArgumentException( - "timing and amplitude arrays must be of equal length" - + " (timings.length=" + mTimings.length - + ", amplitudes.length=" + mAmplitudes.length + ")"); + int segmentCount = mSegments.size(); + boolean hasNonZeroDuration = false; + boolean hasNonZeroAmplitude = false; + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = mSegments.get(i); + segment.validate(); + // A segment with unknown duration = -1 still counts as a non-zero duration. + hasNonZeroDuration |= segment.getDuration() != 0; + hasNonZeroAmplitude |= segment.hasNonZeroAmplitude(); } - if (!hasNonZeroEntry(mTimings)) { + if (!hasNonZeroDuration) { throw new IllegalArgumentException("at least one timing must be non-zero" - + " (timings=" + Arrays.toString(mTimings) + ")"); - } - for (long timing : mTimings) { - if (timing < 0) { - throw new IllegalArgumentException("timings must all be >= 0" - + " (timings=" + Arrays.toString(mTimings) + ")"); - } + + " (segments=" + mSegments + ")"); } - for (int amplitude : mAmplitudes) { - if (amplitude < -1 || amplitude > 255) { - throw new IllegalArgumentException( - "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" - + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")"); - } + if (!hasNonZeroAmplitude) { + throw new IllegalArgumentException("at least one amplitude must be non-zero" + + " (segments=" + mSegments + ")"); } - if (mRepeat < -1 || mRepeat >= mTimings.length) { - throw new IllegalArgumentException( - "repeat index must be within the bounds of the timings array" - + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")"); + if (mRepeatIndex != -1) { + Preconditions.checkArgumentInRange(mRepeatIndex, 0, segmentCount - 1, + "repeat index must be within the bounds of the segments (segments.length=" + + segmentCount + ", index=" + mRepeatIndex + ")"); } } @Override - public boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.Waveform)) { - return false; + public long getDuration() { + if (mRepeatIndex >= 0) { + return Long.MAX_VALUE; } - VibrationEffect.Waveform other = (VibrationEffect.Waveform) o; - return Arrays.equals(mTimings, other.mTimings) - && Arrays.equals(mAmplitudes, other.mAmplitudes) - && mRepeat == other.mRepeat; - } - - @Override - public int hashCode() { - int result = 17; - result += 37 * Arrays.hashCode(mTimings); - result += 37 * Arrays.hashCode(mAmplitudes); - result += 37 * mRepeat; - return result; - } - - @Override - public String toString() { - return "Waveform{mTimings=" + Arrays.toString(mTimings) - + ", mAmplitudes=" + Arrays.toString(mAmplitudes) - + ", mRepeat=" + mRepeat - + "}"; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_WAVEFORM); - out.writeLongArray(mTimings); - out.writeIntArray(mAmplitudes); - out.writeInt(mRepeat); - } - - private static boolean hasNonZeroEntry(long[] vals) { - for (long val : vals) { - if (val != 0) { - return true; + int segmentCount = mSegments.size(); + long totalDuration = 0; + for (int i = 0; i < segmentCount; i++) { + long segmentDuration = mSegments.get(i).getDuration(); + if (segmentDuration < 0) { + return segmentDuration; } + totalDuration += segmentDuration; } - return false; - } - - - public static final @android.annotation.NonNull Parcelable.Creator<Waveform> CREATOR = - new Parcelable.Creator<Waveform>() { - @Override - public Waveform createFromParcel(Parcel in) { - // Skip the type token - in.readInt(); - return new Waveform(in); - } - @Override - public Waveform[] newArray(int size) { - return new Waveform[size]; - } - }; - } - - /** @hide */ - @TestApi - public static class Prebaked extends VibrationEffect implements Parcelable { - private final int mEffectId; - private final boolean mFallback; - private final int mEffectStrength; - @Nullable - private final VibrationEffect mFallbackEffect; - - public Prebaked(Parcel in) { - mEffectId = in.readInt(); - mFallback = in.readByte() != 0; - mEffectStrength = in.readInt(); - mFallbackEffect = in.readParcelable(VibrationEffect.class.getClassLoader()); - } - - public Prebaked(int effectId, boolean fallback, int effectStrength) { - mEffectId = effectId; - mFallback = fallback; - mEffectStrength = effectStrength; - mFallbackEffect = null; - } - - /** @hide */ - public Prebaked(int effectId, int effectStrength, @NonNull VibrationEffect fallbackEffect) { - mEffectId = effectId; - mFallback = true; - mEffectStrength = effectStrength; - mFallbackEffect = fallbackEffect; - } - - public int getId() { - return mEffectId; - } - - /** - * Whether the effect should fall back to a generic pattern if there's no hardware specific - * implementation of it. - */ - public boolean shouldFallback() { - return mFallback; - } - - @Override - public long getDuration() { - return -1; + return totalDuration; } - /** @hide */ + @NonNull @Override - public Prebaked resolve(int defaultAmplitude) { - if (mFallbackEffect != null) { - VibrationEffect resolvedFallback = mFallbackEffect.resolve(defaultAmplitude); - if (!mFallbackEffect.equals(resolvedFallback)) { - return new Prebaked(mEffectId, mEffectStrength, resolvedFallback); - } + public Composed resolve(int defaultAmplitude) { + int segmentCount = mSegments.size(); + ArrayList<VibrationEffectSegment> resolvedSegments = new ArrayList<>(segmentCount); + for (int i = 0; i < segmentCount; i++) { + resolvedSegments.add(mSegments.get(i).resolve(defaultAmplitude)); } - return this; + if (resolvedSegments.equals(mSegments)) { + return this; + } + Composed resolved = new Composed(resolvedSegments, mRepeatIndex); + resolved.validate(); + return resolved; } - /** @hide */ + @NonNull @Override - public Prebaked scale(float scaleFactor) { - if (mFallbackEffect != null) { - VibrationEffect scaledFallback = mFallbackEffect.scale(scaleFactor); - if (!mFallbackEffect.equals(scaledFallback)) { - return new Prebaked(mEffectId, mEffectStrength, scaledFallback); - } + public Composed scale(float scaleFactor) { + int segmentCount = mSegments.size(); + ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount); + for (int i = 0; i < segmentCount; i++) { + scaledSegments.add(mSegments.get(i).scale(scaleFactor)); } - // Prebaked effect strength cannot be scaled with this method. - return this; - } - - /** - * Set the effect strength. - */ - public int getEffectStrength() { - return mEffectStrength; - } - - /** - * Return the fallback effect, if set. - * - * @hide - */ - @Nullable - public VibrationEffect getFallbackEffect() { - return mFallbackEffect; - } - - private static boolean isValidEffectStrength(int strength) { - switch (strength) { - case EffectStrength.LIGHT: - case EffectStrength.MEDIUM: - case EffectStrength.STRONG: - return true; - default: - return false; + if (scaledSegments.equals(mSegments)) { + return this; } + Composed scaled = new Composed(scaledSegments, mRepeatIndex); + scaled.validate(); + return scaled; } - /** @hide */ + @NonNull @Override - public void validate() { - switch (mEffectId) { - case EFFECT_CLICK: - case EFFECT_DOUBLE_CLICK: - case EFFECT_TICK: - case EFFECT_TEXTURE_TICK: - case EFFECT_THUD: - case EFFECT_POP: - case EFFECT_HEAVY_CLICK: - break; - default: - if (mEffectId < RINGTONES[0] || mEffectId > RINGTONES[RINGTONES.length - 1]) { - throw new IllegalArgumentException( - "Unknown prebaked effect type (value=" + mEffectId + ")"); - } + public Composed applyEffectStrength(int effectStrength) { + int segmentCount = mSegments.size(); + ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount); + for (int i = 0; i < segmentCount; i++) { + scaledSegments.add(mSegments.get(i).applyEffectStrength(effectStrength)); } - if (!isValidEffectStrength(mEffectStrength)) { - throw new IllegalArgumentException( - "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); + if (scaledSegments.equals(mSegments)) { + return this; } + Composed scaled = new Composed(scaledSegments, mRepeatIndex); + scaled.validate(); + return scaled; } @Override public boolean equals(@Nullable Object o) { - if (!(o instanceof VibrationEffect.Prebaked)) { + if (!(o instanceof Composed)) { return false; } - VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o; - return mEffectId == other.mEffectId - && mFallback == other.mFallback - && mEffectStrength == other.mEffectStrength - && Objects.equals(mFallbackEffect, other.mFallbackEffect); + Composed other = (Composed) o; + return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex; } @Override public int hashCode() { - return Objects.hash(mEffectId, mFallback, mEffectStrength, mFallbackEffect); + return Objects.hash(mSegments, mRepeatIndex); } @Override public String toString() { - return "Prebaked{mEffectId=" + effectIdToString(mEffectId) - + ", mEffectStrength=" + effectStrengthToString(mEffectStrength) - + ", mFallback=" + mFallback - + ", mFallbackEffect=" + mFallbackEffect - + "}"; - } - - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_EFFECT); - out.writeInt(mEffectId); - out.writeByte((byte) (mFallback ? 1 : 0)); - out.writeInt(mEffectStrength); - out.writeParcelable(mFallbackEffect, flags); - } - - public static final @NonNull Parcelable.Creator<Prebaked> CREATOR = - new Parcelable.Creator<Prebaked>() { - @Override - public Prebaked createFromParcel(Parcel in) { - // Skip the type token - in.readInt(); - return new Prebaked(in); - } - @Override - public Prebaked[] newArray(int size) { - return new Prebaked[size]; - } - }; - } - - /** @hide */ - public static final class Composed extends VibrationEffect implements Parcelable { - private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects; - - /** - * @hide - */ - @SuppressWarnings("unchecked") - public Composed(@NonNull Parcel in) { - this(in.readArrayList(Composed.class.getClassLoader())); - } - - /** - * @hide - */ - public Composed(List<Composition.PrimitiveEffect> effects) { - mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects)); - } - - /** - * @hide - */ - @NonNull - public List<Composition.PrimitiveEffect> getPrimitiveEffects() { - return mPrimitiveEffects; - } - - @Override - public long getDuration() { - return -1; - } - - /** @hide */ - @Override - public VibrationEffect resolve(int defaultAmplitude) { - // Primitive effects already have default primitive intensity set, so ignore this. - return this; - } - - /** @hide */ - @Override - public Composed scale(float scaleFactor) { - if (scaleFactor == 1f) { - // Just return this if there's no scaling to be done. - return this; - } - final int primitiveCount = mPrimitiveEffects.size(); - List<Composition.PrimitiveEffect> scaledPrimitives = new ArrayList<>(); - for (int i = 0; i < primitiveCount; i++) { - Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i); - scaledPrimitives.add(new Composition.PrimitiveEffect( - primitive.id, scale(primitive.scale, scaleFactor), primitive.delay)); - } - return new Composed(scaledPrimitives); - } - - /** @hide */ - @Override - public void validate() { - final int primitiveCount = mPrimitiveEffects.size(); - for (int i = 0; i < primitiveCount; i++) { - Composition.PrimitiveEffect primitive = mPrimitiveEffects.get(i); - Composition.checkPrimitive(primitive.id); - Preconditions.checkArgumentInRange(primitive.scale, 0.0f, 1.0f, "scale"); - Preconditions.checkArgumentNonNegative(primitive.delay, - "Primitive delay must be zero or positive"); - } - } - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(PARCEL_TOKEN_COMPOSITION); - out.writeList(mPrimitiveEffects); + return "Composed{segments=" + mSegments + + ", repeat=" + mRepeatIndex + + "}"; } @Override @@ -1063,34 +696,20 @@ public abstract class VibrationEffect implements Parcelable { } @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Composed composed = (Composed) o; - return mPrimitiveEffects.equals(composed.mPrimitiveEffects); - } - - @Override - public int hashCode() { - return Objects.hash(mPrimitiveEffects); - } - - @Override - public String toString() { - return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}'; + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeList(mSegments); + out.writeInt(mRepeatIndex); } - public static final @NonNull Parcelable.Creator<Composed> CREATOR = - new Parcelable.Creator<Composed>() { + @NonNull + public static final Creator<Composed> CREATOR = + new Creator<Composed>() { @Override - public Composed createFromParcel(@NonNull Parcel in) { - // Skip the type token - in.readInt(); + public Composed createFromParcel(Parcel in) { return new Composed(in); } @Override - @NonNull public Composed[] newArray(int size) { return new Composed[size]; } @@ -1115,7 +734,7 @@ public abstract class VibrationEffect implements Parcelable { PRIMITIVE_LOW_TICK, }) @Retention(RetentionPolicy.SOURCE) - public @interface Primitive {} + public @interface PrimitiveType {} /** * No haptic effect. Used to generate extended delays between primitives. @@ -1166,9 +785,46 @@ public abstract class VibrationEffect implements Parcelable { public static final int PRIMITIVE_LOW_TICK = 8; - private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>(); + private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); + private int mRepeatIndex = -1; + + Composition() {} - Composition() { } + /** + * Add a haptic effect to the end of the current composition. + * + * <p>Similar to {@link #addEffect(VibrationEffect, int)} , but with no delay applied. + * + * @param effect The effect to add to this composition as a primitive + * @return The {@link Composition} object to enable adding multiple primitives in one chain. + * @hide + */ + @TestApi + @NonNull + public Composition addEffect(@NonNull VibrationEffect effect) { + return addEffect(effect, /* delay= */ 0); + } + + /** + * Add a haptic effect to the end of the current composition. + * + * @param effect The effect to add to this composition as a primitive + * @param delay The amount of time in milliseconds to wait before playing this primitive + * @return The {@link Composition} object to enable adding multiple primitives in one chain. + * @hide + */ + @TestApi + @NonNull + public Composition addEffect(@NonNull VibrationEffect effect, + @IntRange(from = 0) int delay) { + Preconditions.checkArgumentNonnegative(delay); + if (delay > 0) { + // Created a segment sustaining the zero amplitude to represent the delay. + addSegment(new StepSegment(/* amplitude= */ 0, /* frequency= */ 0, + /* duration= */ delay)); + } + return addSegments(effect); + } /** * Add a haptic primitive to the end of the current composition. @@ -1181,9 +837,8 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId) { - addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); - return this; + public Composition addPrimitive(@PrimitiveType int primitiveId) { + return addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); } /** @@ -1197,10 +852,9 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId, + public Composition addPrimitive(@PrimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale) { - addPrimitive(primitiveId, scale, /*delay*/ 0); - return this; + return addPrimitive(primitiveId, scale, /*delay*/ 0); } /** @@ -1213,9 +867,36 @@ public abstract class VibrationEffect implements Parcelable { * @return The {@link Composition} object to enable adding multiple primitives in one chain. */ @NonNull - public Composition addPrimitive(@Primitive int primitiveId, + public Composition addPrimitive(@PrimitiveType int primitiveId, @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { - mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay)); + PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, + delay); + primitive.validate(); + return addSegment(primitive); + } + + private Composition addSegment(VibrationEffectSegment segment) { + if (mRepeatIndex >= 0) { + throw new IllegalStateException( + "Composition already have a repeating effect so any new primitive would be" + + " unreachable."); + } + mSegments.add(segment); + return this; + } + + private Composition addSegments(VibrationEffect effect) { + if (mRepeatIndex >= 0) { + throw new IllegalStateException( + "Composition already have a repeating effect so any new primitive would be" + + " unreachable."); + } + Composed composed = (Composed) effect; + if (composed.getRepeatIndex() >= 0) { + // Start repeating from the index relative to the composed waveform. + mRepeatIndex = mSegments.size() + composed.getRepeatIndex(); + } + mSegments.addAll(composed.getSegments()); return this; } @@ -1230,22 +911,13 @@ public abstract class VibrationEffect implements Parcelable { */ @NonNull public VibrationEffect compose() { - if (mEffects.isEmpty()) { + if (mSegments.isEmpty()) { throw new IllegalStateException( "Composition must have at least one element to compose."); } - return new VibrationEffect.Composed(mEffects); - } - - /** - * @throws IllegalArgumentException throws if the primitive ID is not within the valid range - * @hide - * - */ - static int checkPrimitive(int primitiveId) { - Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LOW_TICK, - "primitiveId"); - return primitiveId; + VibrationEffect effect = new Composed(mSegments, mRepeatIndex); + effect.validate(); + return effect; } /** @@ -1254,7 +926,7 @@ public abstract class VibrationEffect implements Parcelable { * @return The ID in a human readable format. * @hide */ - public static String primitiveToString(@Primitive int id) { + public static String primitiveToString(@PrimitiveType int id) { switch (id) { case PRIMITIVE_NOOP: return "PRIMITIVE_NOOP"; @@ -1278,90 +950,172 @@ public abstract class VibrationEffect implements Parcelable { return Integer.toString(id); } } + } + + /** + * A builder for waveform haptic effects. + * + * <p>Waveform vibrations constitute of one or more timed segments where the vibration + * amplitude, frequency or both can linearly ramp to new values. + * + * <p>Waveform segments may have zero duration, which represent a jump to new vibration + * amplitude and/or frequency values. + * + * <p>Waveform segments may have the same start and end vibration amplitude and frequency, + * which represent a step where the amplitude and frequency are maintained for that duration. + * + * @hide + * @see VibrationEffect#startWaveform() + */ + @TestApi + public static final class WaveformBuilder { + private ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>(); + WaveformBuilder() {} /** - * @hide + * Vibrate with given amplitude for the given duration, in millis, keeping the previous + * frequency the same. + * + * <p>If the duration is zero the vibrator will jump to new amplitude. + * + * @param amplitude The amplitude for this step + * @param duration The duration of this step in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. */ - public static class PrimitiveEffect implements Parcelable { - public int id; - public float scale; - public int delay; - - PrimitiveEffect(int id, float scale, int delay) { - this.id = id; - this.scale = scale; - this.delay = delay; - } + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, + @IntRange(from = 0) int duration) { + return addStep(amplitude, getPreviousFrequency(), duration); + } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(id); - dest.writeFloat(scale); - dest.writeInt(delay); - } + /** + * Vibrate with given amplitude for the given duration, in millis, keeping the previous + * vibration frequency the same. + * + * <p>If the duration is zero the vibrator will jump to new amplitude. + * + * @param amplitude The amplitude for this step + * @param frequency The frequency for this step + * @param duration The duration of this step in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude, + @FloatRange(from = -1f, to = 1f) float frequency, + @IntRange(from = 0) int duration) { + mSegments.add(new StepSegment(amplitude, frequency, duration)); + return this; + } - @Override - public int describeContents() { - return 0; - } + /** + * Ramp vibration linearly for the given duration, in millis, from previous amplitude value + * to the given one, keeping previous frequency. + * + * <p>If the duration is zero the vibrator will jump to new amplitude. + * + * @param amplitude The final amplitude this ramp should reach + * @param duration The duration of this ramp in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, + @IntRange(from = 0) int duration) { + return addRamp(amplitude, getPreviousFrequency(), duration); + } - @Override - public String toString() { - return "PrimitiveEffect{" - + "id=" + primitiveToString(id) - + ", scale=" + scale - + ", delay=" + delay - + '}'; - } + /** + * Ramp vibration linearly for the given duration, in millis, from previous amplitude and + * frequency values to the given ones. + * + * <p>If the duration is zero the vibrator will jump to new amplitude and frequency. + * + * @param amplitude The final amplitude this ramp should reach + * @param frequency The final frequency this ramp should reach + * @param duration The duration of this ramp in milliseconds + * @return The {@link WaveformBuilder} object to enable adding multiple steps in chain. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public WaveformBuilder addRamp(@FloatRange(from = 0f, to = 1f) float amplitude, + @FloatRange(from = -1f, to = 1f) float frequency, + @IntRange(from = 0) int duration) { + mSegments.add(new RampSegment(getPreviousAmplitude(), amplitude, getPreviousFrequency(), + frequency, duration)); + return this; + } - @Override - public boolean equals(@Nullable Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PrimitiveEffect that = (PrimitiveEffect) o; - return id == that.id - && Float.compare(that.scale, scale) == 0 - && delay == that.delay; - } + /** + * Compose all of the steps together into a single {@link VibrationEffect}. + * + * The {@link WaveformBuilder} object is still valid after this call, so you can + * continue adding more primitives to it and generating more {@link VibrationEffect}s by + * calling this method again. + * + * @return The {@link VibrationEffect} resulting from the composition of the steps. + */ + @NonNull + public VibrationEffect build() { + return build(/* repeat= */ -1); + } - @Override - public int hashCode() { - return Objects.hash(id, scale, delay); + /** + * Compose all of the steps together into a single {@link VibrationEffect}. + * + * <p>To cause the pattern to repeat, pass the index at which to start the repetition + * (starting at 0), or -1 to disable repeating. + * + * <p>The {@link WaveformBuilder} object is still valid after this call, so you can + * continue adding more primitives to it and generating more {@link VibrationEffect}s by + * calling this method again. + * + * @return The {@link VibrationEffect} resulting from the composition of the steps. + */ + @NonNull + public VibrationEffect build(int repeat) { + if (mSegments.isEmpty()) { + throw new IllegalStateException( + "WaveformBuilder must have at least one element to build."); } + VibrationEffect effect = new Composed(mSegments, repeat); + effect.validate(); + return effect; + } + + private float getPreviousFrequency() { + if (!mSegments.isEmpty()) { + VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); + if (segment instanceof StepSegment) { + return ((StepSegment) segment).getFrequency(); + } else if (segment instanceof RampSegment) { + return ((RampSegment) segment).getEndFrequency(); + } + } + return 0; + } - - public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR = - new Parcelable.Creator<PrimitiveEffect>() { - @Override - public PrimitiveEffect createFromParcel(Parcel in) { - return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt()); - } - @Override - public PrimitiveEffect[] newArray(int size) { - return new PrimitiveEffect[size]; - } - }; + private float getPreviousAmplitude() { + if (!mSegments.isEmpty()) { + VibrationEffectSegment segment = mSegments.get(mSegments.size() - 1); + if (segment instanceof StepSegment) { + return ((StepSegment) segment).getAmplitude(); + } else if (segment instanceof RampSegment) { + return ((RampSegment) segment).getEndAmplitude(); + } + } + return 0; } } - public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR = + @NonNull + public static final Parcelable.Creator<VibrationEffect> CREATOR = new Parcelable.Creator<VibrationEffect>() { @Override public VibrationEffect createFromParcel(Parcel in) { - int token = in.readInt(); - if (token == PARCEL_TOKEN_ONE_SHOT) { - return new OneShot(in); - } else if (token == PARCEL_TOKEN_WAVEFORM) { - return new Waveform(in); - } else if (token == PARCEL_TOKEN_EFFECT) { - return new Prebaked(in); - } else if (token == PARCEL_TOKEN_COMPOSITION) { - return new Composed(in); - } else { - throw new IllegalStateException( - "Unexpected vibration event type token in parcel."); - } + return new Composed(in); } @Override public VibrationEffect[] newArray(int size) { diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index b90d438ffb93..a0f70c8fa526 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -467,7 +467,7 @@ public abstract class Vibrator { */ @NonNull public boolean[] arePrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { return new boolean[primitiveIds.length]; } @@ -478,7 +478,7 @@ public abstract class Vibrator { * @return Whether primitives effects are supported. */ public final boolean areAllPrimitivesSupported( - @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { for (boolean supported : arePrimitivesSupported(primitiveIds)) { if (!supported) { return false; diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 3121b952281e..64e51e71962a 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -16,9 +16,13 @@ package android.os; +import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.vibrator.IVibrator; +import android.util.Log; +import android.util.MathUtils; +import android.util.Range; import android.util.SparseBooleanArray; import java.util.ArrayList; @@ -42,27 +46,27 @@ public final class VibratorInfo implements Parcelable { private final SparseBooleanArray mSupportedEffects; @Nullable private final SparseBooleanArray mSupportedPrimitives; - private final float mResonantFrequency; private final float mQFactor; + private final FrequencyMapping mFrequencyMapping; VibratorInfo(Parcel in) { mId = in.readInt(); mCapabilities = in.readLong(); mSupportedEffects = in.readSparseBooleanArray(); mSupportedPrimitives = in.readSparseBooleanArray(); - mResonantFrequency = in.readFloat(); mQFactor = in.readFloat(); + mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader()); } /** @hide */ public VibratorInfo(int id, long capabilities, int[] supportedEffects, - int[] supportedPrimitives, float resonantFrequency, float qFactor) { + int[] supportedPrimitives, float qFactor, @NonNull FrequencyMapping frequencyMapping) { mId = id; mCapabilities = capabilities; mSupportedEffects = toSparseBooleanArray(supportedEffects); mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives); - mResonantFrequency = resonantFrequency; mQFactor = qFactor; + mFrequencyMapping = frequencyMapping; } @Override @@ -71,8 +75,8 @@ public final class VibratorInfo implements Parcelable { dest.writeLong(mCapabilities); dest.writeSparseBooleanArray(mSupportedEffects); dest.writeSparseBooleanArray(mSupportedPrimitives); - dest.writeFloat(mResonantFrequency); dest.writeFloat(mQFactor); + dest.writeParcelable(mFrequencyMapping, flags); } @Override @@ -92,14 +96,14 @@ public final class VibratorInfo implements Parcelable { return mId == that.mId && mCapabilities == that.mCapabilities && Objects.equals(mSupportedEffects, that.mSupportedEffects) && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives) - && Objects.equals(mResonantFrequency, that.mResonantFrequency) - && Objects.equals(mQFactor, that.mQFactor); + && Objects.equals(mQFactor, that.mQFactor) + && Objects.equals(mFrequencyMapping, that.mFrequencyMapping); } @Override public int hashCode() { return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives, - mResonantFrequency, mQFactor); + mQFactor, mFrequencyMapping); } @Override @@ -110,8 +114,8 @@ public final class VibratorInfo implements Parcelable { + ", mCapabilities flags=" + Long.toBinaryString(mCapabilities) + ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames()) + ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames()) - + ", mResonantFrequency=" + mResonantFrequency + ", mQFactor=" + mQFactor + + ", mFrequencyMapping=" + mFrequencyMapping + '}'; } @@ -153,7 +157,8 @@ public final class VibratorInfo implements Parcelable { * @param primitiveId Which primitives to query for. * @return Whether the primitive is supported. */ - public boolean isPrimitiveSupported(@VibrationEffect.Composition.Primitive int primitiveId) { + public boolean isPrimitiveSupported( + @VibrationEffect.Composition.PrimitiveType int primitiveId) { return hasCapability(IVibrator.CAP_COMPOSE_EFFECTS) && mSupportedPrimitives != null && mSupportedPrimitives.get(primitiveId, false); } @@ -176,7 +181,7 @@ public final class VibratorInfo implements Parcelable { * this vibrator is a composite of multiple physical devices. */ public float getResonantFrequency() { - return mResonantFrequency; + return mFrequencyMapping.mResonantFrequencyHz; } /** @@ -189,6 +194,52 @@ public final class VibratorInfo implements Parcelable { return mQFactor; } + /** + * Return a range of relative frequency values supported by the vibrator. + * + * @return A range of relative frequency values supported. The range will always contain the + * value 0, representing the device resonant frequency. Devices without frequency control will + * return the range [0,0]. Devices with frequency control will always return a range containing + * the safe range [-1, 1]. + * @hide + */ + public Range<Float> getFrequencyRange() { + return mFrequencyMapping.mRelativeFrequencyRange; + } + + /** + * Return the maximum amplitude the vibrator can play at given relative frequency. + * + * @return a value in [0,1] representing the maximum amplitude the device can play at given + * relative frequency. Devices without frequency control will return 1 for the input zero + * (resonant frequency), and 0 to any other input. Devices with frequency control will return + * the supported value, for input in {@code #getFrequencyRange()}, and 0 for any other input. + * @hide + */ + @FloatRange(from = 0, to = 1) + public float getMaxAmplitude(float relativeFrequency) { + if (mFrequencyMapping.isEmpty()) { + // The vibrator has not provided values for frequency mapping. + // Return the expected behavior for devices without frequency control. + return Float.compare(relativeFrequency, 0) == 0 ? 1 : 0; + } + return mFrequencyMapping.getMaxAmplitude(relativeFrequency); + } + + /** + * Return absolute frequency value for this vibrator, in hertz, that corresponds to given + * relative frequency. + * + * @retur a value in hertz that corresponds to given relative frequency. Input values outside + * {@link #getFrequencyRange()} will return {@link Float#NaN}. Devices without frequency control + * will return {@link Float#NaN} for any input. + * @hide + */ + @FloatRange(from = 0) + public float getAbsoluteFrequency(float relativeFrequency) { + return mFrequencyMapping.toHertz(relativeFrequency); + } + private String[] getCapabilitiesNames() { List<String> names = new ArrayList<>(); if (hasCapability(IVibrator.CAP_ON_CALLBACK)) { @@ -249,6 +300,209 @@ public final class VibratorInfo implements Parcelable { return array; } + /** + * Describes how frequency should be mapped to absolute values for a specific {@link Vibrator}. + * + * <p>This mapping is defined by the following parameters: + * + * <ol> + * <li>{@code minFrequency}, {@code resonantFrequency} and {@code frequencyResolution}, in + * hertz, provided by the vibrator. + * <li>{@code maxAmplitudes} a list of values in [0,1] provided by the vibrator, where + * {@code maxAmplitudes[i]} represents max supported amplitude at frequency + * {@code minFrequency + frequencyResolution * i}. + * <li>{@code maxFrequency = minFrequency + frequencyResolution * (maxAmplitudes.length-1)} + * <li>{@code suggestedSafeRangeHz} is the suggested frequency range in hertz that should be + * mapped to relative values -1 and 1, where 0 maps to {@code resonantFrequency}. + * </ol> + * + * <p>The mapping is defined linearly by the following points: + * + * <ol> + * <li>{@code toHertz(relativeMinFrequency} = minFrequency + * <li>{@code toHertz(-1) = resonantFrequency - safeRange / 2} + * <li>{@code toHertz(0) = resonantFrequency} + * <li>{@code toHertz(1) = resonantFrequency + safeRange / 2} + * <li>{@code toHertz(relativeMaxFrequency) = maxFrequency} + * </ol> + * + * @hide + */ + public static final class FrequencyMapping implements Parcelable { + private final float mMinFrequencyHz; + private final float mResonantFrequencyHz; + private final float mFrequencyResolutionHz; + private final float mSuggestedSafeRangeHz; + private final float[] mMaxAmplitudes; + + // Relative fields calculated from input values: + private final Range<Float> mRelativeFrequencyRange; + + FrequencyMapping(Parcel in) { + this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), + in.createFloatArray()); + } + + /** @hide */ + public FrequencyMapping(float minFrequencyHz, float resonantFrequencyHz, + float frequencyResolutionHz, float suggestedSafeRangeHz, float[] maxAmplitudes) { + mMinFrequencyHz = minFrequencyHz; + mResonantFrequencyHz = resonantFrequencyHz; + mFrequencyResolutionHz = frequencyResolutionHz; + mSuggestedSafeRangeHz = suggestedSafeRangeHz; + mMaxAmplitudes = new float[maxAmplitudes == null ? 0 : maxAmplitudes.length]; + if (maxAmplitudes != null) { + System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length); + } + + float maxFrequencyHz = + minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1); + if (Float.isNaN(resonantFrequencyHz) || Float.isNaN(minFrequencyHz) + || Float.isNaN(frequencyResolutionHz) || Float.isNaN(suggestedSafeRangeHz) + || resonantFrequencyHz < minFrequencyHz + || resonantFrequencyHz > maxFrequencyHz) { + // Some required fields are undefined or have bad values. + // Leave this mapping empty. + mRelativeFrequencyRange = Range.create(0f, 0f); + return; + } + + // Calculate actual safe range, limiting the suggested one by the device supported range + float safeDelta = MathUtils.min( + suggestedSafeRangeHz / 2, + resonantFrequencyHz - minFrequencyHz, + maxFrequencyHz - resonantFrequencyHz); + mRelativeFrequencyRange = Range.create( + (minFrequencyHz - resonantFrequencyHz) / safeDelta, + (maxFrequencyHz - resonantFrequencyHz) / safeDelta); + } + + /** + * Returns true if this frequency mapping is empty, i.e. the only supported relative + * frequency is 0 (resonant frequency). + */ + public boolean isEmpty() { + return Float.compare(mRelativeFrequencyRange.getLower(), + mRelativeFrequencyRange.getUpper()) == 0; + } + + /** + * Returns the frequency value in hertz that is mapped to the given relative frequency. + * + * @return The mapped frequency, in hertz, or {@link Float#NaN} is value outside the device + * supported range. + */ + public float toHertz(float relativeFrequency) { + if (!mRelativeFrequencyRange.contains(relativeFrequency)) { + return Float.NaN; + } + float relativeMinFrequency = mRelativeFrequencyRange.getLower(); + if (Float.compare(relativeMinFrequency, 0) == 0) { + // relative supported range is [0,0], so toHertz(0) should be the resonant frequency + return mResonantFrequencyHz; + } + float shift = (mMinFrequencyHz - mResonantFrequencyHz) / relativeMinFrequency; + return mResonantFrequencyHz + relativeFrequency * shift; + } + + /** + * Returns the maximum amplitude the vibrator can reach while playing at given relative + * frequency. + * + * @return A value in [0,1] representing the max amplitude supported at given relative + * frequency. This will return 0 if frequency is outside supported range, or if max + * amplitude mapping is empty. + */ + public float getMaxAmplitude(float relativeFrequency) { + float frequencyHz = toHertz(relativeFrequency); + if (Float.isNaN(frequencyHz)) { + // Unsupported frequency requested, vibrator cannot play at this frequency. + return 0; + } + float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz; + int floorIndex = (int) Math.floor(position); + int ceilIndex = (int) Math.ceil(position); + if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) { + if (mMaxAmplitudes.length > 0) { + // This should never happen if the setup of relative frequencies was correct. + Log.w(TAG, "Max amplitudes has " + mMaxAmplitudes.length + + " entries and was expected to cover the frequency " + frequencyHz + + " Hz when starting at min frequency of " + mMinFrequencyHz + + " Hz with resolution of " + mFrequencyResolutionHz + " Hz."); + } + return 0; + } + if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) { + // Value in between two mapped frequency values, use the lowest supported one. + return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]); + } + return mMaxAmplitudes[floorIndex]; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeFloat(mMinFrequencyHz); + dest.writeFloat(mResonantFrequencyHz); + dest.writeFloat(mFrequencyResolutionHz); + dest.writeFloat(mSuggestedSafeRangeHz); + dest.writeFloatArray(mMaxAmplitudes); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FrequencyMapping)) { + return false; + } + FrequencyMapping that = (FrequencyMapping) o; + return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0 + && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0 + && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0 + && Float.compare(mSuggestedSafeRangeHz, that.mSuggestedSafeRangeHz) == 0 + && Arrays.equals(mMaxAmplitudes, that.mMaxAmplitudes); + } + + @Override + public int hashCode() { + return Objects.hash(mMinFrequencyHz, mFrequencyResolutionHz, mFrequencyResolutionHz, + mSuggestedSafeRangeHz, mMaxAmplitudes); + } + + @Override + public String toString() { + return "FrequencyMapping{" + + "mMinFrequency=" + mMinFrequencyHz + + ", mResonantFrequency=" + mResonantFrequencyHz + + ", mMaxFrequency=" + + (mMinFrequencyHz + mFrequencyResolutionHz * (mMaxAmplitudes.length - 1)) + + ", mFrequencyResolution=" + mFrequencyResolutionHz + + ", mSuggestedSafeRange=" + mSuggestedSafeRangeHz + + ", mMaxAmplitudes count=" + mMaxAmplitudes.length + + '}'; + } + + @NonNull + public static final Creator<FrequencyMapping> CREATOR = + new Creator<FrequencyMapping>() { + @Override + public FrequencyMapping createFromParcel(Parcel in) { + return new FrequencyMapping(in); + } + + @Override + public FrequencyMapping[] newArray(int size) { + return new FrequencyMapping[size]; + } + }; + } + @NonNull public static final Creator<VibratorInfo> CREATOR = new Creator<VibratorInfo>() { diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 98b4e0b4f402..9385402c3d72 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -201,4 +201,6 @@ interface IStorageManager { void notifyAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 91; 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 c967deb5e810..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. @@ -2803,6 +2871,30 @@ public class StorageManager { } } + /** + * Check if {@code uid} with {@code tid} is blocked on IO for {@code reason}. + * + * This requires {@link ExternalStorageService} the + * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission. + * + * @param volumeUuid the UUID of the storage volume to check IO blocked status + * @param uid the UID of the app to check IO blocked status + * @param tid the tid of the app to check IO blocked status + * @param reason the reason to check IO blocked status for + * + * @hide + */ + @TestApi + public boolean isAppIoBlocked(@NonNull UUID volumeUuid, int uid, int tid, + @AppIoBlockedReason int reason) { + Objects.requireNonNull(volumeUuid); + try { + return mStorageManager.isAppIoBlocked(convert(volumeUuid), uid, tid, reason); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final Object mFuseAppLoopLock = new Object(); @GuardedBy("mFuseAppLoopLock") diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index b5abe2a3e311..36177c46ebef 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -16,6 +16,8 @@ package android.os.storage; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -289,9 +291,14 @@ public final class StorageVolume implements Parcelable { return mMaxFileSize; } - /** {@hide} */ + /** + * Returns the user that owns this volume + * + * {@hide} + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public UserHandle getOwner() { + @SystemApi(client = MODULE_LIBRARIES) + public @NonNull UserHandle getOwner() { return mOwner; } diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java new file mode 100644 index 000000000000..78b43468d663 --- /dev/null +++ b/core/java/android/os/vibrator/PrebakedSegment.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that plays a prebaked vibration effect. + * + * @hide + */ +@TestApi +public final class PrebakedSegment extends VibrationEffectSegment { + private final int mEffectId; + private final boolean mFallback; + private final int mEffectStrength; + + PrebakedSegment(@NonNull Parcel in) { + mEffectId = in.readInt(); + mFallback = in.readByte() != 0; + mEffectStrength = in.readInt(); + } + + /** @hide */ + public PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength) { + mEffectId = effectId; + mFallback = shouldFallback; + mEffectStrength = effectStrength; + } + + public int getEffectId() { + return mEffectId; + } + + public int getEffectStrength() { + return mEffectStrength; + } + + /** Return true if a fallback effect should be played if this effect is not supported. */ + public boolean shouldFallback() { + return mFallback; + } + + @Override + public long getDuration() { + return -1; + } + + @Override + public boolean hasNonZeroAmplitude() { + return true; + } + + @NonNull + @Override + public PrebakedSegment resolve(int defaultAmplitude) { + return this; + } + + @NonNull + @Override + public PrebakedSegment scale(float scaleFactor) { + // Prebaked effect strength cannot be scaled with this method. + return this; + } + + @NonNull + @Override + public PrebakedSegment applyEffectStrength(int effectStrength) { + if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) { + return new PrebakedSegment(mEffectId, mFallback, effectStrength); + } + return this; + } + + private static boolean isValidEffectStrength(int strength) { + switch (strength) { + case VibrationEffect.EFFECT_STRENGTH_LIGHT: + case VibrationEffect.EFFECT_STRENGTH_MEDIUM: + case VibrationEffect.EFFECT_STRENGTH_STRONG: + return true; + default: + return false; + } + } + + @Override + public void validate() { + switch (mEffectId) { + case VibrationEffect.EFFECT_CLICK: + case VibrationEffect.EFFECT_DOUBLE_CLICK: + case VibrationEffect.EFFECT_TICK: + case VibrationEffect.EFFECT_TEXTURE_TICK: + case VibrationEffect.EFFECT_THUD: + case VibrationEffect.EFFECT_POP: + case VibrationEffect.EFFECT_HEAVY_CLICK: + break; + default: + int[] ringtones = VibrationEffect.RINGTONES; + if (mEffectId < ringtones[0] || mEffectId > ringtones[ringtones.length - 1]) { + throw new IllegalArgumentException( + "Unknown prebaked effect type (value=" + mEffectId + ")"); + } + } + if (!isValidEffectStrength(mEffectStrength)) { + throw new IllegalArgumentException( + "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); + } + } + + @Override + public boolean equals(@Nullable Object o) { + if (!(o instanceof PrebakedSegment)) { + return false; + } + PrebakedSegment other = (PrebakedSegment) o; + return mEffectId == other.mEffectId + && mFallback == other.mFallback + && mEffectStrength == other.mEffectStrength; + } + + @Override + public int hashCode() { + return Objects.hash(mEffectId, mFallback, mEffectStrength); + } + + @Override + public String toString() { + return "Prebaked{effect=" + VibrationEffect.effectIdToString(mEffectId) + + ", strength=" + VibrationEffect.effectStrengthToString(mEffectStrength) + + ", fallback=" + mFallback + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_PREBAKED); + out.writeInt(mEffectId); + out.writeByte((byte) (mFallback ? 1 : 0)); + out.writeInt(mEffectStrength); + } + + @NonNull + public static final Parcelable.Creator<PrebakedSegment> CREATOR = + new Parcelable.Creator<PrebakedSegment>() { + @Override + public PrebakedSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new PrebakedSegment(in); + } + + @Override + public PrebakedSegment[] newArray(int size) { + return new PrebakedSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java new file mode 100644 index 000000000000..2ef29cb26ebc --- /dev/null +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that plays a primitive vibration effect after a + * specified delay and applying a given scale. + * + * @hide + */ +@TestApi +public final class PrimitiveSegment extends VibrationEffectSegment { + private final int mPrimitiveId; + private final float mScale; + private final int mDelay; + + PrimitiveSegment(@NonNull Parcel in) { + this(in.readInt(), in.readFloat(), in.readInt()); + } + + /** @hide */ + public PrimitiveSegment(int id, float scale, int delay) { + mPrimitiveId = id; + mScale = scale; + mDelay = delay; + } + + public int getPrimitiveId() { + return mPrimitiveId; + } + + public float getScale() { + return mScale; + } + + public int getDelay() { + return mDelay; + } + + @Override + public long getDuration() { + return -1; + } + + @Override + public boolean hasNonZeroAmplitude() { + // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0. + return true; + } + + @NonNull + @Override + public PrimitiveSegment resolve(int defaultAmplitude) { + return this; + } + + @NonNull + @Override + public PrimitiveSegment scale(float scaleFactor) { + return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor), + mDelay); + } + + @NonNull + @Override + public PrimitiveSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public void validate() { + Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP, + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId"); + Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale"); + Preconditions.checkArgumentNonnegative(mDelay, "primitive delay should be >= 0"); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(PARCEL_TOKEN_PRIMITIVE); + dest.writeInt(mPrimitiveId); + dest.writeFloat(mScale); + dest.writeInt(mDelay); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "Primitive{" + + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId) + + ", scale=" + mScale + + ", delay=" + mDelay + + '}'; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PrimitiveSegment that = (PrimitiveSegment) o; + return mPrimitiveId == that.mPrimitiveId + && Float.compare(that.mScale, mScale) == 0 + && mDelay == that.mDelay; + } + + @Override + public int hashCode() { + return Objects.hash(mPrimitiveId, mScale, mDelay); + } + + @NonNull + public static final Parcelable.Creator<PrimitiveSegment> CREATOR = + new Parcelable.Creator<PrimitiveSegment>() { + @Override + public PrimitiveSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new PrimitiveSegment(in); + } + + @Override + public PrimitiveSegment[] newArray(int size) { + return new PrimitiveSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java new file mode 100644 index 000000000000..aad87c5cd4e7 --- /dev/null +++ b/core/java/android/os/vibrator/RampSegment.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that ramps vibration amplitude and/or frequency + * for a specified duration. + * + * @hide + */ +@TestApi +public final class RampSegment extends VibrationEffectSegment { + private final float mStartAmplitude; + private final float mStartFrequency; + private final float mEndAmplitude; + private final float mEndFrequency; + private final int mDuration; + + RampSegment(@NonNull Parcel in) { + this(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readInt()); + } + + /** @hide */ + public RampSegment(float startAmplitude, float endAmplitude, float startFrequency, + float endFrequency, int duration) { + mStartAmplitude = startAmplitude; + mEndAmplitude = endAmplitude; + mStartFrequency = startFrequency; + mEndFrequency = endFrequency; + mDuration = duration; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RampSegment)) { + return false; + } + RampSegment other = (RampSegment) o; + return Float.compare(mStartAmplitude, other.mStartAmplitude) == 0 + && Float.compare(mEndAmplitude, other.mEndAmplitude) == 0 + && Float.compare(mStartFrequency, other.mStartFrequency) == 0 + && Float.compare(mEndFrequency, other.mEndFrequency) == 0 + && mDuration == other.mDuration; + } + + public float getStartAmplitude() { + return mStartAmplitude; + } + + public float getEndAmplitude() { + return mEndAmplitude; + } + + public float getStartFrequency() { + return mStartFrequency; + } + + public float getEndFrequency() { + return mEndFrequency; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public boolean hasNonZeroAmplitude() { + return mStartAmplitude > 0 || mEndAmplitude > 0; + } + + @Override + public void validate() { + Preconditions.checkArgumentNonnegative(mDuration, + "Durations must all be >= 0, got " + mDuration); + Preconditions.checkArgumentInRange(mStartAmplitude, 0f, 1f, "startAmplitude"); + Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude"); + } + + + @NonNull + @Override + public RampSegment resolve(int defaultAmplitude) { + // Default amplitude is not supported for ramping. + return this; + } + + @NonNull + @Override + public RampSegment scale(float scaleFactor) { + float newStartAmplitude = VibrationEffect.scale(mStartAmplitude, scaleFactor); + float newEndAmplitude = VibrationEffect.scale(mEndAmplitude, scaleFactor); + if (Float.compare(mStartAmplitude, newStartAmplitude) == 0 + && Float.compare(mEndAmplitude, newEndAmplitude) == 0) { + return this; + } + return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequency, mEndFrequency, + mDuration); + } + + @NonNull + @Override + public RampSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(mStartAmplitude, mEndAmplitude, mStartFrequency, mEndFrequency, + mDuration); + } + + @Override + public String toString() { + return "Ramp{startAmplitude=" + mStartAmplitude + + ", endAmplitude=" + mEndAmplitude + + ", startFrequency=" + mStartFrequency + + ", endFrequency=" + mEndFrequency + + ", duration=" + mDuration + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_RAMP); + out.writeFloat(mStartAmplitude); + out.writeFloat(mEndAmplitude); + out.writeFloat(mStartFrequency); + out.writeFloat(mEndFrequency); + out.writeInt(mDuration); + } + + @NonNull + public static final Creator<RampSegment> CREATOR = + new Creator<RampSegment>() { + @Override + public RampSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new RampSegment(in); + } + + @Override + public RampSegment[] newArray(int size) { + return new RampSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java new file mode 100644 index 000000000000..11209e0ee425 --- /dev/null +++ b/core/java/android/os/vibrator/StepSegment.java @@ -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 android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Representation of {@link VibrationEffectSegment} that holds a fixed vibration amplitude and + * frequency for a specified duration. + * + * @hide + */ +@TestApi +public final class StepSegment extends VibrationEffectSegment { + private final float mAmplitude; + private final float mFrequency; + private final int mDuration; + + StepSegment(@NonNull Parcel in) { + this(in.readFloat(), in.readFloat(), in.readInt()); + } + + /** @hide */ + public StepSegment(float amplitude, float frequency, int duration) { + mAmplitude = amplitude; + mFrequency = frequency; + mDuration = duration; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof StepSegment)) { + return false; + } + StepSegment other = (StepSegment) o; + return Float.compare(mAmplitude, other.mAmplitude) == 0 + && Float.compare(mFrequency, other.mFrequency) == 0 + && mDuration == other.mDuration; + } + + public float getAmplitude() { + return mAmplitude; + } + + public float getFrequency() { + return mFrequency; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public boolean hasNonZeroAmplitude() { + // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later. + return Float.compare(mAmplitude, 0) != 0; + } + + @Override + public void validate() { + Preconditions.checkArgumentNonnegative(mDuration, + "Durations must all be >= 0, got " + mDuration); + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { + Preconditions.checkArgumentInRange(mAmplitude, 0f, 1f, "amplitude"); + } + } + + @NonNull + @Override + public StepSegment resolve(int defaultAmplitude) { + if (defaultAmplitude > VibrationEffect.MAX_AMPLITUDE || defaultAmplitude <= 0) { + throw new IllegalArgumentException( + "amplitude must be between 1 and 255 inclusive (amplitude=" + + defaultAmplitude + ")"); + } + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) != 0) { + return this; + } + return new StepSegment((float) defaultAmplitude / VibrationEffect.MAX_AMPLITUDE, mFrequency, + mDuration); + } + + @NonNull + @Override + public StepSegment scale(float scaleFactor) { + if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { + return this; + } + return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequency, + mDuration); + } + + @NonNull + @Override + public StepSegment applyEffectStrength(int effectStrength) { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(mAmplitude, mFrequency, mDuration); + } + + @Override + public String toString() { + return "Step{amplitude=" + mAmplitude + + ", frequency=" + mFrequency + + ", duration=" + mDuration + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_STEP); + out.writeFloat(mAmplitude); + out.writeFloat(mFrequency); + out.writeInt(mDuration); + } + + @NonNull + public static final Parcelable.Creator<StepSegment> CREATOR = + new Parcelable.Creator<StepSegment>() { + @Override + public StepSegment createFromParcel(Parcel in) { + // Skip the type token + in.readInt(); + return new StepSegment(in); + } + + @Override + public StepSegment[] newArray(int size) { + return new StepSegment[size]; + } + }; +} diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java new file mode 100644 index 000000000000..5b42845cfa43 --- /dev/null +++ b/core/java/android/os/vibrator/VibrationEffectSegment.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.VibrationEffect; + +/** + * Representation of a single segment of a {@link VibrationEffect}. + * + * <p>Vibration effects are represented as a sequence of segments that describes how vibration + * amplitude and frequency changes over time. Segments can be described as one of the following: + * + * <ol> + * <li>A predefined vibration effect; + * <li>A composable effect primitive; + * <li>Fixed amplitude and frequency values to be held for a specified duration; + * <li>Pairs of amplitude and frequency values to be ramped to for a specified duration; + * </ol> + * + * @hide + */ +@TestApi +@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here. +public abstract class VibrationEffectSegment implements Parcelable { + static final int PARCEL_TOKEN_PREBAKED = 1; + static final int PARCEL_TOKEN_PRIMITIVE = 2; + static final int PARCEL_TOKEN_STEP = 3; + static final int PARCEL_TOKEN_RAMP = 4; + + /** Prevent subclassing from outside of this package */ + VibrationEffectSegment() { + } + + /** + * Gets the estimated duration of the segment in milliseconds. + * + * <p>For segments with an unknown duration (e.g. prebaked or primitive effects where the length + * is device and potentially run-time dependent), this returns -1. + */ + public abstract long getDuration(); + + /** Returns true if this segment plays at a non-zero amplitude at some point. */ + public abstract boolean hasNonZeroAmplitude(); + + /** Validates the segment, throwing exceptions if any parameter is invalid. */ + public abstract void validate(); + + /** + * Resolves amplitudes set to {@link VibrationEffect#DEFAULT_AMPLITUDE}. + * + * <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger + * than {@link VibrationEffect#MAX_AMPLITUDE}. + */ + @NonNull + public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude); + + /** + * Scale the segment intensity with the given factor. + * + * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will + * scale down the intensity, values larger than 1 will scale up + */ + @NonNull + public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor); + + /** + * Applies given effect strength to prebaked effects. + * + * @param effectStrength new effect strength to be applied, one of + * VibrationEffect.EFFECT_STRENGTH_*. + */ + @NonNull + public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength); + + @NonNull + public static final Creator<VibrationEffectSegment> CREATOR = + new Creator<VibrationEffectSegment>() { + @Override + public VibrationEffectSegment createFromParcel(Parcel in) { + switch (in.readInt()) { + case PARCEL_TOKEN_STEP: + return new StepSegment(in); + case PARCEL_TOKEN_RAMP: + return new RampSegment(in); + case PARCEL_TOKEN_PREBAKED: + return new PrebakedSegment(in); + case PARCEL_TOKEN_PRIMITIVE: + return new PrimitiveSegment(in); + default: + throw new IllegalStateException( + "Unexpected vibration event type token in parcel."); + } + } + + @Override + public VibrationEffectSegment[] newArray(int size) { + return new VibrationEffectSegment[size]; + } + }; +} 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..baa25f07f514 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; @@ -79,6 +80,9 @@ public final class PermissionManager { private static final String LOG_TAG = PermissionManager.class.getName(); /** @hide */ + public static final String LOG_TAG_TRACE_GRANTS = "PermissionGrantTrace"; + + /** @hide */ public static final String KILL_APP_REASON_PERMISSIONS_REVOKED = "permissions revoked"; /** @hide */ @@ -102,6 +106,8 @@ public final class PermissionManager { * Note: Changing this won't do anything on its own - you should also change the filtering in * {@link #shouldTraceGrant}. * + * See log output for tag {@link #LOG_TAG_TRACE_GRANTS} + * * @hide */ public static final boolean DEBUG_TRACE_GRANTS = false; @@ -318,8 +324,10 @@ public final class PermissionManager { } /** @hide */ - public static boolean shouldTraceGrant(String packageName, String permissionName, int userId) { + public static boolean shouldTraceGrant( + @NonNull String packageName, @NonNull String permissionName, int userId) { // To be modified when debugging + // template: if ("".equals(packageName) && "".equals(permissionName)) return true; return false; } @@ -347,7 +355,8 @@ public final class PermissionManager { @NonNull String permissionName, @NonNull UserHandle user) { if (DEBUG_TRACE_GRANTS && shouldTraceGrant(packageName, permissionName, user.getIdentifier())) { - Log.i(LOG_TAG, "App " + mContext.getPackageName() + " is granting " + packageName + " " + Log.i(LOG_TAG_TRACE_GRANTS, "App " + mContext.getPackageName() + " is granting " + + packageName + " " + permissionName + " for user " + user.getIdentifier(), new RuntimeException()); } try { @@ -851,6 +860,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 80a3e1693ab1..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,13 +276,15 @@ public class PermissionUsageHelper { opEntry.getAttributedOpEntries().get(attributionTag); long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags); + if (attrOpEntry.isRunning()) { + lastAccessTime = now; + } + if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) { continue; } - if (packageName.equals(SYSTEM_PKG) - || (!shouldShowPermissionsHub() - && !isUserSensitive(packageName, user, op))) { + if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) { continue; } @@ -372,8 +371,10 @@ public class PermissionUsageHelper { proxyLabels.put(usage, new ArrayList<>()); proxyUids.add(usage.uid); } - if (!mostRecentUsages.containsKey(usage.uid) || usage.lastAccessTime - > mostRecentUsages.get(usage.uid).lastAccessTime) { + // If this usage is not by the system, and is more recent than the next-most recent + // for it's uid, save it. + if (!usage.packageName.equals(SYSTEM_PKG) && (!mostRecentUsages.containsKey(usage.uid) + || usage.lastAccessTime > mostRecentUsages.get(usage.uid).lastAccessTime)) { mostRecentUsages.put(usage.uid, usage); } } @@ -416,20 +417,22 @@ public class PermissionUsageHelper { } proxyUids.add(currentUsage.uid); - try { - PackageManager userPkgManager = - getUserContext(currentUsage.getUser()).getPackageManager(); - ApplicationInfo appInfo = userPkgManager.getApplicationInfo( - currentUsage.packageName, 0); - CharSequence appLabel = appInfo.loadLabel(userPkgManager); - // If we don't already have the app label, and it's not the same as the main - // app, add it - if (!proxyLabelList.contains(appLabel) - && !currentUsage.packageName.equals(start.packageName)) { - proxyLabelList.add(appLabel); + // Don't add an app label for the main app, or the system app + if (!currentUsage.packageName.equals(start.packageName) + && !currentUsage.packageName.equals(SYSTEM_PKG)) { + try { + PackageManager userPkgManager = + getUserContext(currentUsage.getUser()).getPackageManager(); + ApplicationInfo appInfo = userPkgManager.getApplicationInfo( + currentUsage.packageName, 0); + CharSequence appLabel = appInfo.loadLabel(userPkgManager); + // If we don't already have the app label add it + if (!proxyLabelList.contains(appLabel)) { + proxyLabelList.add(appLabel); + } + } catch (PackageManager.NameNotFoundException e) { + // Ignore } - } catch (PackageManager.NameNotFoundException e) { - // Ignore } iterNum++; } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 7e404972d11a..03b5a2e2568a 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 @@ -250,6 +257,14 @@ public final class DeviceConfig { public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler"; /** + * Namespace for all media related features. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_MEDIA = "media"; + + /** * Namespace for all media native related features. * * @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 71ffa92a81bb..591f05f27b4e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -470,8 +470,8 @@ public final class Settings { * to be shown, with the "package" scheme. That is "package:com.my.app". * <p> * Output: Nothing. - * @hide */ + @SuppressLint("ActionValue") @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "com.android.settings.APP_OPEN_BY_DEFAULT_SETTINGS"; @@ -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. @@ -6291,6 +6291,20 @@ public final class Settings { "selected_input_method_subtype"; /** + * The {@link android.view.inputmethod.InputMethodInfo.InputMethodInfo#getId() ID} of the + * default voice input method. + * <p> + * This stores the last known default voice IME. If the related system config value changes, + * this is reset by InputMethodManagerService. + * <p> + * This IME is not necessarily in the enabled IME list. That state is still stored in + * {@link #ENABLED_INPUT_METHODS}. + * + * @hide + */ + public static final String DEFAULT_VOICE_INPUT_METHOD = "default_voice_input_method"; + + /** * Setting to record the history of input method subtype, holding the pair of ID of IME * and its last used subtype. * @hide @@ -8523,6 +8537,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. @@ -9153,6 +9176,22 @@ public final class Settings { public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete"; /** + * Whether the assistant can be triggered by a touch gesture. + * + * @hide + */ + public static final String ASSIST_TOUCH_GESTURE_ENABLED = + "assist_touch_gesture_enabled"; + + /** + * Whether the assistant can be triggered by long-pressing the home button + * + * @hide + */ + public static final String ASSIST_LONG_PRESS_HOME_ENABLED = + "assist_long_press_home_enabled"; + + /** * Control whether Trust Agents are in active unlock or extend unlock mode. * @hide */ @@ -13112,7 +13151,7 @@ public final class Settings { * @see #ENABLE_RESTRICTED_BUCKET * @hide */ - public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 0; + public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 1; /** * Whether or not app auto restriction is enabled. When it is enabled, settings app will diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 374de9ccb2f4..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 @@ -4574,6 +4583,24 @@ public final class Telephony { * This is the same as {@link ServiceState#getIsManualSelection()}. */ public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection"; + + /** + * The current data network type. + * <p> + * 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"; } /** @@ -5317,5 +5344,14 @@ public final class Telephony { * @hide */ public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status"; + + /** + * TelephonyProvider column name for information selected contacts that allow device to + * device sharing. + * + * @hide + */ + public static final String COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS = + "d2d_sharing_contacts"; } } diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 04a4ca47d305..13274c6f27ee 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -38,6 +38,8 @@ import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; +import com.android.internal.os.IResultReceiver; + /** * An {@code AutofillService} is a service used to automatically fill the contents of the screen * on behalf of a given user - for more information about autofill, read @@ -575,6 +577,20 @@ public abstract class AutofillService extends Service { */ public static final String SERVICE_META_DATA = "android.autofill"; + /** + * Name of the {@link IResultReceiver} extra used to return the primary result of a request. + * + * @hide + */ + public static final String EXTRA_RESULT = "result"; + + /** + * Name of the {@link IResultReceiver} extra used to return the error reason of a request. + * + * @hide + */ + public static final String EXTRA_ERROR = "error"; + private final IAutoFillService mInterface = new IAutoFillService.Stub() { @Override public void onConnectedStateChanged(boolean connected) { @@ -603,6 +619,14 @@ public abstract class AutofillService extends Service { AutofillService::onSaveRequest, AutofillService.this, request, new SaveCallback(callback))); } + + @Override + public void onSavedPasswordCountRequest(IResultReceiver receiver) { + mHandler.sendMessage(obtainMessage( + AutofillService::onSavedDatasetsInfoRequest, + AutofillService.this, + new SavedDatasetsInfoCallbackImpl(receiver, SavedDatasetsInfo.TYPE_PASSWORDS))); + } }; private Handler mHandler; @@ -673,6 +697,19 @@ public abstract class AutofillService extends Service { @NonNull SaveCallback callback); /** + * Called from system settings to display information about the datasets the user saved to this + * service. + * + * <p>There is no timeout for the request, but it's recommended to return the result within a + * few seconds, or the user may navigate away from the activity that would display the result. + * + * @param callback callback for responding to the request + */ + public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) { + callback.onError(SavedDatasetsInfoCallback.ERROR_UNSUPPORTED); + } + + /** * Called when the Android system disconnects from the service. * * <p> At this point this service may no longer be an active {@link AutofillService}. diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 23a1a3fee47c..d88e0945bdca 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -31,4 +31,5 @@ oneway interface IAutoFillService { void onConnectedStateChanged(boolean connected); void onFillRequest(in FillRequest request, in IFillCallback callback); void onSaveRequest(in SaveRequest request, in ISaveCallback callback); + void onSavedPasswordCountRequest(in IResultReceiver receiver); } diff --git a/core/java/android/service/autofill/SavedDatasetsInfo.java b/core/java/android/service/autofill/SavedDatasetsInfo.java new file mode 100644 index 000000000000..6a4d2b834cef --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfo.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.StringDef; + +import com.android.internal.util.DataClass; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A result returned from + * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}. + */ +@DataClass( + genToString = true, + genHiddenConstDefs = true, + genEqualsHashCode = true) +public final class SavedDatasetsInfo { + + /** + * Any other type of datasets. + */ + public static final String TYPE_OTHER = "other"; + + /** + * Datasets such as login credentials. + */ + public static final String TYPE_PASSWORDS = "passwords"; + + /** + * The type of the datasets that this info is about. + */ + @NonNull + @Type + private final String mType; + + /** + * The number of datasets of {@link #getType() this type} that the user has saved to the + * service. + */ + @IntRange(from = 0) + private final int mCount; + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @StringDef(prefix = "TYPE_", value = { + TYPE_OTHER, + TYPE_PASSWORDS + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Type {} + + /** + * Creates a new SavedDatasetsInfo. + * + * @param type + * The type of the datasets. + * @param count + * The number of datasets of this type that the user has saved to the service. + */ + @DataClass.Generated.Member + public SavedDatasetsInfo( + @NonNull @Type String type, + @IntRange(from = 0) int count) { + this.mType = type; + + if (!(java.util.Objects.equals(mType, TYPE_OTHER)) + && !(java.util.Objects.equals(mType, TYPE_PASSWORDS))) { + throw new java.lang.IllegalArgumentException( + "type was " + mType + " but must be one of: " + + "TYPE_OTHER(" + TYPE_OTHER + "), " + + "TYPE_PASSWORDS(" + TYPE_PASSWORDS + ")"); + } + + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mType); + this.mCount = count; + com.android.internal.util.AnnotationValidations.validate( + IntRange.class, null, mCount, + "from", 0); + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The type of the datasets. + */ + @DataClass.Generated.Member + public @NonNull @Type String getType() { + return mType; + } + + /** + * The number of datasets of this type that the user has saved to the service. + */ + @DataClass.Generated.Member + public @IntRange(from = 0) int getCount() { + return mCount; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "SavedDatasetsInfo { " + + "type = " + mType + ", " + + "count = " + mCount + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(SavedDatasetsInfo other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + SavedDatasetsInfo that = (SavedDatasetsInfo) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mType, that.mType) + && mCount == that.mCount; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mType); + _hash = 31 * _hash + mCount; + return _hash; + } + + @DataClass.Generated( + time = 1615325704446L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java", + inputSignatures = "public static final java.lang.String TYPE_OTHER\npublic static final java.lang.String TYPE_PASSWORDS\nprivate final @android.annotation.NonNull @android.service.autofill.SavedDatasetsInfo.Type java.lang.String mType\nprivate final @android.annotation.IntRange int mCount\nclass SavedDatasetsInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallback.java b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java new file mode 100644 index 000000000000..a47105a23816 --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +/** + * Handles the response to + * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}. + * <p> + * Use {@link #onSuccess(Set)} to return the computed info about the datasets the user saved to this + * service. If there was an error querying the info, or if the service is unable to do so at this + * time (for example, if the user isn't logged in), call {@link #onError(int)}. + * <p> + * This callback can be used only once. + */ +public interface SavedDatasetsInfoCallback { + + /** @hide */ + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_OTHER, + ERROR_UNSUPPORTED, + ERROR_NEEDS_USER_ACTION + }) + @Retention(RetentionPolicy.SOURCE) + @interface Error { + } + + /** + * The result could not be computed for any other reason. + */ + int ERROR_OTHER = 0; + /** + * The service does not support this request. + */ + int ERROR_UNSUPPORTED = 1; + /** + * The result cannot be computed until the user takes some action, such as setting up their + * account. + */ + int ERROR_NEEDS_USER_ACTION = 2; + + /** + * Successfully respond to the request with the info on each type of saved datasets. + */ + void onSuccess(@NonNull Set<SavedDatasetsInfo> results); + + /** + * Respond to the request with an error. System settings may display a suitable notice to the + * user. + */ + void onError(@Error int error); +} diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java new file mode 100644 index 000000000000..b8a8cde12d3e --- /dev/null +++ b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.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.service.autofill; + +import static android.service.autofill.AutofillService.EXTRA_ERROR; +import static android.service.autofill.AutofillService.EXTRA_RESULT; + +import static com.android.internal.util.Preconditions.checkArgumentInRange; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.os.Bundle; +import android.os.DeadObjectException; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.os.IResultReceiver; + +import java.util.Set; + +final class SavedDatasetsInfoCallbackImpl implements SavedDatasetsInfoCallback { + private static final String TAG = "AutofillService"; + + @NonNull + private final IResultReceiver mReceiver; + @NonNull + private final String mType; + + /** + * Creates a {@link SavedDatasetsInfoCallback} that returns the {@link + * SavedDatasetsInfo#getCount() number} of saved datasets of {@code type} to the {@code + * receiver}. + */ + SavedDatasetsInfoCallbackImpl(@NonNull IResultReceiver receiver, @NonNull String type) { + mReceiver = requireNonNull(receiver); + mType = requireNonNull(type); + } + + @Override + public void onSuccess(@NonNull Set<SavedDatasetsInfo> results) { + requireNonNull(results); + if (results.isEmpty()) { + send(1, null); + return; + } + int count = -1; + for (SavedDatasetsInfo info : results) { + if (mType.equals(info.getType())) { + count = info.getCount(); + } + } + if (count < 0) { + send(1, null); + return; + } + Bundle bundle = new Bundle(/* capacity= */ 1); + bundle.putInt(EXTRA_RESULT, count); + send(0, bundle); + } + + @Override + public void onError(@Error int error) { + checkArgumentInRange(error, ERROR_OTHER, ERROR_NEEDS_USER_ACTION, "error"); + Bundle bundle = new Bundle(/* capacity= */ 1); + bundle.putInt(EXTRA_ERROR, error); + send(1, bundle); + } + + private void send(int resultCode, Bundle bundle) { + try { + mReceiver.send(resultCode, bundle); + } catch (DeadObjectException e) { + Log.w(TAG, "Failed to send onSavedPasswordCountRequest result: " + e); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } +} diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index aeeaa97e2287..d9a310f7a6a3 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -618,7 +618,7 @@ public abstract class AugmentedAutofillService extends Service { mFirstOnSuccessTime = SystemClock.elapsedRealtime(); duration = mFirstOnSuccessTime - mFirstRequestTime; if (sDebug) { - Log.d(TAG, "Service responded nothing in " + formatDuration(duration)); + Log.d(TAG, "Inline response in " + formatDuration(duration)); } } } break; diff --git a/core/java/android/service/translation/TranslationServiceInfo.java b/core/java/android/service/translation/TranslationServiceInfo.java index 18cc29d12b5f..c7017b2ea73b 100644 --- a/core/java/android/service/translation/TranslationServiceInfo.java +++ b/core/java/android/service/translation/TranslationServiceInfo.java @@ -100,9 +100,9 @@ public final class TranslationServiceInfo { if (!Manifest.permission.BIND_TRANSLATION_SERVICE.equals(si.permission)) { Slog.w(TAG, "TranslationServiceInfo from '" + si.packageName + "' does not require permission " - + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); + + Manifest.permission.BIND_TRANSLATION_SERVICE); throw new SecurityException("Service does not require permission " - + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE); + + Manifest.permission.BIND_TRANSLATION_SERVICE); } mServiceInfo = si; diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index def13db41559..94ca68ff6af8 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -41,10 +41,11 @@ import android.media.permission.Identity; import android.os.AsyncTask; import android.os.Binder; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; import android.util.Slog; @@ -239,18 +240,6 @@ public class AlwaysOnHotwordDetector { public @interface ModelParams {} /** - * Indicates that the given audio data is a false alert for {@link VoiceInteractionService}. - */ - public static final int HOTWORD_DETECTION_FALSE_ALERT = 0; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, prefix = { "HOTWORD_DETECTION_" }, value = { - HOTWORD_DETECTION_FALSE_ALERT, - }) - public @interface HotwordDetectionResult {} - - /** * Controls the sensitivity threshold adjustment factor for a given model. * Negative value corresponds to less sensitive model (high threshold) and * a positive value corresponds to a more sensitive model (low threshold). @@ -354,14 +343,35 @@ public class AlwaysOnHotwordDetector { // Raw data associated with the event. // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true. private final byte[] mData; + private final HotwordDetectedResult mHotwordDetectedResult; + private final ParcelFileDescriptor mAudioStream; private EventPayload(boolean triggerAvailable, boolean captureAvailable, AudioFormat audioFormat, int captureSession, byte[] data) { + this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, null, + null); + } + + EventPayload(AudioFormat audioFormat, HotwordDetectedResult hotwordDetectedResult) { + this(false, false, audioFormat, -1, null, hotwordDetectedResult, null); + } + + EventPayload(AudioFormat audioFormat, + HotwordDetectedResult hotwordDetectedResult, + ParcelFileDescriptor audioStream) { + this(false, false, audioFormat, -1, null, hotwordDetectedResult, audioStream); + } + + private EventPayload(boolean triggerAvailable, boolean captureAvailable, + AudioFormat audioFormat, int captureSession, byte[] data, + HotwordDetectedResult hotwordDetectedResult, ParcelFileDescriptor audioStream) { mTriggerAvailable = triggerAvailable; mCaptureAvailable = captureAvailable; mCaptureSession = captureSession; mAudioFormat = audioFormat; mData = data; + mHotwordDetectedResult = hotwordDetectedResult; + mAudioStream = audioStream; } /** @@ -417,6 +427,33 @@ public class AlwaysOnHotwordDetector { return null; } } + + /** + * Returns {@link HotwordDetectedResult} associated with the hotword event, passed from + * {@link HotwordDetectionService}. + */ + @Nullable + public HotwordDetectedResult getHotwordDetectedResult() { + return mHotwordDetectedResult; + } + + /** + * Returns a stream with bytes corresponding to the open audio stream with hotword data. + * + * <p>This data represents an audio stream in the format returned by + * {@link #getCaptureAudioFormat}. + * + * <p>Clients are expected to start consuming the stream within 1 second of receiving the + * event. + * + * <p>When this method returns a non-null, clients must close this stream when it's no + * longer needed. Failing to do so will result in microphone being open for longer periods + * of time, and app being attributed for microphone usage. + */ + @Nullable + public ParcelFileDescriptor getAudioStream() { + return mAudioStream; + } } /** @@ -478,11 +515,14 @@ public class AlwaysOnHotwordDetector { public abstract void onRecognitionResumed(); /** - * Called when the validated result is invalid. + * Called when the {@link HotwordDetectionService second stage detection} did not detect the + * keyphrase. * - * @param reason The reason why the validated result is invalid. + * @param result Info about the second stage detection result, provided by the + * {@link HotwordDetectionService}. */ - public void onRejected(@HotwordDetectionResult int reason) {} + public void onRejected(@Nullable HotwordRejectedResult result) { + } } /** @@ -494,8 +534,8 @@ public class AlwaysOnHotwordDetector { * @param supportHotwordDetectionService {@code true} if hotword detection service should be * triggered, otherwise {@code false}. * @param options Application configuration data provided by the - * {@link VoiceInteractionService}. The system strips out any remotable objects or other - * contents that can be used to communicate with other processes. + * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or + * other contents that can be used to communicate with other processes. * @param sharedMemory The unrestricted data blob provided by the * {@link VoiceInteractionService}. Use this to provide the hotword models data or other * such data to the trusted process. @@ -505,7 +545,7 @@ public class AlwaysOnHotwordDetector { public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, IVoiceInteractionManagerService modelManagementService, int targetSdkVersion, - boolean supportHotwordDetectionService, @Nullable Bundle options, + boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { mText = text; mLocale = locale; @@ -517,7 +557,7 @@ public class AlwaysOnHotwordDetector { mTargetSdkVersion = targetSdkVersion; mSupportHotwordDetectionService = supportHotwordDetectionService; if (mSupportHotwordDetectionService) { - setHotwordDetectionServiceConfig(options, sharedMemory); + updateState(options, sharedMemory); } try { Identity identity = new Identity(); @@ -533,30 +573,28 @@ public class AlwaysOnHotwordDetector { /** * Set configuration and pass read-only data to hotword detection service. * - * @param options Application configuration data provided by the - * {@link VoiceInteractionService}. The system strips out any remotable objects or other - * contents that can be used to communicate with other processes. - * @param sharedMemory The unrestricted data blob provided by the - * {@link VoiceInteractionService}. Use this to provide the hotword models data or other + * @param options Application configuration data to provide to the + * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or + * other contents that can be used to communicate with other processes. + * @param sharedMemory The unrestricted data blob to provide to the + * {@link HotwordDetectionService}. Use this to provide the hotword models data or other * such data to the trusted process. * - * @throws IllegalStateException if it doesn't support hotword detection service. - * - * @hide + * @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a + * {@link HotwordDetectionService} when it was created. */ - public final void setHotwordDetectionServiceConfig(@Nullable Bundle options, + public final void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { if (DBG) { - Slog.d(TAG, "setHotwordDetectionServiceConfig()"); + Slog.d(TAG, "updateState()"); } if (!mSupportHotwordDetectionService) { throw new IllegalStateException( - "setHotwordDetectionServiceConfig called, but it doesn't support hotword" - + " detection service"); + "updateState called, but it doesn't support hotword detection service"); } try { - mModelManagementService.setHotwordDetectionServiceConfig(options, sharedMemory); + mModelManagementService.updateState(options, sharedMemory); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1069,13 +1107,13 @@ public class AlwaysOnHotwordDetector { } @Override - public void onRejected(int reason) { + public void onRejected(HotwordRejectedResult result) { if (DBG) { - Slog.d(TAG, "onRejected(" + reason + ")"); + Slog.d(TAG, "onRejected(" + result + ")"); } else { Slog.i(TAG, "onRejected"); } - Message.obtain(mHandler, MSG_HOTWORD_REJECTED, reason).sendToTarget(); + Message.obtain(mHandler, MSG_HOTWORD_REJECTED, result).sendToTarget(); } @Override @@ -1124,7 +1162,7 @@ public class AlwaysOnHotwordDetector { mExternalCallback.onRecognitionResumed(); break; case MSG_HOTWORD_REJECTED: - mExternalCallback.onRejected(msg.arg1); + mExternalCallback.onRejected((HotwordRejectedResult) msg.obj); break; default: super.handleMessage(msg); diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index fcef26f13dd0..db984c246b2f 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -27,11 +27,11 @@ import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.media.AudioFormat; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; import android.util.Log; @@ -82,9 +82,10 @@ public abstract class HotwordDetectionService extends Service { } @Override - public void setConfig(Bundle options, SharedMemory sharedMemory) throws RemoteException { + public void updateState(PersistableBundle options, SharedMemory sharedMemory) + throws RemoteException { if (DBG) { - Log.d(TAG, "#setConfig"); + Log.d(TAG, "#updateState"); } mHandler.sendMessage(obtainMessage(HotwordDetectionService::onUpdateState, HotwordDetectionService.this, @@ -137,21 +138,22 @@ public abstract class HotwordDetectionService extends Service { /** * Called when the {@link VoiceInteractionService#createAlwaysOnHotwordDetector(String, Locale, - * Bundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or {@link AlwaysOnHotwordDetector# - * setHotwordDetectionServiceConfig(Bundle, SharedMemory)} requests an update of the hotword - * detection parameters. + * PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} or + * {@link AlwaysOnHotwordDetector#updateState(PersistableBundle, SharedMemory)} requests an + * update of the hotword detection parameters. * - * @param options Application configuration data provided by the - * {@link VoiceInteractionService}. The system strips out any remotable objects or other - * contents that can be used to communicate with other processes. - * @param sharedMemory The unrestricted data blob provided by the - * {@link VoiceInteractionService}. Use this to provide the hotword models data or other + * @param options Application configuration data to provide to the + * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or + * other contents that can be used to communicate with other processes. + * @param sharedMemory The unrestricted data blob to provide to the + * {@link HotwordDetectionService}. Use this to provide the hotword models data or other * such data to the trusted process. * * @hide */ @SystemApi - public void onUpdateState(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) { + public void onUpdateState(@Nullable PersistableBundle options, + @Nullable SharedMemory sharedMemory) { } /** @@ -180,11 +182,14 @@ public abstract class HotwordDetectionService extends Service { } /** - * Called when the detected result is invalid. + * Informs the {@link AlwaysOnHotwordDetector} that the keyphrase was not detected. + * + * @param result Info about the second stage detection result. This is provided to + * the {@link AlwaysOnHotwordDetector}. */ - public void onRejected() { + public void onRejected(@Nullable HotwordRejectedResult result) { try { - mRemoteCallback.onRejected(); + mRemoteCallback.onRejected(result); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl index bb95bb831dec..6f641e1cd1e7 100644 --- a/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl +++ b/core/java/android/service/voice/IDspHotwordDetectionCallback.aidl @@ -16,6 +16,8 @@ package android.service.voice; +import android.service.voice.HotwordRejectedResult; + /** * Callback for returning the detected result from the HotwordDetectionService. * @@ -28,7 +30,7 @@ oneway interface IDspHotwordDetectionCallback { void onDetected(); /** - * Called when the detected result is invalid. + * Sends {@code result} to the HotwordDetector. */ - void onRejected(); + void onRejected(in HotwordRejectedResult result); } diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/IHotwordDetectionService.aidl index 8f0874a5cb2e..0791f1ca49eb 100644 --- a/core/java/android/service/voice/IHotwordDetectionService.aidl +++ b/core/java/android/service/voice/IHotwordDetectionService.aidl @@ -17,8 +17,8 @@ package android.service.voice; import android.media.AudioFormat; -import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.SharedMemory; import android.service.voice.IDspHotwordDetectionCallback; @@ -34,5 +34,5 @@ oneway interface IHotwordDetectionService { long timeoutMillis, in IDspHotwordDetectionCallback callback); - void setConfig(in Bundle options, in SharedMemory sharedMemory); + void updateState(in PersistableBundle options, in SharedMemory sharedMemory); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 9ba39a1b37f7..cb3791d9986a 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -33,6 +33,7 @@ import android.media.voice.KeyphraseModelManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SharedMemory; @@ -342,8 +343,8 @@ public class VoiceInteractionService extends Service { * @param keyphrase The keyphrase that's being used, for example "Hello Android". * @param locale The locale for which the enrollment needs to be performed. * @param options Application configuration data provided by the - * {@link VoiceInteractionService}. The system strips out any remotable objects or other - * contents that can be used to communicate with other processes. + * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or + * other contents that can be used to communicate with other processes. * @param sharedMemory The unrestricted data blob provided by the * {@link VoiceInteractionService}. Use this to provide the hotword models data or other * such data to the trusted process. @@ -358,7 +359,7 @@ public class VoiceInteractionService extends Service { public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, - @Nullable Bundle options, + @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, @@ -370,7 +371,7 @@ public class VoiceInteractionService extends Service { @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, boolean supportHotwordDetectionService, - @Nullable Bundle options, + @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { if (mSystemService == null) { diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index d47ae2783336..1da7dc403d39 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -343,11 +343,14 @@ public class PhoneStateListener { /** * Listen for display info changed event. * - * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE - * READ_PHONE_STATE} or that the calling app has carrier privileges (see - * {@link TelephonyManager#hasCarrierPrivileges}). - * - * @see #onDisplayInfoChanged + * For clients compiled on Android 11 SDK, requires permission: + * {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling app has carrier + * privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * For clients compiled on Android 12 SDK or newer, + * {@link android.Manifest.permission#READ_PHONE_STATE} or carrier privileges is not required + * anymore. + * + * @see #onDisplayInfoChanged * @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead. */ @Deprecated @@ -981,8 +984,12 @@ public class PhoneStateListener { * <p> The {@link TelephonyDisplayInfo} contains status information shown to the user based on * carrier policy. * - * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling - * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * For clients compiled on Android 11 SDK, requires permission: + * {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling app has carrier + * privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * For clients compiled on Android 12 SDK or newer, + * {@link android.Manifest.permission#READ_PHONE_STATE} or carrier privileges is not required + * anymore. * * @param telephonyDisplayInfo The display information. * @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead. diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index d0000005c237..18949cdbeeab 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -1057,7 +1057,6 @@ public class TelephonyCallback { * * @param telephonyDisplayInfo The display information. */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo); } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 919c6e50e3a4..913ceae63a94 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -71,7 +71,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_silky_home", "true"); DEFAULT_FLAGS.put("settings_contextual_home", "false"); DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false"); - DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "false"); + DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "false"); } diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java index b0916d3d0cca..f2bc0c5a34d6 100644 --- a/core/java/android/util/SparseLongArray.java +++ b/core/java/android/util/SparseLongArray.java @@ -164,7 +164,7 @@ public class SparseLongArray implements Cloneable { } /** - * Returns the number of key-value mappings that this SparseIntArray + * Returns the number of key-value mappings that this SparseLongArray * currently stores. */ public int size() { @@ -246,7 +246,7 @@ public class SparseLongArray implements Cloneable { } /** - * Removes all key-value mappings from this SparseIntArray. + * Removes all key-value mappings from this SparseLongArray. */ public void clear() { mSize = 0; diff --git a/core/java/android/uwb/AngleMeasurement.java b/core/java/android/uwb/AngleMeasurement.java index 8c771baaea37..3d603737c48c 100644 --- a/core/java/android/uwb/AngleMeasurement.java +++ b/core/java/android/uwb/AngleMeasurement.java @@ -48,7 +48,10 @@ public final class AngleMeasurement implements Parcelable { * @throws IllegalArgumentException if the radians, errorRadians, or confidenceLevel is out of * allowed range */ - public AngleMeasurement(double radians, double errorRadians, double confidenceLevel) { + public AngleMeasurement( + @FloatRange(from = -Math.PI, to = +Math.PI) double radians, + @FloatRange(from = 0.0, to = +Math.PI) double errorRadians, + @FloatRange(from = 0.0, to = 1.0) double confidenceLevel) { if (radians < -Math.PI || radians > Math.PI) { throw new IllegalArgumentException("Invalid radians: " + radians); } 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/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 98b7dbfa670f..8db6456c0380 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -64,8 +64,14 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { } @Override - void hide(boolean animationFinished, @AnimationType int animationType) { + public void hide() { super.hide(); + mIsRequestedVisibleAwaitingControl = false; + } + + @Override + void hide(boolean animationFinished, @AnimationType int animationType) { + hide(); if (animationFinished) { // remove IME surface as IME has finished hide animation. @@ -122,6 +128,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { hide(); removeSurface(); } + if (control != null) { + mIsRequestedVisibleAwaitingControl = false; + } } @Override diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 6801c27851a9..dea32cd96756 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -16,6 +16,7 @@ package android.view; +import static android.hardware.input.InputManager.APP_USES_RAW_INPUT_COORDS; import static android.view.Display.DEFAULT_DISPLAY; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -23,6 +24,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.TestApi; +import android.compat.Compatibility; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Matrix; import android.os.Build; @@ -2672,6 +2674,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #AXIS_X */ public final float getRawX() { + Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS); return nativeGetRawAxisValue(mNativePtr, AXIS_X, 0, HISTORY_CURRENT); } @@ -2685,6 +2688,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #AXIS_Y */ public final float getRawY() { + Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS); return nativeGetRawAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT); } @@ -2701,6 +2705,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #AXIS_X */ public float getRawX(int pointerIndex) { + Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS); return nativeGetRawAxisValue(mNativePtr, AXIS_X, pointerIndex, HISTORY_CURRENT); } @@ -2717,6 +2722,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #AXIS_Y */ public float getRawY(int pointerIndex) { + Compatibility.reportChange(APP_USES_RAW_INPUT_COORDS); return nativeGetRawAxisValue(mNativePtr, AXIS_Y, pointerIndex, HISTORY_CURRENT); } diff --git a/core/java/android/view/SoundEffectConstants.java b/core/java/android/view/SoundEffectConstants.java index f177451783dc..bd86a47f3918 100644 --- a/core/java/android/view/SoundEffectConstants.java +++ b/core/java/android/view/SoundEffectConstants.java @@ -16,11 +16,14 @@ package android.view; +import android.annotation.IntDef; import android.media.AudioManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Random; /** @@ -34,25 +37,55 @@ public class SoundEffectConstants { public static final int CLICK = 0; + /** Effect id for a navigation left */ public static final int NAVIGATION_LEFT = 1; + /** Effect id for a navigation up */ public static final int NAVIGATION_UP = 2; + /** Effect id for a navigation right */ public static final int NAVIGATION_RIGHT = 3; + /** Effect id for a navigation down */ public static final int NAVIGATION_DOWN = 4; - /** Sound effect for a repeatedly triggered navigation, e.g. due to long pressing a button */ + /** Effect id for a repeatedly triggered navigation left, e.g. due to long pressing a button */ public static final int NAVIGATION_REPEAT_LEFT = 5; - /** @see #NAVIGATION_REPEAT_LEFT */ + /** Effect id for a repeatedly triggered navigation up, e.g. due to long pressing a button */ public static final int NAVIGATION_REPEAT_UP = 6; - /** @see #NAVIGATION_REPEAT_LEFT */ + /** Effect id for a repeatedly triggered navigation right, e.g. due to long pressing a button */ public static final int NAVIGATION_REPEAT_RIGHT = 7; - /** @see #NAVIGATION_REPEAT_LEFT */ + /** Effect id for a repeatedly triggered navigation down, e.g. due to long pressing a button */ public static final int NAVIGATION_REPEAT_DOWN = 8; + /** @hide */ + @IntDef(value = { + CLICK, + NAVIGATION_LEFT, + NAVIGATION_UP, + NAVIGATION_RIGHT, + NAVIGATION_DOWN, + NAVIGATION_REPEAT_LEFT, + NAVIGATION_REPEAT_UP, + NAVIGATION_REPEAT_RIGHT, + NAVIGATION_REPEAT_DOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SoundEffect {} + + /** @hide */ + @IntDef(prefix = { "NAVIGATION_" }, value = { + NAVIGATION_LEFT, + NAVIGATION_UP, + NAVIGATION_RIGHT, + NAVIGATION_DOWN, + NAVIGATION_REPEAT_LEFT, + NAVIGATION_REPEAT_UP, + NAVIGATION_REPEAT_RIGHT, + NAVIGATION_REPEAT_DOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NavigationSoundEffect {} + /** * Get the sonification constant for the focus directions. - * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD} - * or {@link View#FOCUS_BACKWARD} - + * @param direction The direction of the focus. * @return The appropriate sonification constant. * @throws {@link IllegalArgumentException} when the passed direction is not one of the * documented values. @@ -76,16 +109,14 @@ public class SoundEffectConstants { /** * Get the sonification constant for the focus directions - * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, - * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD} - * or {@link View#FOCUS_BACKWARD} + * @param direction The direction of the focus. * @param repeating True if the user long-presses a direction * @return The appropriate sonification constant * @throws IllegalArgumentException when the passed direction is not one of the * documented values. */ - public static int getConstantForFocusDirection(@View.FocusDirection int direction, - boolean repeating) { + public static @NavigationSoundEffect int getConstantForFocusDirection( + @View.FocusDirection int direction, boolean repeating) { if (repeating) { switch (direction) { case View.FOCUS_RIGHT: @@ -112,7 +143,7 @@ public class SoundEffectConstants { * @hide */ @VisibleForTesting(visibility = Visibility.PACKAGE) - public static boolean isNavigationRepeat(int effectId) { + public static boolean isNavigationRepeat(@NavigationSoundEffect int effectId) { return effectId == SoundEffectConstants.NAVIGATION_REPEAT_DOWN || effectId == SoundEffectConstants.NAVIGATION_REPEAT_LEFT || effectId == SoundEffectConstants.NAVIGATION_REPEAT_RIGHT 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/SurfaceView.java b/core/java/android/view/SurfaceView.java index f1eef9fad8a1..82106b09ca5c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1389,6 +1389,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // If we are using BLAST, merge the transaction with the viewroot buffer transaction. viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber); return; + } else { + mRtTransaction.apply(); } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3abc3853626a..a03e9e352c60 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -26141,9 +26141,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>The sound effect will only be played if sound effects are enabled by the user, and * {@link #isSoundEffectsEnabled()} is true. * - * @param soundConstant One of the constants defined in {@link SoundEffectConstants} + * @param soundConstant One of the constants defined in {@link SoundEffectConstants}. */ - public void playSoundEffect(int soundConstant) { + public void playSoundEffect(@SoundEffectConstants.SoundEffect int soundConstant) { if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) { return; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index dbccf10822cd..0a246a68c56d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4293,7 +4293,7 @@ public final class ViewRootImpl implements ViewParent, mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS; boolean useAsyncReport = false; - if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { + if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) { if (isHardwareEnabled()) { // If accessibility focus moved, always invalidate the root. boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested; @@ -7754,7 +7754,7 @@ public final class ViewRootImpl implements ViewParent, * {@inheritDoc} */ @Override - public void playSoundEffect(int effectId) { + public void playSoundEffect(@SoundEffectConstants.SoundEffect int effectId) { checkThread(); try { 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/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index 2b12230510bf..ce014693c4c4 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -143,6 +143,9 @@ public final class ContentCaptureEvent implements Parcelable { private @Nullable ContentCaptureContext mClientContext; private @Nullable Insets mInsets; + /** Only used in the main Content Capture session, no need to parcel */ + private boolean mTextHasComposingSpan; + /** @hide */ public ContentCaptureEvent(int sessionId, int type, long eventTime) { mSessionId = sessionId; @@ -243,11 +246,21 @@ public final class ContentCaptureEvent implements Parcelable { /** @hide */ @NonNull - public ContentCaptureEvent setText(@Nullable CharSequence text) { + public ContentCaptureEvent setText(@Nullable CharSequence text, boolean hasComposingSpan) { mText = text; + mTextHasComposingSpan = hasComposingSpan; return this; } + /** + * The value is not parcelled, become false after parcelled. + * @hide + */ + @NonNull + public boolean getTextHasComposingSpan() { + return mTextHasComposingSpan; + } + /** @hide */ @NonNull public ContentCaptureEvent setInsets(@NonNull Insets insets) { @@ -361,7 +374,7 @@ public final class ContentCaptureEvent implements Parcelable { throw new IllegalArgumentException("mergeEvent(): got " + "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event); } else if (eventType == TYPE_VIEW_TEXT_CHANGED) { - setText(event.getText()); + setText(event.getText(), event.getTextHasComposingSpan()); } else { Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType) + ") does not support this event type."); @@ -479,7 +492,7 @@ public final class ContentCaptureEvent implements Parcelable { if (node != null) { event.setViewNode(node); } - event.setText(parcel.readCharSequence()); + event.setText(parcel.readCharSequence(), false); if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) { event.setParentSessionId(parcel.readInt()); } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 5ca793e3c394..f196f75861ec 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -43,12 +43,15 @@ import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; +import android.text.Spannable; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.LocalLog; import android.util.Log; import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.contentcapture.ViewNode.ViewStructureImpl; +import android.view.inputmethod.BaseInputConnection; import com.android.internal.os.IResultReceiver; @@ -57,6 +60,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -147,6 +151,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private final LocalLog mFlushHistory; /** + * If the event in the buffer is of type {@link TYPE_VIEW_TEXT_CHANGED}, this value + * indicates whether the event has composing span or not. + */ + private final Map<AutofillId, Boolean> mLastComposingSpan = new ArrayMap<>(); + + /** * Binder object used to update the session state. */ @NonNull @@ -335,26 +345,47 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // Some type of events can be merged together boolean addEvent = true; - if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) { - final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1); - - // We merge two consecutive text change event, unless one of them clears the text. - if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED - && lastEvent.getId().equals(event.getId())) { - boolean bothNonEmpty = !TextUtils.isEmpty(lastEvent.getText()) - && !TextUtils.isEmpty(event.getText()); - boolean equalContent = TextUtils.equals(lastEvent.getText(), event.getText()); - if (equalContent) { - addEvent = false; - } else if (bothNonEmpty) { - lastEvent.mergeEvent(event); - addEvent = false; - } - if (!addEvent && sVerbose) { - Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text=" - + getSanitizedString(event.getText())); + if (eventType == TYPE_VIEW_TEXT_CHANGED) { + // We determine whether to add or merge the current event by following criteria: + // 1. Don't have composing span: always add. + // 2. Have composing span: + // 2.1 either last or current text is empty: add. + // 2.2 last event doesn't have composing span: add. + // Otherwise, merge. + + final CharSequence text = event.getText(); + final boolean textHasComposingSpan = event.getTextHasComposingSpan(); + + if (textHasComposingSpan && !mLastComposingSpan.isEmpty()) { + final Boolean lastEventHasComposingSpan = mLastComposingSpan.get(event.getId()); + if (lastEventHasComposingSpan != null && lastEventHasComposingSpan.booleanValue()) { + ContentCaptureEvent lastEvent = null; + for (int index = mEvents.size() - 1; index >= 0; index--) { + final ContentCaptureEvent tmpEvent = mEvents.get(index); + if (event.getId().equals(tmpEvent.getId())) { + lastEvent = tmpEvent; + break; + } + } + if (lastEvent != null) { + final CharSequence lastText = lastEvent.getText(); + final boolean bothNonEmpty = !TextUtils.isEmpty(lastText) + && !TextUtils.isEmpty(text); + boolean equalContent = TextUtils.equals(lastText, text); + if (equalContent) { + addEvent = false; + } else if (bothNonEmpty && lastEventHasComposingSpan) { + lastEvent.mergeEvent(event); + addEvent = false; + } + if (!addEvent && sVerbose) { + Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text=" + + getSanitizedString(text)); + } + } } } + mLastComposingSpan.put(event.getId(), textHasComposingSpan); } if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) { @@ -374,6 +405,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { mEvents.add(event); } + // TODO: we need to change when the flush happens so that we don't flush while the + // composing span hasn't changed. But we might need to keep flushing the events for the + // non-editable views and views that don't have the composing state; otherwise some other + // Content Capture features may be delayed. + final int numberEvents = mEvents.size(); final boolean bufferEvent = numberEvents < maxBufferSize; @@ -550,6 +586,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { ? Collections.emptyList() : mEvents; mEvents = null; + mLastComposingSpan.clear(); return new ParceledListSlice<>(events); } @@ -677,9 +714,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { + // Since the same CharSequence instance may be reused in the TextView, we need to make + // a copy of its content so that its value will not be changed by subsequent updates + // in the TextView. + final String eventText = text == null ? null : text.toString(); + final boolean textHasComposingSpan = + text instanceof Spannable && BaseInputConnection.getComposingSpanStart( + (Spannable) text) >= 0; mHandler.post(() -> sendEvent( new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED) - .setAutofillId(id).setText(text))); + .setAutofillId(id).setText(eventText, textHasComposingSpan))); } /** Public because is also used by ViewRootImpl */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 3cd39021685d..6edd07178ed3 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2017,10 +2017,12 @@ public final class InputMethodManager { } mServedInputConnectionWrapper = servedContext; - try { - if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic=" + if (DEBUG) { + Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic=" + ic + " tba=" + tba + " startInputFlags=" + InputMethodDebug.startInputFlagsToString(startInputFlags)); + } + try { final Completable.InputBindResult value = Completable.createInputBindResult(); mService.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, @@ -2028,37 +2030,37 @@ public final class InputMethodManager { view.getContext().getApplicationInfo().targetSdkVersion, ResultCallbacks.of(value)); res = Completable.getResult(value); - if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); - if (res == null) { - Log.wtf(TAG, "startInputOrWindowGainedFocus must not return" - + " null. startInputReason=" - + InputMethodDebug.startInputReasonToString(startInputReason) - + " editorInfo=" + tba - + " startInputFlags=" - + InputMethodDebug.startInputFlagsToString(startInputFlags)); - return false; - } - mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix(); - mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker; - if (res.id != null) { - setInputChannelLocked(res.channel); - mBindSequence = res.sequence; - mCurMethod = res.method; // for @UnsupportedAppUsage - mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method); - mCurId = res.id; - } else if (res.channel != null && res.channel != mCurChannel) { - res.channel.dispose(); - } - switch (res.result) { - case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW: - mRestartOnNextWindowFocus = true; - break; - } - if (mCurrentInputMethodSession != null && mCompletions != null) { - mCurrentInputMethodSession.displayCompletions(mCompletions); - } } catch (RemoteException e) { - Log.w(TAG, "IME died: " + mCurId, e); + throw e.rethrowFromSystemServer(); + } + if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); + if (res == null) { + Log.wtf(TAG, "startInputOrWindowGainedFocus must not return" + + " null. startInputReason=" + + InputMethodDebug.startInputReasonToString(startInputReason) + + " editorInfo=" + tba + + " startInputFlags=" + + InputMethodDebug.startInputFlagsToString(startInputFlags)); + return false; + } + mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix(); + mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker; + if (res.id != null) { + setInputChannelLocked(res.channel); + mBindSequence = res.sequence; + mCurMethod = res.method; // for @UnsupportedAppUsage + mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method); + mCurId = res.id; + } else if (res.channel != null && res.channel != mCurChannel) { + res.channel.dispose(); + } + switch (res.result) { + case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW: + mRestartOnNextWindowFocus = true; + break; + } + if (mCurrentInputMethodSession != null && mCompletions != null) { + mCurrentInputMethodSession.displayCompletions(mCompletions); } } diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index dbc32e9cf0ca..9c53f461eb95 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -46,4 +46,5 @@ oneway interface ITranslationManager { void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId); void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId); + void getServiceSettingsActivity(in IResultReceiver result, int userId); } diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java index 66b45f3e41e0..dfa70951059a 100644 --- a/core/java/android/view/translation/TranslationManager.java +++ b/core/java/android/view/translation/TranslationManager.java @@ -35,6 +35,7 @@ import android.util.Pair; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.SyncResultReceiver; import java.util.ArrayList; import java.util.Collections; @@ -263,6 +264,31 @@ public final class TranslationManager { //TODO: Add method to propagate updates to mTCapabilityUpdateListeners + /** + * Returns an immutable PendingIntent which can used by apps to launch translation settings. + * + * @return An immutable PendingIntent or {@code null} if one of reason met: + * <ul> + * <li>Device manufacturer (OEM) does not provide TranslationService.</li> + * <li>The TranslationService doesn't provide the Settings.</li> + * </ul> + **/ + @Nullable + public PendingIntent getTranslationSettingsActivityIntent() { + final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); + try { + mService.getServiceSettingsActivity(resultReceiver, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + try { + return resultReceiver.getParcelableResult(); + } catch (SyncResultReceiver.TimeoutException e) { + Log.e(TAG, "Fail to get translation service settings activity."); + return null; + } + } + void removeTranslator(int id) { synchronized (mLock) { mTranslators.remove(id); 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/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 34ad659d148c..1951194816bd 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -130,11 +130,11 @@ public class EdgeEffect { public @interface EdgeEffectType { } - private static final float LINEAR_STRETCH_INTENSITY = 0.03f; + private static final float LINEAR_STRETCH_INTENSITY = 0.06f; - private static final float EXP_STRETCH_INTENSITY = 0.02f; + private static final float EXP_STRETCH_INTENSITY = 0.06f; - private static final float SCROLL_DIST_AFFECTED_BY_EXP_STRETCH = 0.4f; + private static final float SCROLL_DIST_AFFECTED_BY_EXP_STRETCH = 0.33f; @SuppressWarnings("UnusedDeclaration") private static final String TAG = "EdgeEffect"; diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 616a0d0e4148..9789d70c09c4 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -211,14 +211,14 @@ public final class SplashScreenView extends FrameLayout { view.mParceledIconBitmap = mParceledIconBitmap; } // branding image - if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) { + if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0 && mBrandingDrawable != null) { final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams(); params.width = mBrandingImageWidth; params.height = mBrandingImageHeight; view.mBrandingImageView.setLayoutParams(params); - } - if (mBrandingDrawable != null) { view.mBrandingImageView.setBackground(mBrandingDrawable); + } else { + view.mBrandingImageView.setVisibility(GONE); } if (mParceledBrandingBitmap != null) { view.mParceledBrandingBitmap = mParceledBrandingBitmap; 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..3b6a877907f2 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,44 +209,31 @@ 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); } } @Override protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { - if (mIsSendAction) { - showEmptyState(listAdapter, - R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available_share, - /* subtitleRes */ 0); - } else { - showEmptyState(listAdapter, - R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available_resolve, - /* subtitleRes */ 0); - } + showEmptyState(listAdapter, + R.drawable.ic_no_apps, + R.string.resolver_no_personal_apps_available, + /* subtitleRes */ 0); + } @Override protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) { - if (mIsSendAction) { - showEmptyState(listAdapter, - R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available_share, - /* subtitleRes */ 0); - } else { - showEmptyState(listAdapter, - R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available_resolve, - /* subtitleRes */ 0); - } + showEmptyState(listAdapter, + R.drawable.ic_no_apps, + R.string.resolver_no_work_apps_available, + /* subtitleRes */ 0); } void setEmptyStateBottomOffset(int bottomOffset) { diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl index 65bd8415475e..23314e7be622 100644 --- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl +++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl @@ -17,6 +17,7 @@ package com.android.internal.app; import android.hardware.soundtrigger.SoundTrigger; +import android.service.voice.HotwordRejectedResult; /** * @hide @@ -41,11 +42,13 @@ oneway interface IHotwordRecognitionStatusCallback { void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent); /** - * Called when the validated result is invalid. + * Called when the {@link HotwordDetectionService second stage detection} did not detect the + * keyphrase. * - * @param reason The reason why the validated result is invalid. + * @param result Info about the second stage detection result, provided by the + * {@link HotwordDetectionService}. */ - void onRejected(int reason); + void onRejected(in HotwordRejectedResult result); /** * Called when the detection fails due to an error. diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 592f7c7e1925..e2732867d195 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Intent; import android.media.permission.Identity; import android.os.Bundle; +import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.SharedMemory; @@ -228,14 +229,14 @@ interface IVoiceInteractionManagerService { /** * Set configuration and pass read-only data to hotword detection service. * - * @param options Application configuration data provided by the - * {@link VoiceInteractionService}. The system strips out any remotable objects or other - * contents that can be used to communicate with other processes. - * @param sharedMemory The unrestricted data blob provided by the - * {@link VoiceInteractionService}. Use this to provide the hotword models data or other + * @param options Application configuration data to provide to the + * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or + * other contents that can be used to communicate with other processes. + * @param sharedMemory The unrestricted data blob to provide to the + * {@link HotwordDetectionService}. Use this to provide the hotword models data or other * such data to the trusted process. */ - void setHotwordDetectionServiceConfig(in Bundle options, in SharedMemory sharedMemory); + void updateState(in PersistableBundle options, in SharedMemory sharedMemory); /** * Requests to shutdown hotword detection service. diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index 2464fc737a1f..622f1668d052 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); } @@ -221,7 +221,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available_resolve, + R.string.resolver_no_personal_apps_available, /* subtitleRes */ 0); } @@ -229,7 +229,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available_resolve, + R.string.resolver_no_work_apps_available, /* subtitleRes */ 0); } diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java index 762297d15e6d..86f29a8f07ef 100644 --- a/core/java/com/android/internal/app/SuspendedAppActivity.java +++ b/core/java/com/android/internal/app/SuspendedAppActivity.java @@ -106,13 +106,17 @@ public class SuspendedAppActivity extends AlertActivity } private String resolveTitle() { - final int titleId = (mSuppliedDialogInfo != null) ? mSuppliedDialogInfo.getTitleResId() - : ID_NULL; - if (titleId != ID_NULL && mSuspendingAppResources != null) { - try { - return mSuspendingAppResources.getString(titleId); - } catch (Resources.NotFoundException nfe) { - Slog.e(TAG, "Could not resolve string resource id " + titleId); + if (mSuppliedDialogInfo != null) { + final int titleId = mSuppliedDialogInfo.getTitleResId(); + final String title = mSuppliedDialogInfo.getTitle(); + if (titleId != ID_NULL && mSuspendingAppResources != null) { + try { + return mSuspendingAppResources.getString(titleId); + } catch (Resources.NotFoundException nfe) { + Slog.e(TAG, "Could not resolve string resource id " + titleId); + } + } else if (title != null) { + return title; } } return getString(R.string.app_suspended_title); @@ -159,13 +163,17 @@ public class SuspendedAppActivity extends AlertActivity Slog.w(TAG, "Unknown neutral button action: " + mNeutralButtonAction); return null; } - final int buttonTextId = (mSuppliedDialogInfo != null) - ? mSuppliedDialogInfo.getNeutralButtonTextResId() : ID_NULL; - if (buttonTextId != ID_NULL && mSuspendingAppResources != null) { - try { - return mSuspendingAppResources.getString(buttonTextId); - } catch (Resources.NotFoundException nfe) { - Slog.e(TAG, "Could not resolve string resource id " + buttonTextId); + if (mSuppliedDialogInfo != null) { + final int buttonTextId = mSuppliedDialogInfo.getNeutralButtonTextResId(); + final String buttonText = mSuppliedDialogInfo.getNeutralButtonText(); + if (buttonTextId != ID_NULL && mSuspendingAppResources != null) { + try { + return mSuspendingAppResources.getString(buttonTextId); + } catch (Resources.NotFoundException nfe) { + Slog.e(TAG, "Could not resolve string resource id " + buttonTextId); + } + } else if (buttonText != null) { + return buttonText; } } return getString(defaultButtonTextId); diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index a5b894dff46d..c7a36eeaa5f1 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -374,11 +374,6 @@ public final class SystemUiDeviceConfigFlags { */ public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow"; - /** - * (boolean) Whether scrolling screenshots are enabled. - */ - public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling"; - // Flags related to Nav Bar /** diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index fae58622d91e..6776c27fc16e 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -20,6 +20,7 @@ package com.android.internal.display; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -28,6 +29,7 @@ import android.os.PowerManager; import android.os.UserHandle; import android.provider.Settings; import android.util.MathUtils; +import android.view.Display; import java.util.LinkedList; import java.util.Queue; @@ -52,6 +54,7 @@ public class BrightnessSynchronizer { // This value is approximately 1/3 of the smallest possible brightness value. public static final float EPSILON = 0.001f; + private DisplayManager mDisplayManager; private final Context mContext; private final Queue<Object> mWriteHistory = new LinkedList<>(); @@ -87,11 +90,15 @@ public class BrightnessSynchronizer { * value, if float is invalid. If both are invalid, use default float value from config. */ public void startSynchronizing() { + if (mDisplayManager == null) { + mDisplayManager = mContext.getSystemService(DisplayManager.class); + } + final BrightnessSyncObserver brightnessSyncObserver; brightnessSyncObserver = new BrightnessSyncObserver(mHandler); brightnessSyncObserver.startObserving(); - final float currentFloatBrightness = getScreenBrightnessFloat(mContext); + final float currentFloatBrightness = getScreenBrightnessFloat(); final int currentIntBrightness = getScreenBrightnessInt(mContext); if (!Float.isNaN(currentFloatBrightness)) { @@ -101,9 +108,7 @@ public class BrightnessSynchronizer { } else { final float defaultBrightness = mContext.getResources().getFloat( com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat); - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, defaultBrightness, - UserHandle.USER_CURRENT); + mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, defaultBrightness); } } @@ -135,7 +140,7 @@ public class BrightnessSynchronizer { /** * Translates specified value from the float brightness system to the int brightness system, * given the min/max of each range. Accounts for special values such as OFF and invalid values. - * Value returned as a float privimite (to preserve precision), but is a value within the + * Value returned as a float primitive (to preserve precision), but is a value within the * int-system range. */ public static float brightnessFloatToIntRange(float brightnessFloat) { @@ -152,10 +157,8 @@ public class BrightnessSynchronizer { } } - private static float getScreenBrightnessFloat(Context context) { - return Settings.System.getFloatForUser(context.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT, - UserHandle.USER_CURRENT); + private float getScreenBrightnessFloat() { + return mDisplayManager.getBrightness(Display.DEFAULT_DISPLAY); } private static int getScreenBrightnessInt(Context context) { @@ -184,9 +187,7 @@ public class BrightnessSynchronizer { float newBrightnessFloat = brightnessIntToFloat(value); mWriteHistory.offer(newBrightnessFloat); mPreferredSettingValue = newBrightnessFloat; - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, newBrightnessFloat, - UserHandle.USER_CURRENT); + mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat); } } @@ -255,7 +256,7 @@ public class BrightnessSynchronizer { mHandler.removeMessages(MSG_UPDATE_FLOAT); mHandler.obtainMessage(MSG_UPDATE_FLOAT, currentBrightness, 0).sendToTarget(); } else if (BRIGHTNESS_FLOAT_URI.equals(uri)) { - float currentFloat = getScreenBrightnessFloat(mContext); + float currentFloat = getScreenBrightnessFloat(); int toSend = Float.floatToIntBits(currentFloat); mHandler.removeMessages(MSG_UPDATE_INT); mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget(); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index a043756ca262..b0920b0100da 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -170,7 +170,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 195; + static final int VERSION = 196; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -1014,6 +1014,9 @@ public class BatteryStatsImpl extends BatteryStats { @Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null; /** Cpu Power calculator for attributing measured cpu charge consumption to uids */ @Nullable CpuPowerCalculator mCpuPowerCalculator = null; + /** Mobile Radio Power calculator for attributing measured radio charge consumption to uids */ + @Nullable + MobileRadioPowerCalculator mMobileRadioPowerCalculator = null; /** Wifi Power calculator for attributing measured wifi charge consumption to uids */ @Nullable WifiPowerCalculator mWifiPowerCalculator = null; @@ -1195,6 +1198,7 @@ public class BatteryStatsImpl extends BatteryStats { public BatteryStatsImpl(Clocks clocks) { init(clocks); + mStartClockTimeMs = System.currentTimeMillis(); mStatsFile = null; mCheckinFile = null; mDailyFile = null; @@ -6982,6 +6986,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getGnssMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS); + } + + @Override + public long getMobileRadioMeasuredBatteryConsumptionUC() { + return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO); + } + + @Override public long getScreenOnMeasuredBatteryConsumptionUC() { return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); } @@ -7841,6 +7855,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getGnssMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS); + } + + @Override + public long getMobileRadioMeasuredBatteryConsumptionUC() { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO); + } + + @Override public long getScreenOnMeasuredBatteryConsumptionUC() { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON); } @@ -7879,6 +7903,27 @@ public class BatteryStatsImpl extends BatteryStats { return (topTimeUs < fgTimeUs) ? topTimeUs : fgTimeUs; } + + /** + * Gets the uid's time spent using the GNSS since last marked. Also sets the mark time for + * the GNSS timer. + */ + private long markGnssTimeUs(long elapsedRealtimeMs) { + final Sensor sensor = mSensorStats.get(Sensor.GPS); + if (sensor == null) { + return 0; + } + + final StopwatchTimer timer = sensor.mTimer; + if (timer == null) { + return 0; + } + + final long gnssTimeUs = timer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000); + timer.setMark(elapsedRealtimeMs); + return gnssTimeUs; + } + public StopwatchTimer createAudioTurnedOnTimerLocked() { if (mAudioTurnedOnTimer == null) { mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClocks, Uid.this, AUDIO_TURNED_ON, @@ -11822,7 +11867,7 @@ public class BatteryStatsImpl extends BatteryStats { * Distribute Cell radio energy info and network traffic to apps. */ public void noteModemControllerActivity(@Nullable final ModemActivityInfo activityInfo, - long elapsedRealtimeMs, long uptimeMs) { + final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) { if (DEBUG_ENERGY) { Slog.d(TAG, "Updating mobile radio stats with " + activityInfo); } @@ -11853,6 +11898,16 @@ public class BatteryStatsImpl extends BatteryStats { return; } + final SparseDoubleArray uidEstimatedConsumptionMah; + if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null + && mGlobalMeasuredEnergyStats != null) { + mGlobalMeasuredEnergyStats.updateStandardBucket( + MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC); + uidEstimatedConsumptionMah = new SparseDoubleArray(); + } else { + uidEstimatedConsumptionMah = null; + } + if (deltaInfo != null) { mHasModemReporting = true; mModemActivity.getIdleTimeCounter().addCountLocked( @@ -11897,7 +11952,7 @@ public class BatteryStatsImpl extends BatteryStats { mTmpRailStats.resetCellularTotalEnergyUsed(); } } - long radioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( + long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000); mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs); @@ -11957,12 +12012,21 @@ public class BatteryStatsImpl extends BatteryStats { // Distribute total radio active time in to this app. final long appPackets = entry.rxPackets + entry.txPackets; - final long appRadioTimeUs = (radioTimeUs * appPackets) / totalPackets; + final long appRadioTimeUs = + (totalAppRadioTimeUs * appPackets) / totalPackets; u.noteMobileRadioActiveTimeLocked(appRadioTimeUs); + // Distribute measured mobile radio charge consumption based on app radio + // active time + if (uidEstimatedConsumptionMah != null) { + uidEstimatedConsumptionMah.add(u.getUid(), + mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( + appRadioTimeUs / 1000)); + } + // Remove this app from the totals, so that we don't lose any time // due to rounding. - radioTimeUs -= appRadioTimeUs; + totalAppRadioTimeUs -= appRadioTimeUs; totalPackets -= appPackets; if (deltaInfo != null) { @@ -11987,12 +12051,51 @@ public class BatteryStatsImpl extends BatteryStats { } } - if (radioTimeUs > 0) { + if (totalAppRadioTimeUs > 0) { // Whoops, there is some radio time we can't blame on an app! - mMobileRadioActiveUnknownTime.addCountLocked(radioTimeUs); + mMobileRadioActiveUnknownTime.addCountLocked(totalAppRadioTimeUs); mMobileRadioActiveUnknownCount.addCountLocked(1); } + + // Update the MeasuredEnergyStats information. + if (uidEstimatedConsumptionMah != null) { + double totalEstimatedConsumptionMah = 0.0; + + // Estimate total active radio power consumption since last mark. + final long totalRadioTimeMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + mMobileRadioActiveTimer.setMark(elapsedRealtimeMs); + totalEstimatedConsumptionMah += + mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah( + totalRadioTimeMs); + + // Estimate idle power consumption at each signal strength level + final int numSignalStrengthLevels = mPhoneSignalStrengthsTimer.length; + for (int strengthLevel = 0; strengthLevel < numSignalStrengthLevels; + strengthLevel++) { + final long strengthLevelDurationMs = + mPhoneSignalStrengthsTimer[strengthLevel].getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + mPhoneSignalStrengthsTimer[strengthLevel].setMark(elapsedRealtimeMs); + + totalEstimatedConsumptionMah += + mMobileRadioPowerCalculator.calcIdlePowerAtSignalStrengthMah( + strengthLevelDurationMs, strengthLevel); + } + + // Estimate total active radio power consumption since last mark. + final long scanTimeMs = mPhoneSignalScanningTimer.getTimeSinceMarkLocked( + elapsedRealtimeMs * 1000) / 1000; + mPhoneSignalScanningTimer.setMark(elapsedRealtimeMs); + totalEstimatedConsumptionMah += + mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs); + + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, + consumedChargeUC, uidEstimatedConsumptionMah, + totalEstimatedConsumptionMah); + } + mNetworkStatsPool.release(delta); delta = null; } @@ -12452,7 +12555,7 @@ public class BatteryStatsImpl extends BatteryStats { // 'double counted' and will simply exceed the realtime that elapsed. // If multidisplay becomes a reality, this is probably more reasonable than pooling. - // On the first pass, collect total time since mark so that we can normalize power. + // Collect total time since mark so that we can normalize power. final SparseDoubleArray fgTimeUsArray = new SparseDoubleArray(); final long elapsedRealtimeUs = elapsedRealtimeMs * 1000; // TODO(b/175726779): Update and optimize the algorithm (e.g. avoid iterating over ALL uids) @@ -12467,6 +12570,50 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * Accumulate GNSS charge consumption and distribute it to the correct state and the apps. + * + * @param chargeUC amount of charge (microcoulombs) used by GNSS since this was last called. + */ + @GuardedBy("this") + public void updateGnssMeasuredEnergyStatsLocked(long chargeUC, long elapsedRealtimeMs) { + if (DEBUG_ENERGY) Slog.d(TAG, "Updating gnss stats: " + chargeUC); + if (mGlobalMeasuredEnergyStats == null) { + return; + } + + if (!mOnBatteryInternal || chargeUC <= 0) { + // There's nothing further to update. + return; + } + if (mIgnoreNextExternalStats) { + // Although under ordinary resets we won't get here, and typically a new sync will + // happen right after the reset, strictly speaking we need to set all mark times to now. + final int uidStatsSize = mUidStats.size(); + for (int i = 0; i < uidStatsSize; i++) { + final Uid uid = mUidStats.valueAt(i); + uid.markGnssTimeUs(elapsedRealtimeMs); + } + return; + } + + mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_GNSS, + chargeUC); + + // Collect the per uid time since mark so that we can normalize power. + final SparseDoubleArray gnssTimeUsArray = new SparseDoubleArray(); + // TODO(b/175726779): Update and optimize the algorithm (e.g. avoid iterating over ALL uids) + final int uidStatsSize = mUidStats.size(); + for (int i = 0; i < uidStatsSize; i++) { + final Uid uid = mUidStats.valueAt(i); + final long gnssTimeUs = uid.markGnssTimeUs(elapsedRealtimeMs); + if (gnssTimeUs == 0) continue; + gnssTimeUsArray.put(uid.getUid(), (double) gnssTimeUs); + } + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_GNSS, chargeUC, + gnssTimeUsArray, 0); + } + + /** * Accumulate Custom power bucket charge, globally and for each app. * * @param totalChargeUC charge (microcoulombs) used for this bucket since this was last called. @@ -14393,6 +14540,9 @@ public class BatteryStatsImpl extends BatteryStats { if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU]) { mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile); } + if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO]) { + mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile); + } if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_WIFI]) { mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile); } @@ -16524,6 +16674,8 @@ public class BatteryStatsImpl extends BatteryStats { // Pull the clock time. This may update the time and make a new history entry // if we had originally pulled a time before the RTC was set. getStartClockTime(); + + updateSystemServiceCallStats(); } public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 6dd612e4c9bf..f8ae0c1858f8 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -21,9 +21,7 @@ import android.hardware.SensorManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.Bundle; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; @@ -88,29 +86,31 @@ public class BatteryUsageStatsProvider { } /** - * Returns snapshots of battery attribution data, one per supplied query. + * Returns true if the last update was too long ago for the tolerances specified + * by the supplied queries. */ - public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { - - // TODO(b/174186345): instead of BatteryStatsHelper, use PowerCalculators directly. - final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext, - false /* collectBatteryBroadcast */); - batteryStatsHelper.create((Bundle) null); - final List<UserHandle> users = new ArrayList<>(); - for (int i = 0; i < queries.size(); i++) { + public boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries, + long lastUpdateTimeStampMs) { + long allowableStatsAge = Long.MAX_VALUE; + for (int i = queries.size() - 1; i >= 0; i--) { BatteryUsageStatsQuery query = queries.get(i); - for (int userId : query.getUserIds()) { - UserHandle userHandle = UserHandle.of(userId); - if (!users.contains(userHandle)) { - users.add(userHandle); - } - } + allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge()); } - batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users); + return mStats.mClocks.elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge; + } + + /** + * Returns snapshots of battery attribution data, one per supplied query. + */ + public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size()); - for (int i = 0; i < queries.size(); i++) { - results.add(getBatteryUsageStats(queries.get(i))); + synchronized (mStats) { + mStats.prepareForDumpLocked(); + + for (int i = 0; i < queries.size(); i++) { + results.add(getBatteryUsageStats(queries.get(i))); + } } return results; } @@ -134,7 +134,7 @@ public class BatteryUsageStatsProvider { final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(customPowerComponentCount, customTimeComponentCount) - .setStatsStartRealtime(mStats.getStatsStartRealtime() / 1000); + .setStatsStartTimestamp(mStats.getStartClockTime()); SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats(); for (int i = uidStats.size() - 1; i >= 0; i--) { diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java index df25cdaee17f..97c4fd8b7b7a 100644 --- a/core/java/com/android/internal/os/GnssPowerCalculator.java +++ b/core/java/com/android/internal/os/GnssPowerCalculator.java @@ -61,7 +61,17 @@ public class GnssPowerCalculator extends PowerCalculator { long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query, double averageGnssPowerMa) { final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - double powerMah = computePower(durationMs, averageGnssPowerMa); + + final long measuredChargeUC = u.getGnssMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = !query.shouldForceUsePowerProfileModel() + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + + final double powerMah; + if (isMeasuredPowerAvailable) { + powerMah = uCtoMah(measuredChargeUC); + } else { + powerMah = computePower(durationMs, averageGnssPowerMa); + } app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS, durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah); } @@ -73,15 +83,25 @@ public class GnssPowerCalculator extends PowerCalculator { for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, rawRealtimeUs, statsType, averageGnssPowerMa); + calculateApp(app, app.uidObj, rawRealtimeUs, statsType, averageGnssPowerMa, false); } } } protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - int statsType, double averageGnssPowerMa) { + int statsType, double averageGnssPowerMa, boolean shouldForceUsePowerProfileModel) { final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - double powerMah = computePower(durationMs, averageGnssPowerMa); + + final long measuredChargeUC = u.getGnssMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = shouldForceUsePowerProfileModel + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + + final double powerMah; + if (isMeasuredPowerAvailable) { + powerMah = uCtoMah(measuredChargeUC); + } else { + powerMah = computePower(durationMs, averageGnssPowerMa); + } app.gpsTimeMs = durationMs; app.gpsPowerMah = powerMah; diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java index 22001d4ffbb7..498e1f2931a0 100644 --- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java +++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java @@ -96,10 +96,12 @@ public class MobileRadioPowerCalculator extends PowerCalculator { for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); final BatteryStats.Uid uid = app.getBatteryStatsUid(); - calculateApp(app, uid, powerPerPacketMah, total); + calculateApp(app, uid, powerPerPacketMah, total, + query.shouldForceUsePowerProfileModel()); } - calculateRemaining(total, batteryStats, rawRealtimeUs); + calculateRemaining(total, batteryStats, rawRealtimeUs, + query.shouldForceUsePowerProfileModel()); if (total.powerMah != 0) { builder.getOrCreateSystemBatteryConsumerBuilder( @@ -111,11 +113,13 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, - double powerPerPacketMah, PowerAndDuration total) { + double powerPerPacketMah, PowerAndDuration total, + boolean shouldForceUsePowerProfileModel) { final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED); total.totalAppDurationMs += radioActiveDurationMs; - final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs); + final double powerMah = calculatePower(u, powerPerPacketMah, radioActiveDurationMs, + shouldForceUsePowerProfileModel); app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MOBILE_RADIO, radioActiveDurationMs) @@ -132,12 +136,12 @@ public class MobileRadioPowerCalculator extends PowerCalculator { final BatterySipper app = sippers.get(i); if (app.drainType == BatterySipper.DrainType.APP) { final BatteryStats.Uid u = app.uidObj; - calculateApp(app, u, statsType, mobilePowerPerPacket, total); + calculateApp(app, u, statsType, mobilePowerPerPacket, total, false); } } BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0); - calculateRemaining(total, batteryStats, rawRealtimeUs); + calculateRemaining(total, batteryStats, rawRealtimeUs, false); if (total.powerMah != 0) { if (total.signalDurationMs != 0) { radio.noCoveragePercent = @@ -154,9 +158,12 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, - double powerPerPacketMah, PowerAndDuration total) { + double powerPerPacketMah, PowerAndDuration total, + boolean shouldForceUsePowerProfileModel) { app.mobileActive = calculateDuration(u, statsType); - app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive); + + app.mobileRadioPowerMah = calculatePower(u, powerPerPacketMah, app.mobileActive, + shouldForceUsePowerProfileModel); total.totalAppDurationMs += app.mobileActive; // Add cost of mobile traffic. @@ -183,11 +190,19 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private double calculatePower(BatteryStats.Uid u, double powerPerPacketMah, - long radioActiveDurationMs) { + long radioActiveDurationMs, boolean shouldForceUsePowerProfileModel) { + + final long measuredChargeUC = u.getMobileRadioMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = !shouldForceUsePowerProfileModel + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + if (isMeasuredPowerAvailable) { + return uCtoMah(measuredChargeUC); + } + if (radioActiveDurationMs > 0) { // We are tracking when the radio is up, so can use the active time to // determine power use. - return mActivePowerEstimator.calculatePower(radioActiveDurationMs); + return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs); } else { // We are not tracking when the radio is up, so must approximate power use // based on the number of packets. @@ -202,18 +217,29 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } private void calculateRemaining(MobileRadioPowerCalculator.PowerAndDuration total, - BatteryStats batteryStats, long rawRealtimeUs) { + BatteryStats batteryStats, long rawRealtimeUs, + boolean shouldForceUsePowerProfileModel) { long signalTimeMs = 0; double powerMah = 0; + + final long measuredChargeUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC(); + final boolean isMeasuredPowerAvailable = !shouldForceUsePowerProfileModel + && measuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE; + if (isMeasuredPowerAvailable) { + powerMah = uCtoMah(measuredChargeUC); + } + for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) { long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; - final double p = mIdlePowerEstimators[i].calculatePower(strengthTimeMs); - if (DEBUG && p != 0) { - Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" - + formatCharge(p)); + if (!isMeasuredPowerAvailable) { + final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" + + formatCharge(p)); + } + powerMah += p; } - powerMah += p; signalTimeMs += strengthTimeMs; if (i == 0) { total.noCoverageDurationMs = strengthTimeMs; @@ -222,16 +248,21 @@ public class MobileRadioPowerCalculator extends PowerCalculator { final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; - final double p = mScanPowerEstimator.calculatePower(scanningTimeMs); - if (DEBUG && p != 0) { - Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(p)); - } - powerMah += p; long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs; - if (remainingActiveTimeMs > 0) { - powerMah += mActivePowerEstimator.calculatePower(remainingActiveTimeMs); + + if (!isMeasuredPowerAvailable) { + final double p = calcScanTimePowerMah(scanningTimeMs); + if (DEBUG && p != 0) { + Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge( + p)); + } + powerMah += p; + + if (remainingActiveTimeMs > 0) { + powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs); + } } total.durationMs = radioActiveTimeMs; total.powerMah = powerMah; @@ -239,12 +270,35 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } /** + * Calculates active radio power consumption (in milliamp-hours) from active radio duration. + */ + public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) { + return mActivePowerEstimator.calculatePower(radioActiveDurationMs); + } + + /** + * Calculates idle radio power consumption (in milliamp-hours) for time spent at a cell signal + * strength level. + * see {@link CellSignalStrength#getNumSignalStrengthLevels()} + */ + public double calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel) { + return mIdlePowerEstimators[strengthLevel].calculatePower(strengthTimeMs); + } + + /** + * Calculates radio scan power consumption (in milliamp-hours) from scan time. + */ + public double calcScanTimePowerMah(long scanningTimeMs) { + return mScanPowerEstimator.calculatePower(scanningTimeMs); + } + + /** * Return estimated power (in mAh) of sending or receiving a packet with the mobile radio. */ private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) { final long radioDataUptimeMs = stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; - final double mobilePower = mActivePowerEstimator.calculatePower(radioDataUptimeMs); + final double mobilePower = calcPowerFromRadioActiveDurationMah(radioDataUptimeMs); final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, statsType); diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 39bde742e828..611fe29f4fcf 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -2508,6 +2509,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mDecor.mForceWindowDrawsBarBackgrounds) { params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; } + params.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; } if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { decor.setSystemUiVisibility( diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index 845b3e501c08..00a0627d068c 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -54,7 +54,9 @@ public class MeasuredEnergyStats { public static final int POWER_BUCKET_CPU = 3; public static final int POWER_BUCKET_WIFI = 4; public static final int POWER_BUCKET_BLUETOOTH = 5; - public static final int NUMBER_STANDARD_POWER_BUCKETS = 6; // Buckets above this are custom. + public static final int POWER_BUCKET_GNSS = 6; + public static final int POWER_BUCKET_MOBILE_RADIO = 7; + public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom. @IntDef(prefix = {"POWER_BUCKET_"}, value = { POWER_BUCKET_UNKNOWN, @@ -64,6 +66,8 @@ public class MeasuredEnergyStats { POWER_BUCKET_CPU, POWER_BUCKET_WIFI, POWER_BUCKET_BLUETOOTH, + POWER_BUCKET_GNSS, + POWER_BUCKET_MOBILE_RADIO, }) @Retention(RetentionPolicy.SOURCE) public @interface StandardPowerBucket { diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 52d21a858d4f..10927b9e566e 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -18,28 +18,28 @@ //#define LOG_NDEBUG 0 -#include <nativehelper/JNIHelp.h> - #include <android_runtime/AndroidRuntime.h> +#include <input/InputTransport.h> #include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include <utils/Looper.h> -#include <input/InputTransport.h> #include "android_os_MessageQueue.h" #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" +#include "core_jni_helpers.h" -#include <nativehelper/ScopedLocalRef.h> +#include <inttypes.h> #include <unordered_map> -#include "core_jni_helpers.h" using android::base::Result; namespace android { // Log debug messages about the dispatch cycle. -static const bool kDebugDispatchCycle = false; +static constexpr bool kDebugDispatchCycle = false; static struct { jclass clazz; @@ -74,8 +74,10 @@ private: return mInputPublisher.getChannel()->getName(); } - virtual int handleEvent(int receiveFd, int events, void* data); + int handleEvent(int receiveFd, int events, void* data) override; status_t receiveFinishedSignals(JNIEnv* env); + bool notifyFinishedSignal(JNIEnv* env, jobject sender, const InputPublisher::Finished& finished, + bool skipCallbacks); }; NativeInputEventSender::NativeInputEventSender(JNIEnv* env, jobject senderWeak, @@ -196,8 +198,13 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName().c_str()); } - ScopedLocalRef<jobject> senderObj(env, NULL); - bool skipCallbacks = false; + ScopedLocalRef<jobject> senderObj(env, jniGetReferent(env, mSenderWeakGlobal)); + if (!senderObj.get()) { + ALOGW("channel '%s' ~ Sender object was finalized without being disposed.", + getInputChannelName().c_str()); + return DEAD_OBJECT; + } + bool skipCallbacks = false; // stop calling Java functions after an exception occurs for (;;) { Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal(); if (!result.ok()) { @@ -206,46 +213,56 @@ status_t NativeInputEventSender::receiveFinishedSignals(JNIEnv* env) { return OK; } ALOGE("channel '%s' ~ Failed to consume finished signals. status=%d", - getInputChannelName().c_str(), status); + getInputChannelName().c_str(), status); return status; } - auto it = mPublishedSeqMap.find(result->seq); - if (it == mPublishedSeqMap.end()) { - continue; + const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks); + if (!notified) { + skipCallbacks = true; } + } +} - uint32_t seq = it->second; - mPublishedSeqMap.erase(it); - - if (kDebugDispatchCycle) { - ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", - getInputChannelName().c_str(), seq, result->handled ? "true" : "false", - mPublishedSeqMap.size()); - } +/** + * Invoke the Java function dispatchInputEventFinished for the received "Finished" signal. + * Set the variable 'skipCallbacks' to 'true' if a Java exception occurred. + * Java function will only be called if 'skipCallbacks' is originally 'false'. + * + * Return "false" if an exception occurred while calling the Java function + * "true" otherwise + */ +bool NativeInputEventSender::notifyFinishedSignal(JNIEnv* env, jobject sender, + const InputPublisher::Finished& finished, + bool skipCallbacks) { + auto it = mPublishedSeqMap.find(finished.seq); + if (it == mPublishedSeqMap.end()) { + ALOGW("Received 'finished' signal for unknown seq number = %" PRIu32, finished.seq); + // Since this is coming from the receiver (typically app), it's possible that an app + // does something wrong and sends bad data. Just ignore and process other events. + return true; + } + const uint32_t seq = it->second; + mPublishedSeqMap.erase(it); - if (!skipCallbacks) { - if (!senderObj.get()) { - senderObj.reset(jniGetReferent(env, mSenderWeakGlobal)); - if (!senderObj.get()) { - ALOGW("channel '%s' ~ Sender object was finalized without being disposed.", - getInputChannelName().c_str()); - return DEAD_OBJECT; - } - } + if (kDebugDispatchCycle) { + ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", + getInputChannelName().c_str(), seq, finished.handled ? "true" : "false", + mPublishedSeqMap.size()); + } + if (skipCallbacks) { + return true; + } - env->CallVoidMethod(senderObj.get(), - gInputEventSenderClassInfo.dispatchInputEventFinished, - static_cast<jint>(seq), static_cast<jboolean>(result->handled)); - if (env->ExceptionCheck()) { - ALOGE("Exception dispatching finished signal."); - skipCallbacks = true; - } - } + env->CallVoidMethod(sender, gInputEventSenderClassInfo.dispatchInputEventFinished, + static_cast<jint>(seq), static_cast<jboolean>(finished.handled)); + if (env->ExceptionCheck()) { + ALOGE("Exception dispatching finished signal for seq=%" PRIu32, seq); + return false; } + return true; } - static jlong nativeInit(JNIEnv* env, jclass clazz, jobject senderWeak, jobject inputChannelObj, jobject messageQueueObj) { std::shared_ptr<InputChannel> inputChannel = diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index f54ffc50095b..419dc6e53744 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1658,10 +1658,12 @@ public: jobject jJankData = env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor, jankData[i].frameVsyncId, jankData[i].jankType); env->SetObjectArrayElement(jJankDataArray, i, jJankData); + env->DeleteLocalRef(jJankData); } env->CallVoidMethod(target, gJankDataListenerClassInfo.onJankDataAvailable, jJankDataArray); + env->DeleteLocalRef(jJankDataArray); env->DeleteLocalRef(target); } diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index dca6002d23f8..530cb44d3aae 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -123,6 +123,8 @@ message SecureSettingsProto { optional SettingProto gesture_silence_alerts_enabled = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gesture_wake_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto gesture_setup_complete = 9 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Assist assist = 7; @@ -296,6 +298,7 @@ message SecureSettingsProto { optional SettingProto subtype_history = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto selected_input_method_subtype = 6 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto show_ime_with_hard_keyboard = 7 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto default_voice_input_method = 8 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional InputMethods input_methods = 26; diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index 0d23946ebbb8..acb7429df5c3 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -58,6 +58,9 @@ message PowerManagerServiceDumpProto { optional bool is_screen_bright = 1; optional bool is_screen_dim = 2; optional bool is_screen_dream = 3; + optional int64 last_user_activity_time_ms = 4; + optional int64 last_user_activity_time_no_change_lights_ms = 5; + optional int32 display_group_id = 6; } // A com.android.server.power.PowerManagerService.UidState object. message UidStateProto { @@ -109,7 +112,7 @@ message PowerManagerServiceDumpProto { // The time we decided to do next long check. (In milliseconds timestamp) optional int64 notify_long_next_check_ms = 19; // Summarizes the effect of the user activity timer. - optional UserActivityProto user_activity = 20; + repeated UserActivityProto user_activity = 20; // If true, instructs the display controller to wait for the proximity // sensor to go negative before turning the screen on. optional bool is_request_wait_for_negative_proximity = 21; @@ -134,8 +137,8 @@ message PowerManagerServiceDumpProto { // Timestamp of the last time the device was put to sleep. optional int64 last_sleep_time_ms = 30; // Timestamp of the last call to user activity. - optional int64 last_user_activity_time_ms = 31; - optional int64 last_user_activity_time_no_change_lights_ms = 32; + optional int64 last_user_activity_time_ms = 31 [deprecated = true]; + optional int64 last_user_activity_time_no_change_lights_ms = 32 [deprecated = true]; // Timestamp of last interactive power hint. optional int64 last_interactive_power_hint_time_ms = 33; // Timestamp of the last screen brightness boost. diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index aab054f4bf73..7b97524d0510 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -21,40 +21,49 @@ option java_multiple_files = true; import "frameworks/base/core/proto/android/privacy.proto"; -message OneShotProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 duration = 1; - repeated int32 amplitude = 2; +message StepSegmentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 duration = 1; + optional float amplitude = 2; + optional float frequency = 3; } -message WaveformProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 timings = 1; - repeated int32 amplitudes = 2; - required bool repeat = 3; +message RampSegmentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 duration = 1; + optional float startAmplitude = 2; + optional float endAmplitude = 3; + optional float startFrequency = 4; + optional float endFrequency = 5; } -message PrebakedProto { +message PrebakedSegmentProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional int32 effect_id = 1; optional int32 effect_strength = 2; optional int32 fallback = 3; } -message ComposedProto { +message PrimitiveSegmentProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 primitive_id = 1; + optional float scale = 2; + optional int32 delay = 3; +} + +message SegmentProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - repeated int32 effect_ids = 1; - repeated float effect_scales = 2; - repeated int32 delays = 3; + optional PrebakedSegmentProto prebaked = 1; + optional PrimitiveSegmentProto primitive = 2; + optional StepSegmentProto step = 3; + optional RampSegmentProto ramp = 4; } // A com.android.os.VibrationEffect object. message VibrationEffectProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional OneShotProto oneshot = 1; - optional WaveformProto waveform = 2; - optional PrebakedProto prebaked = 3; - optional ComposedProto composed = 4; + optional SegmentProto segments = 1; + required int32 repeat = 2; } message SyncVibrationEffectProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8d7f5425ea62..b3b66c48fe6c 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. @@ -1388,6 +1388,14 @@ android:backgroundPermission="android.permission.BACKGROUND_CAMERA" android:protectionLevel="dangerous|instant" /> + <!-- Required to be able to discover and connect to nearby Bluetooth devices. + <p>Protection level: dangerous --> + <permission-group android:name="android.permission-group.NEARBY_DEVICES" + android:icon="@drawable/ic_qs_bluetooth" + android:label="@string/permgrouplab_nearby_devices" + android:description="@string/permgroupdesc_nearby_devices" + android:priority="750" /> + <!-- @SystemApi @TestApi Required to be able to access the camera device in the background. This permission is not intended to be held by apps. <p>Protection level: internal @@ -1930,6 +1938,22 @@ android:label="@string/permlab_bluetooth" android:protectionLevel="normal" /> + <!-- Required to be able to discover and pair nearby Bluetooth devices. + <p>Protection level: dangerous --> + <permission android:name="android.permission.BLUETOOTH_SCAN" + android:permissionGroup="android.permission-group.UNDEFINED" + android:description="@string/permdesc_bluetooth_scan" + android:label="@string/permlab_bluetooth_scan" + android:protectionLevel="dangerous" /> + + <!-- Required to be able to connect to paired Bluetooth devices. + <p>Protection level: dangerous --> + <permission android:name="android.permission.BLUETOOTH_CONNECT" + android:permissionGroup="android.permission-group.UNDEFINED" + android:description="@string/permdesc_bluetooth_connect" + android:label="@string/permlab_bluetooth_connect" + android:protectionLevel="dangerous" /> + <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the user from using them until they are unsuspended. @hide @@ -1963,6 +1987,12 @@ <permission android:name="android.permission.BLUETOOTH_STACK" android:protectionLevel="signature" /> + <!-- Allows uhid write access for creating virtual input devices + @hide + --> + <permission android:name="android.permission.VIRTUAL_INPUT_DEVICE" + android:protectionLevel="signature" /> + <!-- Allows applications to perform I/O operations over NFC. <p>Protection level: normal --> @@ -2659,6 +2689,11 @@ <permission android:name="android.permission.CREATE_USERS" android:protectionLevel="signature" /> + <!-- @SystemApi @hide Allows an application to access data blobs across users. + This permission is not available to third party applications. --> + <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS" + android:protectionLevel="signature|privileged|development" /> + <!-- @hide Allows an application to set the profile owners and the device owner. This permission is not available to third party applications.--> <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" @@ -2939,6 +2974,15 @@ <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE" android:protectionLevel="signature" /> + <!-- Allows system clock time suggestions from an external clock / time source to be made. + The nature of "external" could be highly form-factor specific. Example, times + obtained via the VHAL for Android Auto OS. + <p>Not for use by third-party applications. + @SystemApi @hide + --> + <permission android:name="android.permission.SUGGEST_EXTERNAL_TIME" + android:protectionLevel="signature|privileged" /> + <!-- Allows applications like settings to manage configuration associated with automatic time and time zone detection. <p>Not for use by third-party applications. @@ -3959,6 +4003,15 @@ <permission android:name="android.permission.SET_KEYBOARD_LAYOUT" android:protectionLevel="signature" /> + <!-- Allows an app to schedule a prioritized alarm that can be used to perform + background work even when the device is in doze. + <p>Not for use by third-party applications. + @hide + @SystemApi + --> + <permission android:name="android.permission.SCHEDULE_PRIORITIZED_ALARM" + android:protectionLevel="signature|privileged"/> + <!-- Allows an app to use exact alarm scheduling APIs to perform timing sensitive background work. --> @@ -4188,6 +4241,11 @@ <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" android:protectionLevel="normal" /> + <!-- Allows an application to create new companion device associations. + @hide --> + <permission android:name="android.permission.ASSOCIATE_COMPANION_DEVICES" + android:protectionLevel="internal|role" /> + <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features. <p>Not for use by third-party applications. @hide @@ -4401,6 +4459,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 --> @@ -4911,6 +4976,11 @@ <permission android:name="android.permission.SET_INITIAL_LOCK" android:protectionLevel="signature|setup"/> + <!-- @TestApi Allows applications to set and verify lockscreen credentials. + @hide --> + <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" + android:protectionLevel="signature"/> + <!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide --> <permission android:name="android.permission.MANAGE_FINGERPRINT" android:protectionLevel="signature|privileged" /> @@ -5373,12 +5443,6 @@ <permission android:name="android.permission.WATCH_APPOPS" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to directly open the "Open by default" page inside a package's - Details screen. - @hide <p>Not for use by third-party applications. --> - <permission android:name="android.permission.OPEN_APP_OPEN_BY_DEFAULT_SETTINGS" - android:protectionLevel="signature" /> - <!-- Allows hidden API checks to be disabled when starting a process. @hide <p>Not for use by third-party applications. --> <permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS" @@ -5508,6 +5572,7 @@ @hide <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" android:protectionLevel="signature|privileged" /> + <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/> <!-- Allows input events to be monitored. Very dangerous! @hide --> <permission android:name="android.permission.MONITOR_INPUT" diff --git a/core/res/res/drawable/bottomsheet_background.xml b/core/res/res/drawable/bottomsheet_background.xml index 3a8ad71187a0..00d53004c9dc 100644 --- a/core/res/res/drawable/bottomsheet_background.xml +++ b/core/res/res/drawable/bottomsheet_background.xml @@ -18,5 +18,5 @@ <corners android:topLeftRadius="@dimen/config_bottomDialogCornerRadius" android:topRightRadius="@dimen/config_bottomDialogCornerRadius"/> - <solid android:color="?attr/colorBackgroundFloating" /> + <solid android:color="?attr/colorBackground" /> </shape> diff --git a/core/res/res/drawable/chooser_action_button_bg.xml b/core/res/res/drawable/chooser_action_button_bg.xml index 0dd9e9c7cd98..18bbd93d1535 100644 --- a/core/res/res/drawable/chooser_action_button_bg.xml +++ b/core/res/res/drawable/chooser_action_button_bg.xml @@ -15,7 +15,7 @@ ~ limitations under the License --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/lighter_gray"> + android:color="?android:attr/colorControlHighlight"> <item> <inset android:insetLeft="0dp" @@ -23,10 +23,8 @@ android:insetRight="0dp" android:insetBottom="8dp"> <shape android:shape="rectangle"> - <corners android:radius="16dp"></corners> - <stroke android:width="1dp" - android:color="?attr/opacityListDivider" /> - <solid android:color="?attr/colorBackgroundFloating" /> + <corners android:radius="16dp" /> + <solid android:color="@color/system_neutral2_100" /> </shape> </inset> </item> diff --git a/core/res/res/layout/chooser_action_button.xml b/core/res/res/layout/chooser_action_button.xml index 6af7937960f0..16ffaa4b90ca 100644 --- a/core/res/res/layout/chooser_action_button.xml +++ b/core/res/res/layout/chooser_action_button.xml @@ -25,6 +25,7 @@ android:singleLine="true" android:clickable="true" android:background="@drawable/chooser_action_button_bg" - android:drawableTint="@color/chooser_chip_icon" + android:drawableTint="?android:textColorPrimary" android:drawableTintMode="src_in" + style="?android:attr/borderlessButtonStyle" /> diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index c0de6936dd12..10683b189fc5 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -67,7 +67,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" @@ -84,7 +84,7 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <FrameLayout android:id="@android:id/tabcontent" diff --git a/core/res/res/layout/chooser_grid_preview_file.xml b/core/res/res/layout/chooser_grid_preview_file.xml index 2a39215a4bd8..d8c1d1782834 100644 --- a/core/res/res/layout/chooser_grid_preview_file.xml +++ b/core/res/res/layout/chooser_grid_preview_file.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="@dimen/chooser_view_spacing" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:layout_width="@dimen/chooser_preview_width" diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml index 62df1650612a..0d04d7f319b8 100644 --- a/core/res/res/layout/chooser_grid_preview_image.xml +++ b/core/res/res/layout/chooser_grid_preview_image.xml @@ -22,14 +22,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <RelativeLayout android:id="@+id/content_preview_image_area" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:paddingBottom="@dimen/chooser_view_spacing" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView" android:id="@+id/content_preview_image_1_large" diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml index 1d18648b9ef7..bc4f327d7a15 100644 --- a/core/res/res/layout/chooser_grid_preview_text.xml +++ b/core/res/res/layout/chooser_grid_preview_text.xml @@ -23,7 +23,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:background="?android:attr/colorBackgroundFloating"> + android:background="?android:attr/colorBackground"> <RelativeLayout android:layout_width="@dimen/chooser_preview_width" diff --git a/core/res/res/layout/chooser_list_per_profile.xml b/core/res/res/layout/chooser_list_per_profile.xml index 86dc71cbbfb8..912173cc725b 100644 --- a/core/res/res/layout/chooser_list_per_profile.xml +++ b/core/res/res/layout/chooser_list_per_profile.xml @@ -23,7 +23,7 @@ android:layoutManager="com.android.internal.app.ChooserGridLayoutManager" android:id="@+id/resolver_list" android:clipToPadding="false" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:scrollbars="none" android:elevation="1dp" android:nestedScrollingEnabled="true" /> diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml index bdcfeb21598a..8594c33c3082 100644 --- a/core/res/res/layout/resolver_empty_states.xml +++ b/core/res/res/layout/resolver_empty_states.xml @@ -84,7 +84,7 @@ <TextView android:id="@+id/empty" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:text="@string/noApplications" android:padding="@dimen/chooser_edge_margin_normal" android:layout_marginBottom="56dp" diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml index 6fde1dfe248e..d791598a3552 100644 --- a/core/res/res/layout/resolver_list.xml +++ b/core/res/res/layout/resolver_list.xml @@ -68,7 +68,7 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <FrameLayout @@ -76,7 +76,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorBackgroundFloating"/> + android:background="?attr/colorBackground"/> <TabHost android:id="@+id/profile_tabhost" @@ -85,7 +85,7 @@ android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:accessibilityTraversalAfter="@id/title" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" @@ -101,7 +101,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical"/> <FrameLayout android:id="@android:id/tabcontent" @@ -120,13 +120,13 @@ android:layout_height="wrap_content" android:layout_alwaysShow="true" android:orientation="vertical" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:layout_ignoreOffset="true"> <View android:id="@+id/resolver_button_bar_divider" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <LinearLayout android:id="@+id/button_bar" diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml index 9410301e40fc..d6ca7abdca71 100644 --- a/core/res/res/layout/resolver_list_per_profile.xml +++ b/core/res/res/layout/resolver_list_per_profile.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:id="@+id/resolver_list" android:clipToPadding="false" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:elevation="@dimen/resolver_elevation" android:nestedScrollingEnabled="true" android:scrollbarStyle="outsideOverlay" diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml index 4a5aa020fb9f..7610e732ed06 100644 --- a/core/res/res/layout/resolver_list_with_default.xml +++ b/core/res/res/layout/resolver_list_with_default.xml @@ -148,7 +148,7 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> <FrameLayout @@ -157,7 +157,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/colorBackgroundFloating"/> + android:background="?attr/colorBackground"/> <TabHost android:layout_alwaysShow="true" @@ -166,7 +166,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" - android:background="?attr/colorBackgroundFloating"> + android:background="?attr/colorBackground"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" @@ -182,7 +182,7 @@ android:visibility="gone" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical"/> <FrameLayout android:id="@android:id/tabcontent" @@ -200,6 +200,6 @@ android:layout_alwaysShow="true" android:layout_width="match_parent" android:layout_height="1dp" - android:background="?attr/colorBackgroundFloating" + android:background="?attr/colorBackground" android:foreground="?attr/dividerVertical" /> </com.android.internal.widget.ResolverDrawerLayout> diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml index 4410e944db39..baffa5a82cdb 100644 --- a/core/res/res/values-night/colors.xml +++ b/core/res/res/values-night/colors.xml @@ -36,7 +36,6 @@ <color name="resolver_empty_state_text">#FFFFFF</color> <color name="resolver_empty_state_icon">#FFFFFF</color> - <color name="chooser_chip_icon">#8AB4F8</color> <!-- Blue 300 --> <color name="personal_apps_suspension_notification_color">#8AB4F8</color> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 5e95f941d14c..4b15e01369be 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1125,6 +1125,15 @@ to framework controls (via colorControlActivated). --> <attr name="colorAccent" format="color" /> + <!-- Light accent color used on Material NEXT buttons. @hide --> + <attr name="colorAccentPrimary" format="color" /> + + <!-- Secondary accent color used on Material NEXT buttons. @hide --> + <attr name="colorAccentSecondary" format="color" /> + + <!-- Tertiary accent color used on Material NEXT buttons. @hide --> + <attr name="colorAccentTertiary" format="color" /> + <!-- The color applied to framework controls in their normal state. --> <attr name="colorControlNormal" format="color" /> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 140163e47600..986bb820eb2c 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1595,6 +1595,20 @@ <enum name="always" value="1" /> </attr> + <!-- Enable hardware memory tagging (ARM MTE) in this process. + When enabled, heap memory bugs like use-after-free and buffer overlow + are detected and result in an immediate ("sync" mode) or delayed ("async" + mode) crash instead of a silent memory corruption. Sync mode, while slower, + provides enhanced bug reports including stack traces at the time of allocation + and deallocation of memory, similar to AddressSanitizer. + + See the <a href="https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety">ARM announcement</a> + for more details. + + <p>This attribute can be applied to a + {@link android.R.styleable#AndroidManifestProcess process} tag, or to an + {@link android.R.styleable#AndroidManifestApplication application} tag (to supply + a default setting for all application components). --> <attr name="memtagMode"> <enum name="default" value="-1" /> <enum name="off" value="0" /> @@ -1853,6 +1867,25 @@ --> <attr name="preserveLegacyExternalStorage" format="boolean" /> + <!-- If {@code true} this app would like optimized external storage access. + + <p> This flag can only be used by apps holding + <ul> + <li>{@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission or + <li>{@link android.app.role}#SYSTEM_GALLERY role. + </ul> + When the flag is set, bulk file path operations will be optimized. + + The default value is {@code true} if + <ul> + <li>app has {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission and + targets targetSDK<=30. + <li>app has {@link android.app.role}#SYSTEM_GALLERY role and targetSDK<=29 + </ul> + {@code false} otherwise. + --> + <attr name="requestOptimizedExternalStorageAccess" format="boolean" /> + <!-- If {@code true} this app declares that it should be visible to all other apps on device, regardless of what they declare via the {@code queries} tags in their manifest. @@ -1879,7 +1912,9 @@ <attr name="memtagMode" /> - <attr name="nativeHeapZeroInit" format="boolean" /> + <!-- If {@code true} enables automatic zero initialization of all native heap + allocations. --> + <attr name="nativeHeapZeroInitialized" format="boolean" /> <!-- @hide no longer used, kept to preserve padding --> <attr name="allowAutoRevokePermissionsExemption" format="boolean" /> @@ -2467,7 +2502,7 @@ <attr name="process" /> <attr name="gwpAsanMode" /> <attr name="memtagMode" /> - <attr name="nativeHeapZeroInit" /> + <attr name="nativeHeapZeroInitialized" /> </declare-styleable> <!-- The <code>deny-permission</code> tag specifies that a permission is to be denied @@ -2843,6 +2878,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/colors.xml b/core/res/res/values/colors.xml index 91896febc571..0213c60e9f60 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -237,7 +237,6 @@ <color name="resolver_text_color_secondary_dark">#ffC4C6C6</color> <color name="resolver_empty_state_text">#FF202124</color> <color name="resolver_empty_state_icon">#FF5F6368</color> - <color name="chooser_chip_icon">#FF1A73E8</color> <!-- Blue 600 --> <!-- Color for personal app suspension notification button text and icon tint. --> <color name="personal_apps_suspension_notification_color">#1A73E8</color> @@ -249,34 +248,34 @@ <color name="system_accent1_0">#ffffff</color> <!-- Shade of the accent system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_50">#91fff4</color> + <color name="system_accent1_50">#9CFFF2</color> <!-- Shade of the accent system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_100">#83f6e5</color> + <color name="system_accent1_100">#8DF5E3</color> <!-- Shade of the accent system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_200">#65d9c9</color> + <color name="system_accent1_200">#71D8C7</color> <!-- Shade of the accent system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_300">#45bdae</color> + <color name="system_accent1_300">#53BCAC</color> <!-- Shade of the accent system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_400">#1fa293</color> + <color name="system_accent1_400">#34A192</color> <!-- Shade of the accent system color at 49% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_500">#008377</color> + <color name="system_accent1_500">#008375</color> <!-- Shade of the accent system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_600">#006d61</color> + <color name="system_accent1_600">#006C5F</color> <!-- Shade of the accent system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_700">#005449</color> + <color name="system_accent1_700">#005747</color> <!-- Shade of the accent system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_800">#003c33</color> + <color name="system_accent1_800">#003E31</color> <!-- Shade of the accent system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent1_900">#00271e</color> + <color name="system_accent1_900">#002214</color> <!-- Darkest shade of the accent color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_accent1_1000">#000000</color> @@ -286,34 +285,34 @@ <color name="system_accent2_0">#ffffff</color> <!-- Shade of the secondary accent system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_50">#91fff4</color> + <color name="system_accent2_50">#CDFAF1</color> <!-- Shade of the secondary accent system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_100">#83f6e5</color> + <color name="system_accent2_100">#BFEBE3</color> <!-- Shade of the secondary accent system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_200">#65d9c9</color> + <color name="system_accent2_200">#A4CFC7</color> <!-- Shade of the secondary accent system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_300">#45bdae</color> + <color name="system_accent2_300">#89B4AC</color> <!-- Shade of the secondary accent system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_400">#1fa293</color> + <color name="system_accent2_400">#6F9991</color> <!-- Shade of the secondary accent system color at 49% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_500">#008377</color> + <color name="system_accent2_500">#537C75</color> <!-- Shade of the secondary accent system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_600">#006d61</color> + <color name="system_accent2_600">#3D665F</color> <!-- Shade of the secondary accent system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_700">#005449</color> + <color name="system_accent2_700">#254E47</color> <!-- Shade of the secondary accent system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_800">#003c33</color> + <color name="system_accent2_800">#0C3731</color> <!-- Shade of the secondary accent system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent2_900">#00271e</color> + <color name="system_accent2_900">#00211C</color> <!-- Darkest shade of the secondary accent color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_accent2_1000">#000000</color> @@ -323,34 +322,34 @@ <color name="system_accent3_0">#ffffff</color> <!-- Shade of the tertiary accent system color at 95% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_50">#91fff4</color> + <color name="system_accent3_50">#F9EAFF</color> <!-- Shade of the tertiary accent system color at 90% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_100">#83f6e5</color> + <color name="system_accent3_100">#ECDBFF</color> <!-- Shade of the tertiary accent system color at 80% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_200">#65d9c9</color> + <color name="system_accent3_200">#CFBFEB</color> <!-- Shade of the tertiary accent system color at 70% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_300">#45bdae</color> + <color name="system_accent3_300">#B3A4CF</color> <!-- Shade of the tertiary accent system color at 60% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_400">#1fa293</color> + <color name="system_accent3_400">#988AB3</color> <!-- Shade of the tertiary accent system color at 49% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_500">#008377</color> + <color name="system_accent3_500">#7B6E96</color> <!-- Shade of the tertiary accent system color at 40% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_600">#006d61</color> + <color name="system_accent3_600">#64587F</color> <!-- Shade of the tertiary accent system color at 30% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_700">#005449</color> + <color name="system_accent3_700">#4C4165</color> <!-- Shade of the tertiary accent system color at 20% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_800">#003c33</color> + <color name="system_accent3_800">#352B4D</color> <!-- Shade of the tertiary accent system color at 10% lightness. This value can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_accent3_900">#00271e</color> + <color name="system_accent3_900">#1E1636</color> <!-- Darkest shade of the tertiary accent color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_accent3_1000">#000000</color> diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml index 3fbd7caf4875..0b41769b08ac 100644 --- a/core/res/res/values/colors_device_defaults.xml +++ b/core/res/res/values/colors_device_defaults.xml @@ -36,6 +36,9 @@ <color name="accent_device_default_light">@color/system_accent1_600</color> <color name="accent_device_default_dark">@color/system_accent1_200</color> <color name="accent_device_default">@color/accent_device_default_light</color> + <color name="accent_primary_device_default">@color/system_accent1_100</color> + <color name="accent_secondary_device_default">@color/system_accent2_100</color> + <color name="accent_tertiary_device_default">@color/system_accent3_100</color> <color name="background_device_default_dark">@color/system_neutral1_800</color> <color name="background_device_default_light">@color/system_neutral1_50</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b4ef63fbcce3..b6c22bb68086 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -951,6 +951,11 @@ --> <integer name="config_longPressOnPowerBehavior">1</integer> + <!-- Whether the setting to change long press on power behaviour from default to assistant (5) + is available in Settings. + --> + <bool name="config_longPressOnPowerForAssistantSettingAvailable">true</bool> + <!-- Control the behavior when the user long presses the power button for a long time. 0 - Nothing 1 - Global actions menu @@ -3727,6 +3732,13 @@ --> <string name="config_defaultWellbeingPackage" translatable="false"></string> + <!-- The package name for the companion provider app. + This package must be trusted, as it has the permissions to associate apps with devices + without a UI prompt. + Example: "com.google.android.gms" + --> + <string name="config_companionProviderPackage" translatable="false"></string> + <!-- The component name for the default system attention service. This service must be trusted, as it can be activated without explicit consent of the user. See android.attention.AttentionManagerService. @@ -4575,6 +4587,11 @@ check after reboot or airplane mode toggling --> <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool> + <!-- Boolean indicating whether all CB messages should be disabled on this device. This config + is intended to be used by OEMs who need to disable CB messages for regulatory requirements, + (e.g. the device is a tablet in a country where tablets should not receive CB messages) --> + <bool translatable="false" name="config_disable_all_cb_messages">false</bool> + <!-- Screen Wake Keys Determines whether the specified key groups can be used to wake up the device. --> <bool name="config_wakeOnDpadKeyPress">true</bool> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 7694fafaa6fb..bc49818638bc 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3059,7 +3059,7 @@ <public name="fontProviderSystemFontFamily" /> <public name="hand_second" /> <public name="memtagMode" /> - <public name="nativeHeapZeroInit" /> + <public name="nativeHeapZeroInitialized" /> <!-- @hide @SystemApi --> <public name="hotwordDetectionService" /> <public name="previewLayout" /> @@ -3092,6 +3092,9 @@ <public name="attributionTags"/> <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 7ea762c2fbbb..dd647508338f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -822,6 +822,11 @@ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgroupdesc_camera">take pictures and record video</string> + <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]--> + <string name="permgrouplab_nearby_devices">Nearby Bluetooth Devices</string> + <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]--> + <string name="permgroupdesc_nearby_devices">discover and connect to nearby Bluetooth devices</string> + <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_calllog">Call logs</string> <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> @@ -1471,6 +1476,14 @@ <string name="permdesc_bluetooth" product="default">Allows the app to view the configuration of the Bluetooth on the phone, and to make and accept connections with paired devices.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]--> + <string name="permlab_bluetooth_scan">discover and pair nearby Bluetooth devices</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]--> + <string name="permdesc_bluetooth_scan" product="default">Allows the app to discover and pair nearby Bluetooth devices</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]--> + <string name="permlab_bluetooth_connect">connect to paired Bluetooth devices</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]--> + <string name="permdesc_bluetooth_connect" product="default">Allows the app to connect to paired Bluetooth devices</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string> @@ -1643,6 +1656,14 @@ <string name="face_recalibrate_notification_title">Re-enroll your face</string> <!-- Notification content shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] --> <string name="face_recalibrate_notification_content">To improve recognition, please re-enroll your face</string> + <!-- Title of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] --> + <string name="face_setup_notification_title">Set up face unlock</string> + <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] --> + <string name="face_setup_notification_content">Unlock your phone by looking at it</string> + <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> + <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string> + <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> + <string name="fingerprint_setup_notification_content">Tap to add a fingerprint</string> <!-- Message shown during face acquisition when the face cannot be recognized [CHAR LIMIT=50] --> <string name="face_acquired_insufficient">Couldn\u2019t capture accurate face data. Try again.</string> @@ -2802,6 +2823,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" --> @@ -5613,40 +5643,39 @@ <!-- 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. [CHAR LIMIT=NONE] --> + <string name="resolver_no_work_apps_available">No work apps</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> - <!-- Error message. This text lets the user know that their current work apps can't open this specific content. [CHAR LIMIT=NONE] --> - <string name="resolver_no_work_apps_available_resolve">No work apps can open this content</string> + <!-- Error message. This text lets the user know that their current personal apps don't support the specific content. [CHAR LIMIT=NONE] --> + <string name="resolver_no_personal_apps_available">No personal apps</string> - <!-- Error message. This text lets the user know that their current personal apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] --> - <string name="resolver_no_personal_apps_available_share">No personal apps can support this content</string> - <!-- Error message. This text lets the user know that their current personal apps can't open this specific content. [CHAR LIMIT=NONE] --> - <string name="resolver_no_personal_apps_available_resolve">No personal apps can open this content</string> + <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string> + <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string> + <!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_use_personal_browser">Use personal browser</string> + <!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] --> + <string name="miniresolver_use_work_browser">Use work browser</string> <!-- Icc depersonalization related strings --> <!-- Label text for PIN entry widget on SIM Network Depersonalization panel [CHAR LIMIT=none] --> @@ -5893,9 +5922,9 @@ ul.</string> <!-- Window magnification prompt related string. --> <!-- Notification title to prompt the user that new magnification feature is available. [CHAR LIMIT=50] --> - <string name="window_magnification_prompt_title">Magnify part of your screen</string> + <string name="window_magnification_prompt_title">New magnification settings</string> <!-- Notification content to prompt the user that new magnification feature is available. [CHAR LIMIT=NONE] --> - <string name="window_magnification_prompt_content">You can now magnify your full screen, a specific area, or switch between both options.</string> + <string name="window_magnification_prompt_content">You can now magnify part of your screen.</string> <!-- Notification action to bring the user to magnification settings page. [CHAR LIMIT=50] --> <string name="turn_on_magnification_settings_action">Turn on in Settings</string> <!-- Notification action to dismiss. [CHAR LIMIT=50] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d6a6f4dea220..b924ecd3f301 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -431,6 +431,7 @@ <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" /> <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" /> <java-symbol type="integer" name="config_longPressOnPowerBehavior" /> + <java-symbol type="bool" name="config_longPressOnPowerForAssistantSettingAvailable" /> <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" /> <java-symbol type="integer" name="config_veryLongPressTimeout" /> <java-symbol type="integer" name="config_longPressOnBackBehavior" /> @@ -554,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" /> @@ -3532,6 +3536,7 @@ <java-symbol type="string" name="notification_channel_do_not_disturb" /> <java-symbol type="string" name="notification_channel_accessibility_magnification" /> <java-symbol type="string" name="notification_channel_accessibility_security_policy" /> + <java-symbol type="string" name="config_companionProviderPackage" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" /> <java-symbol type="string" name="config_defaultTextClassifierPackage" /> @@ -3954,6 +3959,7 @@ <java-symbol type="layout" name="chooser_action_button" /> <java-symbol type="dimen" name="chooser_action_button_icon_size" /> <java-symbol type="string" name="config_defaultNearbySharingComponent" /> + <java-symbol type="bool" name="config_disable_all_cb_messages" /> <java-symbol type="drawable" name="ic_close" /> <java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" /> @@ -4090,19 +4096,14 @@ <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" /> - <java-symbol type="string" name="resolver_no_work_apps_available_resolve" /> - <java-symbol type="string" name="resolver_no_personal_apps_available_share" /> - <java-symbol type="string" name="resolver_no_personal_apps_available_resolve" /> + <java-symbol type="string" name="resolver_no_work_apps_available" /> + <java-symbol type="string" name="resolver_no_personal_apps_available" /> <java-symbol type="string" name="resolver_switch_on_work" /> <java-symbol type="drawable" name="ic_work_apps_off" /> <java-symbol type="drawable" name="ic_sharing_disabled" /> @@ -4219,6 +4220,10 @@ <java-symbol type="bool" name="config_enableOneHandedKeyguard" /> + <java-symbol type="attr" name="colorAccentPrimary" /> + <java-symbol type="attr" name="colorAccentSecondary" /> + <java-symbol type="attr" name="colorAccentTertiary" /> + <!-- CEC Configuration --> <java-symbol type="bool" name="config_cecHdmiCecEnabled_userConfigurable" /> <java-symbol type="bool" name="config_cecHdmiCecControlEnabled_allowed" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 16d720b891e2..e40e31e582db 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -214,6 +214,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -237,6 +240,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -272,6 +278,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -309,6 +318,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -345,6 +357,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -396,6 +411,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -424,6 +442,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -458,6 +479,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -493,6 +517,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -544,6 +571,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -580,6 +610,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -614,6 +647,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -650,6 +686,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -685,6 +724,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -720,6 +762,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -755,6 +800,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -790,6 +838,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -829,6 +880,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -865,6 +919,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -898,6 +955,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -1085,6 +1145,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1104,6 +1167,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1138,6 +1204,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1173,6 +1242,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1210,6 +1282,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1246,6 +1321,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1299,6 +1377,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1326,6 +1407,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1363,6 +1447,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1401,6 +1488,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1440,6 +1530,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> @@ -1459,6 +1552,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> @@ -1477,6 +1573,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1516,6 +1615,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1553,6 +1655,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1589,6 +1694,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1624,6 +1732,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1659,6 +1770,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1692,6 +1806,9 @@ easier. <item name="colorPrimary">@color/primary_device_default_light</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1742,6 +1859,9 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item> <item name="colorSecondary">@color/secondary_device_default_settings_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorEdgeEffect">@color/edge_effect_device_default_light</item> @@ -1772,6 +1892,9 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item> <item name="colorSecondary">@color/secondary_device_default_settings_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorControlNormal">?attr/textColorPrimary</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> @@ -1797,6 +1920,9 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item> <item name="colorSecondary">@color/secondary_device_default_settings_light</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> @@ -1815,6 +1941,9 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item> <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> @@ -1849,6 +1978,9 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item> <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1893,6 +2025,9 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item> <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -1930,6 +2065,9 @@ easier. <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item> <item name="colorSecondary">@color/secondary_device_default_settings</item> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> <item name="colorError">@color/error_color_device_default_light</item> <item name="colorBackground">@color/background_device_default_light</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item> @@ -2030,10 +2168,16 @@ easier. <style name="ThemeOverlay.DeviceDefault.Accent"> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> </style> <style name="ThemeOverlay.DeviceDefault.Accent.Light"> <item name="colorAccent">@color/accent_device_default_light</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> </style> <!-- Theme overlay that replaces colorAccent with the colorAccent from {@link #Theme_DeviceDefault_DayNight}. --> @@ -2042,6 +2186,9 @@ easier. <style name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" parent="ThemeOverlay.Material.Dark.ActionBar"> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> </style> <style name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" parent="Theme.DeviceDefault.NoActionBar.Fullscreen"> diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml index 6c28607a110e..6a01abee3e1c 100644 --- a/core/tests/GameManagerTests/AndroidManifest.xml +++ b/core/tests/GameManagerTests/AndroidManifest.xml @@ -19,7 +19,7 @@ package="com.android.app.gamemanagertests" android:sharedUserId="android.uid.system" > - <application> + <application android:appCategory="game"> <uses-library android:name="android.test.runner" /> </application> diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java index 0c964114a76e..baecc8c4650e 100644 --- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java +++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java @@ -37,9 +37,6 @@ import org.junit.runner.RunWith; @SmallTest @Presubmit public final class GameManagerTests { - private static final String PACKAGE_NAME_0 = "com.android.app0"; - private static final String PACKAGE_NAME_1 = "com.android.app1"; - protected Context mContext; private GameManager mGameManager; private String mPackageName; @@ -52,8 +49,6 @@ public final class GameManagerTests { // Reset the Game Mode for the test app, since it persists across invocations. mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_UNSUPPORTED); - mGameManager.setGameMode(PACKAGE_NAME_0, GameManager.GAME_MODE_UNSUPPORTED); - mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_UNSUPPORTED); } @Test @@ -73,14 +68,14 @@ public final class GameManagerTests { @Test public void testPrivilegedGameModeGetterSetter() { assertEquals(GameManager.GAME_MODE_UNSUPPORTED, - mGameManager.getGameMode(PACKAGE_NAME_0)); + mGameManager.getGameMode(mPackageName)); - mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD); + mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD); assertEquals(GameManager.GAME_MODE_STANDARD, - mGameManager.getGameMode(PACKAGE_NAME_1)); + mGameManager.getGameMode(mPackageName)); - mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE); + mGameManager.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE); assertEquals(GameManager.GAME_MODE_PERFORMANCE, - mGameManager.getGameMode(PACKAGE_NAME_1)); + mGameManager.getGameMode(mPackageName)); } } diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml index 6849a90f5010..f8c69ac17bb0 100644 --- a/core/tests/bluetoothtests/AndroidManifest.xml +++ b/core/tests/bluetoothtests/AndroidManifest.xml @@ -20,6 +20,8 @@ <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f31233b29cd0..408624a6f1b4 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -44,6 +44,8 @@ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java index 7ef1d5e426cc..56c685afa026 100644 --- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java +++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java @@ -42,8 +42,8 @@ public class AppSearchSessionUnitTest { public void setUp() throws Exception { // Remove all documents from any instances that may have been created in the tests. Objects.requireNonNull(mAppSearch); - AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder() - .setDatabaseName("testDb").build(); + AppSearchManager.SearchContext searchContext = + new AppSearchManager.SearchContext.Builder("testDb").build(); CompletableFuture<AppSearchResult<AppSearchSession>> future = new CompletableFuture<>(); mAppSearch.createSearchSession(searchContext, mExecutor, future::complete); mSearchSession = future.get().getResultValue(); @@ -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/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java index 7cb680499d98..36da927116b7 100644 --- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java +++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java @@ -235,6 +235,7 @@ public class PeopleSpaceTileTest { .setStatuses(statusList).setNotificationKey("key") .setNotificationContent("content") .setNotificationDataUri(Uri.parse("data")) + .setMessagesCount(2) .setIntent(new Intent()) .build(); @@ -256,6 +257,7 @@ public class PeopleSpaceTileTest { assertThat(readTile.getNotificationKey()).isEqualTo(tile.getNotificationKey()); assertThat(readTile.getNotificationContent()).isEqualTo(tile.getNotificationContent()); assertThat(readTile.getNotificationDataUri()).isEqualTo(tile.getNotificationDataUri()); + assertThat(readTile.getMessagesCount()).isEqualTo(tile.getMessagesCount()); assertThat(readTile.getIntent().toString()).isEqualTo(tile.getIntent().toString()); } @@ -291,6 +293,17 @@ public class PeopleSpaceTileTest { } @Test + public void testMessagesCount() { + PeopleSpaceTile tile = + new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build(), + mLauncherApps) + .setMessagesCount(2) + .build(); + + assertThat(tile.getMessagesCount()).isEqualTo(2); + } + + @Test public void testIntent() { PeopleSpaceTile tile = new PeopleSpaceTile.Builder( new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build(); 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/content/pm/AppSearchShortcutInfoTest.java b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java index ffe93bc99cf5..21eb44ad113a 100644 --- a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java +++ b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java @@ -45,7 +45,7 @@ public class AppSearchShortcutInfoTest { final Set<String> categorySet = new ArraySet<>(); categorySet.add(category); final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW); - final ShortcutInfo shortcut = new AppSearchShortcutInfo.Builder(id) + final ShortcutInfo shortcut = new AppSearchShortcutInfo.Builder(/*packageName=*/"", id) .setActivity(activity) .setLongLabel(id) .setIconResName(shortcutIconResName) diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java index 11239db9f404..30b2d8e47ab1 100644 --- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java @@ -28,13 +28,15 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.Arrays; @Presubmit @RunWith(JUnit4.class) public class CombinedVibrationEffectTest { private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255); - private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1); + private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.Composed( + new ArrayList<>(), 0); @Test public void testValidateMono() { diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java index d555cd90d907..009665fa1f53 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -18,13 +18,9 @@ package android.os; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; -import static org.junit.Assert.assertArrayEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -35,11 +31,13 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.Uri; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; import android.platform.test.annotations.Presubmit; import com.android.internal.R; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -53,9 +51,6 @@ public class VibrationEffectTest { private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3"; private static final String UNKNOWN_URI = "content://test/system/other_audio"; - private static final float INTENSITY_SCALE_TOLERANCE = 1e-2f; - private static final int AMPLITUDE_SCALE_TOLERANCE = 1; - private static final long TEST_TIMING = 100; private static final int TEST_AMPLITUDE = 100; private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 }; @@ -68,12 +63,6 @@ public class VibrationEffectTest { VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); private static final VibrationEffect TEST_WAVEFORM = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1); - private static final VibrationEffect TEST_COMPOSED = - VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0f, 100) - .compose(); @Test public void getRingtones_noPrebakedRingtones() { @@ -129,7 +118,16 @@ public class VibrationEffectTest { public void testValidateWaveform() { VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate(); VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate(); + VibrationEffect.startWaveform() + .addStep(/* amplitude= */ 1, /* duration= */ 10) + .addRamp(/* amplitude= */ 0, /* duration= */ 20) + .addStep(/* amplitude= */ 1, /* frequency*/ 1, /* duration= */ 100) + .addRamp(/* amplitude= */ 0.5f, /* frequency*/ -1, /* duration= */ 50) + .build() + .validate(); + assertThrows(IllegalStateException.class, + () -> VibrationEffect.startWaveform().build().validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createWaveform(new long[0], new int[0], -1).validate()); assertThrows(IllegalArgumentException.class, @@ -143,17 +141,31 @@ public class VibrationEffectTest { assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createWaveform( TEST_TIMINGS, TEST_AMPLITUDES, TEST_TIMINGS.length).validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startWaveform() + .addStep(/* amplitude= */ -2, 10).build().validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startWaveform() + .addStep(1, /* duration= */ -1).build().validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startWaveform() + .addStep(1, 0, /* duration= */ -1).build().validate()); } @Test public void testValidateComposed() { VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addEffect(TEST_ONE_SHOT) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) + .addEffect(TEST_WAVEFORM, 100) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) + .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .compose() .validate(); + assertThrows(IllegalStateException.class, + () -> VibrationEffect.startComposition().compose().validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startComposition().addPrimitive(-1).compose().validate()); assertThrows(IllegalArgumentException.class, @@ -163,256 +175,138 @@ public class VibrationEffectTest { .validate()); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, -10) + .compose() + .validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startComposition() + .addEffect(TEST_ONE_SHOT, /* delay= */ -10) + .compose() + .validate()); + assertThrows(IllegalArgumentException.class, + () -> VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1) .compose() .validate()); } @Test - public void testScalePrebaked_scalesFallbackEffect() { - VibrationEffect.Prebaked prebaked = - (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]); - assertSame(prebaked, prebaked.scale(0.5f)); - - prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, TEST_ONE_SHOT); - VibrationEffect.OneShot scaledFallback = - (VibrationEffect.OneShot) prebaked.scale(0.5f).getFallbackEffect(); - assertEquals(34, scaledFallback.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - } + public void testResolveOneShot() { + VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51); + assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude()); - @Test - public void testResolvePrebaked_resolvesFallbackEffectIfSet() { - VibrationEffect.Prebaked prebaked = - (VibrationEffect.Prebaked) VibrationEffect.get(VibrationEffect.RINGTONES[1]); - assertSame(prebaked, prebaked.resolve(1000)); - - prebaked = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, - VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE)); - VibrationEffect.OneShot resolvedFallback = - (VibrationEffect.OneShot) prebaked.resolve(10).getFallbackEffect(); - assertEquals(10, resolvedFallback.getAmplitude()); + assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000)); } @Test - public void testScaleOneShot() { - VibrationEffect.OneShot unset = new VibrationEffect.OneShot( - TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); - assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, unset.scale(2).getAmplitude()); - - VibrationEffect.OneShot initial = (VibrationEffect.OneShot) TEST_ONE_SHOT; - - VibrationEffect.OneShot halved = initial.scale(0.5f); - assertEquals(34, halved.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - - VibrationEffect.OneShot copied = initial.scale(1f); - assertEquals(TEST_AMPLITUDE, copied.getAmplitude()); - - VibrationEffect.OneShot scaledUp = initial.scale(1.5f); - assertTrue(scaledUp.getAmplitude() > initial.getAmplitude()); - VibrationEffect.OneShot restored = scaledUp.scale(2 / 3f); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(105, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - - VibrationEffect.OneShot scaledDown = initial.scale(0.8f); - assertTrue(scaledDown.getAmplitude() < initial.getAmplitude()); - restored = scaledDown.scale(1.25f); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(101, restored.getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); + public void testResolveWaveform() { + VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102); + assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude()); - // Does not go below min amplitude while scaling down. - VibrationEffect.OneShot minAmplitude = new VibrationEffect.OneShot(TEST_TIMING, 1); - assertEquals(1, minAmplitude.scale(0.5f).getAmplitude()); + assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000)); } @Test - public void testResolveOneShot() { - VibrationEffect.OneShot initial = (VibrationEffect.OneShot) DEFAULT_ONE_SHOT; - VibrationEffect.OneShot resolved = initial.resolve(239); - assertNotSame(initial, resolved); - assertEquals(239, resolved.getAmplitude()); - - // Ignores input when amplitude already set. - VibrationEffect.OneShot resolved2 = resolved.resolve(10); - assertSame(resolved, resolved2); - assertEquals(239, resolved2.getAmplitude()); + public void testResolvePrebaked() { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + assertEquals(effect, effect.resolve(51)); } @Test - public void testResolveOneshotFailsWhenMaxAmplitudeAboveThreshold() { - try { - TEST_ONE_SHOT.resolve(1000); - fail("Max amplitude above threshold, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + public void testResolveComposed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 1) + .compose(); + assertEquals(effect, effect.resolve(51)); + + VibrationEffect.Composed resolved = VibrationEffect.startComposition() + .addEffect(DEFAULT_ONE_SHOT) + .compose() + .resolve(51); + assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude()); } @Test - public void testResolveOneshotFailsWhenAmplitudeNonPositive() { - try { - TEST_ONE_SHOT.resolve(0); - fail("Amplitude is set to zero, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + public void testApplyEffectStrengthOneShot() { + VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength( + VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(DEFAULT_ONE_SHOT, applied); } @Test - public void testScaleWaveform() { - VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM; - - VibrationEffect.Waveform copied = initial.scale(1f); - assertArrayEquals(TEST_AMPLITUDES, copied.getAmplitudes()); - - VibrationEffect.Waveform scaled = initial.scale(0.9f); - assertEquals(216, scaled.getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE); - assertEquals(0, scaled.getAmplitudes()[1]); - assertEquals(-1, scaled.getAmplitudes()[2]); - - VibrationEffect.Waveform minAmplitude = new VibrationEffect.Waveform( - new long[]{100}, new int[] {1}, -1); - assertArrayEquals(new int[]{1}, minAmplitude.scale(0.5f).getAmplitudes()); + public void testApplyEffectStrengthWaveform() { + VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength( + VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(TEST_WAVEFORM, applied); } @Test - public void testResolveWaveform() { - VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM; - VibrationEffect.Waveform resolved = initial.resolve(123); - assertNotSame(initial, resolved); - assertArrayEquals(new int[]{255, 0, 123}, resolved.getAmplitudes()); - - // Ignores input when amplitude already set. - VibrationEffect.Waveform resolved2 = resolved.resolve(10); - assertSame(resolved, resolved2); - assertArrayEquals(new int[]{255, 0, 123}, resolved2.getAmplitudes()); + public void testApplyEffectStrengthPrebaked() { + VibrationEffect.Composed applied = VibrationEffect.get(VibrationEffect.EFFECT_CLICK) + .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, + ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength()); } @Test - public void testResolveWaveformFailsWhenMaxAmplitudeAboveThreshold() { - try { - TEST_WAVEFORM.resolve(1000); - fail("Max amplitude above threshold, should throw IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - } + public void testApplyEffectStrengthComposed() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1) + .compose(); + assertEquals(effect, effect.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT)); + + VibrationEffect.Composed applied = VibrationEffect.startComposition() + .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .compose() + .applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, + ((PrebakedSegment) applied.getSegments().get(0)).getEffectStrength()); } @Test - public void testScaleComposed() { - VibrationEffect.Composed initial = (VibrationEffect.Composed) TEST_COMPOSED; - - VibrationEffect.Composed copied = initial.scale(1); - assertEquals(1f, copied.getPrimitiveEffects().get(0).scale); - assertEquals(0.5f, copied.getPrimitiveEffects().get(1).scale); - assertEquals(0f, copied.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed halved = initial.scale(0.5f); - assertEquals(0.34f, halved.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0.17f, halved.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0f, halved.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed scaledUp = initial.scale(1.5f); - // Does not scale up from 1. - assertEquals(1f, scaledUp.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - assertTrue(0.5f < scaledUp.getPrimitiveEffects().get(1).scale); - assertEquals(0f, scaledUp.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed restored = scaledUp.scale(2 / 3f); - // The original value was not scaled up, so this only scales it down. - assertEquals(0.53f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(0.47f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0f, restored.getPrimitiveEffects().get(2).scale); - - VibrationEffect.Composed scaledDown = initial.scale(0.8f); - assertTrue(1f > scaledDown.getPrimitiveEffects().get(0).scale); - assertTrue(0.5f > scaledDown.getPrimitiveEffects().get(1).scale); - assertEquals(0f, scaledDown.getPrimitiveEffects().get(2).scale); - - restored = scaledDown.scale(1.25f); - // Does not restore to the exact original value because scale up is a bit offset. - assertEquals(0.84f, restored.getPrimitiveEffects().get(0).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0.5f, restored.getPrimitiveEffects().get(1).scale, INTENSITY_SCALE_TOLERANCE); - assertEquals(0f, restored.getPrimitiveEffects().get(2).scale); - } + public void testScaleOneShot() { + VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f); + assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude()); - @Test - public void testResolveComposed_ignoresDefaultAmplitudeAndReturnsSameEffect() { - VibrationEffect initial = TEST_COMPOSED; - assertSame(initial, initial.resolve(1000)); + VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f); + assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude()); } @Test - public void testScaleAppliesSameAdjustmentsOnAllEffects() { - VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(TEST_TIMING, TEST_AMPLITUDE); - VibrationEffect.Waveform waveform = new VibrationEffect.Waveform( - new long[] { TEST_TIMING }, new int[]{ TEST_AMPLITUDE }, -1); - VibrationEffect.Composed composed = - (VibrationEffect.Composed) VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, - TEST_AMPLITUDE / 255f) - .compose(); + public void testScaleWaveform() { + VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f); + assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f); - assertEquals(oneShot.scale(0.8f).getAmplitude(), - waveform.scale(0.8f).getAmplitudes()[0], - AMPLITUDE_SCALE_TOLERANCE); - assertEquals(oneShot.scale(1.2f).getAmplitude() / 255f, - composed.scale(1.2f).getPrimitiveEffects().get(0).scale, - INTENSITY_SCALE_TOLERANCE); + VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f); + assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude()); } @Test - public void testScaleOnMaxAmplitude() { - VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot( - TEST_TIMING, VibrationEffect.MAX_AMPLITUDE); - VibrationEffect.Waveform waveform = new VibrationEffect.Waveform( - new long[]{TEST_TIMING}, new int[]{VibrationEffect.MAX_AMPLITUDE}, -1); - VibrationEffect.Composed composed = - (VibrationEffect.Composed) VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) - .compose(); + public void testScalePrebaked() { + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - // Scale up does NOT scale MAX_AMPLITUDE - assertEquals(VibrationEffect.MAX_AMPLITUDE, oneShot.scale(1.1f).getAmplitude()); - assertEquals(VibrationEffect.MAX_AMPLITUDE, waveform.scale(1.2f).getAmplitudes()[0]); - assertEquals(1f, - composed.scale(1.4f).getPrimitiveEffects().get(0).scale, - INTENSITY_SCALE_TOLERANCE); // This needs tolerance for float point comparison. - - // Scale down does scale MAX_AMPLITUDE - assertEquals(216, oneShot.scale(0.9f).getAmplitude(), AMPLITUDE_SCALE_TOLERANCE); - assertEquals(180, waveform.scale(0.8f).getAmplitudes()[0], AMPLITUDE_SCALE_TOLERANCE); - assertEquals(0.57f, composed.scale(0.7f).getPrimitiveEffects().get(0).scale, - INTENSITY_SCALE_TOLERANCE); - } + VibrationEffect.Composed scaledUp = effect.scale(1.5f); + assertEquals(effect, scaledUp); - @Test - public void getEffectStrength_returnsValueFromConstructor() { - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_LIGHT, null); - Assert.assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, effect.getEffectStrength()); + VibrationEffect.Composed scaledDown = effect.scale(0.5f); + assertEquals(effect, scaledDown); } @Test - public void getFallbackEffect_withFallbackDisabled_isNull() { - VibrationEffect fallback = VibrationEffect.createOneShot(100, 100); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - false, VibrationEffect.EFFECT_STRENGTH_LIGHT); - Assert.assertNull(effect.getFallbackEffect()); - } + public void testScaleComposed() { + VibrationEffect.Composed effect = + (VibrationEffect.Composed) VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1) + .addEffect(TEST_ONE_SHOT) + .compose(); - @Test - public void getFallbackEffect_withoutEffectSet_isNull() { - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - true, VibrationEffect.EFFECT_STRENGTH_LIGHT); - Assert.assertNull(effect.getFallbackEffect()); - } + VibrationEffect.Composed scaledUp = effect.scale(1.5f); + assertTrue(0.5f < ((PrimitiveSegment) scaledUp.getSegments().get(0)).getScale()); + assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(1)).getAmplitude()); - @Test - public void getFallbackEffect_withFallback_returnsValueFromConstructor() { - VibrationEffect fallback = VibrationEffect.createOneShot(100, 100); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_LIGHT, fallback); - Assert.assertEquals(fallback, effect.getFallbackEffect()); + VibrationEffect.Composed scaledDown = effect.scale(0.5f); + assertTrue(0.5f > ((PrimitiveSegment) scaledDown.getSegments().get(0)).getScale()); + assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude()); } private Resources mockRingtoneResources() { @@ -448,4 +342,4 @@ public class VibrationEffectTest { return context; } -}
\ No newline at end of file +} diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java index 09c36dd261bd..40fc00a97122 100644 --- a/core/tests/coretests/src/android/os/VibratorInfoTest.java +++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import android.hardware.vibrator.IVibrator; import android.platform.test.annotations.Presubmit; +import android.util.Range; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,6 +32,20 @@ import org.junit.runners.JUnit4; @Presubmit @RunWith(JUnit4.class) public class VibratorInfoTest { + private static final float TEST_TOLERANCE = 1e-5f; + + private static final float TEST_MIN_FREQUENCY = 50; + private static final float TEST_RESONANT_FREQUENCY = 150; + private static final float TEST_FREQUENCY_RESOLUTION = 25; + private static final float[] TEST_AMPLITUDE_MAP = new float[]{ + /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}; + + private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING = + new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); + private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING = + new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY, + TEST_RESONANT_FREQUENCY, TEST_FREQUENCY_RESOLUTION, + /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP); @Test public void testHasAmplitudeControl() { @@ -83,6 +98,139 @@ public class VibratorInfoTest { } @Test + public void testGetFrequencyRange_invalidFrequencyMappingReturnsEmptyRange() { + // Invalid, contains NaN values or empty array. + assertEquals(Range.create(0f, 0f), new InfoBuilder().build().getFrequencyRange()); + assertEquals(Range.create(0f, 0f), new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + Float.NaN, 150, 25, 50, TEST_AMPLITUDE_MAP)) + .build().getFrequencyRange()); + assertEquals(Range.create(0f, 0f), new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + 50, Float.NaN, 25, 50, TEST_AMPLITUDE_MAP)) + .build().getFrequencyRange()); + assertEquals(Range.create(0f, 0f), new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + 50, 150, Float.NaN, 50, TEST_AMPLITUDE_MAP)) + .build().getFrequencyRange()); + assertEquals(Range.create(0f, 0f), new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + 50, 150, 25, Float.NaN, TEST_AMPLITUDE_MAP)) + .build().getFrequencyRange()); + assertEquals(Range.create(0f, 0f), new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping(50, 150, 25, 50, null)) + .build().getFrequencyRange()); + // Invalid, minFrequency > resonantFrequency + assertEquals(Range.create(0f, 0f), new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + /* minFrequencyHz= */ 250, /* resonantFrequency= */ 150, 25, 50, null)) + .build().getFrequencyRange()); + // Invalid, maxFrequency < resonantFrequency by changing resolution. + assertEquals(Range.create(0f, 0f), new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + 50, 150, /* frequencyResolutionHz= */10, 50, null)) + .build().getFrequencyRange()); + } + + @Test + public void testGetFrequencyRange_safeRangeLimitedByMaxFrequency() { + VibratorInfo info = new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150, + /* frequencyResolutionHz= */ 25, /* suggestedSafeRangeHz= */ 200, + TEST_AMPLITUDE_MAP)) + .build(); + + // Mapping should range from 50Hz = -2 to 200Hz = 1 + // Safe range [-1, 1] = [100Hz, 200Hz] defined by max - resonant = 50Hz + assertEquals(Range.create(-2f, 1f), info.getFrequencyRange()); + } + + @Test + public void testGetFrequencyRange_safeRangeLimitedByMinFrequency() { + VibratorInfo info = new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150, + /* frequencyResolutionHz= */ 50, /* suggestedSafeRangeHz= */ 200, + TEST_AMPLITUDE_MAP)) + .build(); + + // Mapping should range from 50Hz = -1 to 350Hz = 2 + // Safe range [-1, 1] = [50Hz, 250Hz] defined by resonant - min = 100Hz + assertEquals(Range.create(-1f, 2f), info.getFrequencyRange()); + } + + @Test + public void testGetFrequencyRange_validMappingReturnsFullRelativeRange() { + VibratorInfo info = new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping( + /* minFrequencyHz= */ 50, /* resonantFrequencyHz= */ 150, + /* frequencyResolutionHz= */ 50, /* suggestedSafeRangeHz= */ 100, + TEST_AMPLITUDE_MAP)) + .build(); + + // Mapping should range from 50Hz = -2 to 350Hz = 4 + // Safe range [-1, 1] = [100Hz, 200Hz] defined by suggested safe range 100Hz + assertEquals(Range.create(-2f, 4f), info.getFrequencyRange()); + } + + @Test + public void testAbsoluteFrequency_emptyMappingReturnsNaN() { + VibratorInfo info = new InfoBuilder().build(); + assertTrue(Float.isNaN(info.getAbsoluteFrequency(-1))); + assertTrue(Float.isNaN(info.getAbsoluteFrequency(0))); + assertTrue(Float.isNaN(info.getAbsoluteFrequency(1))); + } + + @Test + public void testAbsoluteFrequency_validRangeReturnsOriginalValue() { + VibratorInfo info = new InfoBuilder().setFrequencyMapping(TEST_FREQUENCY_MAPPING).build(); + assertEquals(TEST_RESONANT_FREQUENCY, info.getAbsoluteFrequency(0), TEST_TOLERANCE); + + // Safe range [-1, 1] = [125Hz, 175Hz] defined by suggested safe range 100Hz + assertEquals(125, info.getAbsoluteFrequency(-1), TEST_TOLERANCE); + assertEquals(175, info.getAbsoluteFrequency(1), TEST_TOLERANCE); + assertEquals(155, info.getAbsoluteFrequency(0.2f), TEST_TOLERANCE); + assertEquals(140, info.getAbsoluteFrequency(-0.4f), TEST_TOLERANCE); + + // Full range [-4, 2] = [50Hz, 200Hz] defined by min frequency and amplitude mapping size + assertEquals(50, info.getAbsoluteFrequency(info.getFrequencyRange().getLower()), + TEST_TOLERANCE); + assertEquals(200, info.getAbsoluteFrequency(info.getFrequencyRange().getUpper()), + TEST_TOLERANCE); + } + + @Test + public void testGetMaxAmplitude_emptyMappingReturnsOnlyResonantFrequency() { + VibratorInfo info = new InfoBuilder().build(); + assertEquals(1f, info.getMaxAmplitude(0), TEST_TOLERANCE); + assertEquals(0f, info.getMaxAmplitude(0.1f), TEST_TOLERANCE); + assertEquals(0f, info.getMaxAmplitude(-1), TEST_TOLERANCE); + } + + @Test + public void testGetMaxAmplitude_validMappingReturnsMappedValues() { + VibratorInfo info = new InfoBuilder() + .setFrequencyMapping(new VibratorInfo.FrequencyMapping(/* minFrequencyHz= */ 50, + /* resonantFrequencyHz= */ 150, /* frequencyResolutionHz= */ 25, + /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP)) + .build(); + + assertEquals(1f, info.getMaxAmplitude(0), TEST_TOLERANCE); // 150Hz + assertEquals(0.9f, info.getMaxAmplitude(1), TEST_TOLERANCE); // 175Hz + assertEquals(0.8f, info.getMaxAmplitude(-1), TEST_TOLERANCE); // 125Hz + assertEquals(0.8f, info.getMaxAmplitude(info.getFrequencyRange().getUpper()), + TEST_TOLERANCE); // 200Hz + assertEquals(0.1f, info.getMaxAmplitude(info.getFrequencyRange().getLower()), + TEST_TOLERANCE); // 50Hz + + // Rounds 145Hz to the max amplitude for 125Hz, which is lower. + assertEquals(0.8f, info.getMaxAmplitude(-0.1f), TEST_TOLERANCE); // 145Hz + // Rounds 185Hz to the max amplitude for 200Hz, which is lower. + assertEquals(0.8f, info.getMaxAmplitude(1.2f), TEST_TOLERANCE); // 185Hz + } + + @Test public void testEquals() { InfoBuilder completeBuilder = new InfoBuilder() .setId(1) @@ -90,7 +238,7 @@ public class VibratorInfoTest { .setSupportedEffects(VibrationEffect.EFFECT_CLICK) .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK) .setQFactor(2f) - .setResonantFrequency(150f); + .setFrequencyMapping(TEST_FREQUENCY_MAPPING); VibratorInfo complete = completeBuilder.build(); assertEquals(complete, complete); @@ -110,22 +258,24 @@ public class VibratorInfoTest { VibratorInfo completeWithUnknownEffects = completeBuilder .setSupportedEffects(null) .build(); - assertNotEquals(complete, completeWithNoEffects); + assertNotEquals(complete, completeWithUnknownEffects); VibratorInfo completeWithUnknownPrimitives = completeBuilder .setSupportedPrimitives(null) .build(); assertNotEquals(complete, completeWithUnknownPrimitives); - VibratorInfo completeWithDifferentF0 = completeBuilder - .setResonantFrequency(complete.getResonantFrequency() + 3f) + VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder + .setFrequencyMapping(new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY + 10, + TEST_RESONANT_FREQUENCY + 20, TEST_FREQUENCY_RESOLUTION + 5, + /* suggestedSafeRangeHz= */ 100, TEST_AMPLITUDE_MAP)) .build(); - assertNotEquals(complete, completeWithDifferentF0); + assertNotEquals(complete, completeWithDifferentFrequencyMapping); - VibratorInfo completeWithUnknownF0 = completeBuilder - .setResonantFrequency(Float.NaN) + VibratorInfo completeWithEmptyFrequencyMapping = completeBuilder + .setFrequencyMapping(EMPTY_FREQUENCY_MAPPING) .build(); - assertNotEquals(complete, completeWithUnknownF0); + assertNotEquals(complete, completeWithEmptyFrequencyMapping); VibratorInfo completeWithUnknownQFactor = completeBuilder .setQFactor(Float.NaN) @@ -153,8 +303,8 @@ public class VibratorInfoTest { .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) .setSupportedEffects(VibrationEffect.EFFECT_CLICK) .setSupportedPrimitives(null) - .setResonantFrequency(1.3f) .setQFactor(Float.NaN) + .setFrequencyMapping(TEST_FREQUENCY_MAPPING) .build(); Parcel parcel = Parcel.obtain(); @@ -169,8 +319,8 @@ public class VibratorInfoTest { private int mCapabilities = 0; private int[] mSupportedEffects = null; private int[] mSupportedPrimitives = null; - private float mResonantFrequency = Float.NaN; private float mQFactor = Float.NaN; + private VibratorInfo.FrequencyMapping mFrequencyMapping = EMPTY_FREQUENCY_MAPPING; public InfoBuilder setId(int id) { mId = id; @@ -192,19 +342,19 @@ public class VibratorInfoTest { return this; } - public InfoBuilder setResonantFrequency(float resonantFrequency) { - mResonantFrequency = resonantFrequency; + public InfoBuilder setQFactor(float qFactor) { + mQFactor = qFactor; return this; } - public InfoBuilder setQFactor(float qFactor) { - mQFactor = qFactor; + public InfoBuilder setFrequencyMapping(VibratorInfo.FrequencyMapping frequencyMapping) { + mFrequencyMapping = frequencyMapping; return this; } public VibratorInfo build() { return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives, - mResonantFrequency, mQFactor); + mQFactor, mFrequencyMapping); } } } diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java new file mode 100644 index 000000000000..de80812f9c29 --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class PrebakedSegmentTest { + + @Test + public void testCreation() { + PrebakedSegment prebaked = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + assertEquals(-1, prebaked.getDuration()); + assertTrue(prebaked.hasNonZeroAmplitude()); + assertEquals(VibrationEffect.EFFECT_CLICK, prebaked.getEffectId()); + assertEquals(VibrationEffect.EFFECT_STRENGTH_MEDIUM, prebaked.getEffectStrength()); + assertTrue(prebaked.shouldFallback()); + } + + @Test + public void testSerialization() { + PrebakedSegment original = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, PrebakedSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new PrebakedSegment(VibrationEffect.EFFECT_CLICK, true, + VibrationEffect.EFFECT_STRENGTH_MEDIUM).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new PrebakedSegment(1000, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrebakedSegment(VibrationEffect.EFFECT_TICK, false, 1000) + .validate()); + } + + @Test + public void testResolve_ignoresAndReturnsSameEffect() { + PrebakedSegment prebaked = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertSame(prebaked, prebaked.resolve(1000)); + } + + @Test + public void testApplyEffectStrength() { + PrebakedSegment medium = new PrebakedSegment( + VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + PrebakedSegment light = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_LIGHT); + assertNotEquals(medium, light); + assertEquals(medium.getEffectId(), light.getEffectId()); + assertEquals(medium.shouldFallback(), light.shouldFallback()); + assertEquals(VibrationEffect.EFFECT_STRENGTH_LIGHT, light.getEffectStrength()); + + PrebakedSegment strong = medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG); + assertNotEquals(medium, strong); + assertEquals(medium.getEffectId(), strong.getEffectId()); + assertEquals(medium.shouldFallback(), strong.shouldFallback()); + assertEquals(VibrationEffect.EFFECT_STRENGTH_STRONG, strong.getEffectStrength()); + + assertSame(medium, medium.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_MEDIUM)); + // Invalid vibration effect strength is ignored. + assertSame(medium, medium.applyEffectStrength(1000)); + } + + @Test + public void testScale_ignoresAndReturnsSameEffect() { + PrebakedSegment prebaked = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertSame(prebaked, prebaked.scale(0.5f)); + } +} diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java new file mode 100644 index 000000000000..538655bb394b --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class PrimitiveSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10); + + assertEquals(-1, primitive.getDuration()); + assertTrue(primitive.hasNonZeroAmplitude()); + assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId()); + assertEquals(10, primitive.getDelay()); + assertEquals(1f, primitive.getScale(), TOLERANCE); + } + + @Test + public void testSerialization() { + PrimitiveSegment original = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, PrimitiveSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(1000, 0, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, -1, 0) + .validate()); + assertThrows(IllegalArgumentException.class, + () -> new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_NOOP, 1, -1) + .validate()); + } + + @Test + public void testResolve_ignoresAndReturnsSameEffect() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + assertSame(primitive, primitive.resolve(1000)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + PrimitiveSegment primitive = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + assertSame(primitive, + primitive.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale_fullPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0); + + assertEquals(1f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0.34f, initial.scale(0.5f).getScale(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.71f, initial.scale(0.8f).getScale(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } + + @Test + public void testScale_halfPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0); + + assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getScale(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(0.86f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.35f, initial.scale(0.8f).getScale(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } + + @Test + public void testScale_zeroPrimitiveScaleValue() { + PrimitiveSegment initial = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0); + + assertEquals(0f, initial.scale(1).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); + assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); + } +} diff --git a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java new file mode 100644 index 000000000000..174b4a76b3c3 --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class RampSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0, + /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100); + + assertEquals(100L, ramp.getDuration()); + assertTrue(ramp.hasNonZeroAmplitude()); + assertEquals(1f, ramp.getStartAmplitude()); + assertEquals(0f, ramp.getEndAmplitude()); + assertEquals(-1f, ramp.getStartFrequency()); + assertEquals(1f, ramp.getEndFrequency()); + } + + @Test + public void testSerialization() { + RampSegment original = new RampSegment(0, 1, 0, 0.5f, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, RampSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0, + /* StartFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 100).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0, 0, 0).validate()); + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(/* startAmplitude= */ -2, 0, 0, 0, 0).validate()); + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(0, /* endAmplitude= */ 2, 0, 0, 0).validate()); + assertThrows(IllegalArgumentException.class, + () -> new RampSegment(0, 0, 0, 0, /* duration= */ -1).validate()); + } + + @Test + public void testHasNonZeroAmplitude() { + assertTrue(new RampSegment(0, 1, 0, 0, 0).hasNonZeroAmplitude()); + assertTrue(new RampSegment(0.01f, 0, 0, 0, 0).hasNonZeroAmplitude()); + assertFalse(new RampSegment(0, 0, 0, 0, 0).hasNonZeroAmplitude()); + } + + @Test + public void testResolve() { + RampSegment ramp = new RampSegment(0, 1, 0, 0, 0); + assertSame(ramp, ramp.resolve(100)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + RampSegment ramp = new RampSegment(1, 0, 1, 0, 0); + assertSame(ramp, ramp.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale() { + RampSegment initial = new RampSegment(0, 1, 0, 0, 0); + + assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE); + + assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE); + assertEquals(0.34f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(1f, initial.scale(1.5f).getEndAmplitude(), TOLERANCE); + assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getEndAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.71f, initial.scale(0.8f).getEndAmplitude(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getEndAmplitude(), TOLERANCE); + } + + @Test + public void testScale_halfPrimitiveScaleValue() { + RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0); + + assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE); + assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE); + } +} diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java new file mode 100644 index 000000000000..79529b8cb13c --- /dev/null +++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.vibrator; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.VibrationEffect; +import android.platform.test.annotations.Presubmit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class StepSegmentTest { + private static final float TOLERANCE = 1e-2f; + + @Test + public void testCreation() { + StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequency= */ -1f, + /* duration= */ 100); + + assertEquals(100, step.getDuration()); + assertTrue(step.hasNonZeroAmplitude()); + assertEquals(1f, step.getAmplitude()); + assertEquals(-1f, step.getFrequency()); + } + + @Test + public void testSerialization() { + StepSegment original = new StepSegment(0.5f, 1f, 10); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + assertEquals(original, StepSegment.CREATOR.createFromParcel(parcel)); + } + + @Test + public void testValidate() { + new StepSegment(/* amplitude= */ 0f, /* frequency= */ -1f, /* duration= */ 100).validate(); + + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(/* amplitude= */ -2, 1f, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(/* amplitude= */ 2, 1f, 10).validate()); + assertThrows(IllegalArgumentException.class, + () -> new StepSegment(2, 1f, /* duration= */ -1).validate()); + } + + @Test + public void testHasNonZeroAmplitude() { + assertTrue(new StepSegment(1f, 0, 0).hasNonZeroAmplitude()); + assertTrue(new StepSegment(0.01f, 0, 0).hasNonZeroAmplitude()); + assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0).hasNonZeroAmplitude()); + assertFalse(new StepSegment(0, 0, 0).hasNonZeroAmplitude()); + } + + @Test + public void testResolve() { + StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); + assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude()); + assertEquals(0.2f, original.resolve(51).getAmplitude(), TOLERANCE); + + StepSegment resolved = new StepSegment(0, 0, 0); + assertSame(resolved, resolved.resolve(100)); + + assertThrows(IllegalArgumentException.class, () -> resolved.resolve(1000)); + } + + @Test + public void testApplyEffectStrength_ignoresAndReturnsSameEffect() { + StepSegment step = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); + assertSame(step, step.applyEffectStrength(VibrationEffect.EFFECT_STRENGTH_STRONG)); + } + + @Test + public void testScale_fullAmplitude() { + StepSegment initial = new StepSegment(1f, 0, 0); + + assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0.34f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + assertEquals(0.53f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.71f, initial.scale(0.8f).getAmplitude(), TOLERANCE); + assertEquals(0.84f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_halfAmplitude() { + StepSegment initial = new StepSegment(0.5f, 0, 0); + + assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0.17f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + // The original value was not scaled up, so this only scales it down. + assertEquals(0.86f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE); + // Does not restore to the exact original value because scale up is a bit offset. + assertEquals(0.35f, initial.scale(0.8f).getAmplitude(), TOLERANCE); + assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_zeroAmplitude() { + StepSegment initial = new StepSegment(0, 0, 0); + + assertEquals(0f, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(0.5f).getAmplitude(), TOLERANCE); + assertEquals(0f, initial.scale(1.5f).getAmplitude(), TOLERANCE); + } + + @Test + public void testScale_defaultAmplitude() { + StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0); + + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1).getAmplitude(), TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(0.5f).getAmplitude(), + TOLERANCE); + assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(), + TOLERANCE); + } +} diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java index f3a6f9e9de17..22c71b527d48 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java @@ -32,7 +32,9 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.ICancellationSignal; +import android.platform.test.annotations.Presubmit; +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; @@ -45,6 +47,8 @@ import org.mockito.MockitoAnnotations; * Tests of {@link ScrollCaptureConnection}. */ @SuppressWarnings("UnnecessaryLocalVariable") +@Presubmit +@SmallTest @RunWith(AndroidJUnit4.class) public class ScrollCaptureConnectionTest { diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java index cc229e11dcf2..dc43204c35d6 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java @@ -32,8 +32,10 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.CancellationSignal; import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; @@ -47,8 +49,10 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** - * Tests of {@link ScrollCaptureTargetSelector}. + * Tests of {@link ScrollCaptureSearchResults}. */ +@Presubmit +@SmallTest @RunWith(AndroidJUnit4.class) public class ScrollCaptureSearchResultsTest { diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java index 41cd4c562bd8..2833ea3f9ac0 100644 --- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java @@ -49,7 +49,6 @@ import java.util.function.Consumer; */ @Presubmit @SmallTest -@FlakyTest(detail = "promote once confirmed flake-free") @RunWith(MockitoJUnitRunner.class) public class ViewGroupScrollCaptureTest { 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/view/contentcapture/ContentCaptureEventTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java index 67614bb22e4e..e6a25d00ff10 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java @@ -236,12 +236,13 @@ public class ContentCaptureEventTest { @Test public void testMergeEvent_typeViewTextChanged() { final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_TEXT_CHANGED) - .setText("test"); + .setText("test", false); final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_TEXT_CHANGED) - .setText("empty"); + .setText("empty", true); event.mergeEvent(event2); assertThat(event.getText()).isEqualTo(event2.getText()); + assertThat(event.getTextHasComposingSpan()).isEqualTo(event2.getTextHasComposingSpan()); } @Test @@ -282,16 +283,18 @@ public class ContentCaptureEventTest { @Test public void testMergeEvent_differentEventTypes() { final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED) - .setText("test").setAutofillId(new AutofillId(1)); + .setText("test", false).setAutofillId(new AutofillId(1)); final ContentCaptureEvent event2 = new ContentCaptureEvent(17, TYPE_VIEW_TEXT_CHANGED) - .setText("empty").setAutofillId(new AutofillId(2)); + .setText("empty", true).setAutofillId(new AutofillId(2)); event.mergeEvent(event2); assertThat(event.getText()).isEqualTo("test"); + assertThat(event.getTextHasComposingSpan()).isFalse(); assertThat(event.getId()).isEqualTo(new AutofillId(1)); event2.mergeEvent(event); assertThat(event2.getText()).isEqualTo("empty"); + assertThat(event2.getTextHasComposingSpan()).isTrue(); assertThat(event2.getId()).isEqualTo(new AutofillId(2)); } 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..1633d2897fcb 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(); @@ -1660,7 +1660,7 @@ public class ChooserActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available_share)) + onView(withText(R.string.resolver_no_work_apps_available)) .check(matches(isDisplayed())); } @@ -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())); } @@ -1711,7 +1711,7 @@ public class ChooserActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available_share)) + onView(withText(R.string.resolver_no_work_apps_available)) .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())); } @@ -2146,7 +2146,7 @@ public class ChooserActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available_resolve)) + onView(withText(R.string.resolver_no_work_apps_available)) .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..97652a970420 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())); } @@ -700,7 +700,7 @@ public class ResolverActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available_resolve)) + onView(withText(R.string.resolver_no_work_apps_available)) .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())); } @@ -751,7 +751,7 @@ public class ResolverActivityTest { onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); - onView(withText(R.string.resolver_no_work_apps_available_resolve)) + onView(withText(R.string.resolver_no_work_apps_available)) .check(matches(isDisplayed())); } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java index 0f591433a84e..d36f06ab683a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -158,4 +158,22 @@ public class BatteryUsageStatsProviderTest { assertThat(item.time).isEqualTo(elapsedTimeMs); } + + @Test + public void shouldUpdateStats() { + Context context = InstrumentationRegistry.getContext(); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, + mStatsRule.getBatteryStats()); + + final List<BatteryUsageStatsQuery> queries = List.of( + new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(), + new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build() + ); + + mStatsRule.setTime(10500, 0); + assertThat(provider.shouldUpdateStats(queries, 10000)).isFalse(); + + mStatsRule.setTime(11500, 0); + assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue(); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 33b8aedb7970..fc3be1337b45 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -68,7 +68,8 @@ public class BatteryUsageStatsTest { final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1) .setDischargePercentage(20) - .setDischargedPowerRange(1000, 2000); + .setDischargedPowerRange(1000, 2000) + .setStatsStartTimestamp(1000); builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid) .setPackageWithHighestDrain("foo") @@ -105,6 +106,7 @@ public class BatteryUsageStatsTest { assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20); assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000); assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000); + assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(1000); final List<UidBatteryConsumer> uidBatteryConsumers = batteryUsageStats.getUidBatteryConsumers(); diff --git a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java index bd7f1e2c3410..eed61cb0afe7 100644 --- a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java @@ -19,6 +19,7 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; import android.os.BatteryConsumer; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; @@ -35,12 +36,14 @@ public class GnssPowerCalculatorTest { private static final double PRECISION = 0.00001; private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 222; @Rule public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() .setAveragePower(PowerProfile.POWER_GPS_ON, 360.0) .setAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, - new double[] {720.0, 1440.0, 1800.0}); + new double[] {720.0, 1440.0, 1800.0}) + .initMeasuredEnergyStatsLocked(0); @Test public void testTimerBasedModel() { @@ -51,7 +54,8 @@ public class GnssPowerCalculatorTest { GnssPowerCalculator calculator = new GnssPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID); assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS)) @@ -59,4 +63,35 @@ public class GnssPowerCalculatorTest { assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS)) .isWithin(PRECISION).of(0.1); } + + @Test + public void testMeasuredEnergyBasedModel() { + BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID); + uidStats.noteStartGps(1000); + uidStats.noteStopGps(2000); + + BatteryStatsImpl.Uid uidStats2 = mStatsRule.getUidStats(APP_UID2); + uidStats2.noteStartGps(3000); + uidStats2.noteStopGps(5000); + + BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + stats.updateGnssMeasuredEnergyStatsLocked(30_000_000, 6000); + + GnssPowerCalculator calculator = + new GnssPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS)) + .isEqualTo(1000); + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS)) + .isWithin(PRECISION).of(2.77777); + + UidBatteryConsumer consumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2); + assertThat(consumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS)) + .isEqualTo(2000); + assertThat(consumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS)) + .isWithin(PRECISION).of(5.55555); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java index 66a8379177c2..ae59a546642d 100644 --- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -24,6 +26,7 @@ import static org.mockito.Mockito.when; import android.net.NetworkCapabilities; import android.net.NetworkStats; import android.os.BatteryConsumer; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.SystemBatteryConsumer; import android.os.UidBatteryConsumer; @@ -54,7 +57,8 @@ public class MobileRadioPowerCalculatorTest { .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0) .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0) .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, - new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0}); + new double[] {720.0, 1080.0, 1440.0, 1800.0, 2160.0}) + .initMeasuredEnergyStatsLocked(0); @Test public void testCounterBasedModel() { @@ -88,14 +92,15 @@ public class MobileRadioPowerCalculatorTest { ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, new int[] {100, 200, 300, 400, 500}, 600); - stats.noteModemControllerActivity(mai, 10000, 10000); + stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000); mStatsRule.setTime(12_000_000, 12_000_000); MobileRadioPowerCalculator calculator = new MobileRadioPowerCalculator(mStatsRule.getPowerProfile()); - mStatsRule.apply(calculator); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(), + calculator); SystemBatteryConsumer consumer = mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO); @@ -106,4 +111,57 @@ public class MobileRadioPowerCalculatorTest { assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) .isWithin(PRECISION).of(0.8); } + + @Test + public void testMeasuredEnergyBasedModel() { + BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + + // Scan for a cell + stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE, + TelephonyManager.SIM_STATE_READY, + 2000, 2000); + + // Found a cell + stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY, + 5000, 5000); + + // Note cell signal strength + SignalStrength signalStrength = mock(SignalStrength.class); + when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE); + stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000); + + stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + 8_000_000_000L, APP_UID, 8000, 8000); + + // Note established network + stats.noteNetworkInterfaceForTransports("cellular", + new int[] { NetworkCapabilities.TRANSPORT_CELLULAR }); + + // Note application network activity + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100); + mStatsRule.setNetworkStats(networkStats); + + ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, + new int[] {100, 200, 300, 400, 500}, 600); + stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000); + + mStatsRule.setTime(12_000_000, 12_000_000); + + MobileRadioPowerCalculator calculator = + new MobileRadioPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + SystemBatteryConsumer consumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO); + + // 100000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 2.77777 mAh + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) + .isWithin(PRECISION).of(2.77777); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) + .isWithin(PRECISION).of(1.53934); + } } diff --git a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml index 0898faeb7080..b3b34ef93ebe 100644 --- a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml @@ -32,6 +32,8 @@ <uses-permission android:name="android.permission.BIND_INPUT_METHOD" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BRICK" /> <uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED" /> <uses-permission android:name="android.permission.BROADCAST_SMS" /> diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml index 98f7177992e2..42d94071400d 100644 --- a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml @@ -21,6 +21,9 @@ android:sharedUserId="com.android.framework.externalsharedpermstestapp"> <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 27bf4ef4c84d..321339073c0b 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -48,6 +48,10 @@ <group gid="uhid" /> </permission> + <permission name="android.permission.VIRTUAL_INPUT_DEVICE" > + <group gid="uhid" /> + </permission> + <permission name="android.permission.NET_TUNNELING" > <group gid="vpn" /> </permission> @@ -224,7 +228,22 @@ targetSdk="29"> <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" /> </split-permission> - + <split-permission name="android.permission.BLUETOOTH" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_SCAN" /> + </split-permission> + <split-permission name="android.permission.BLUETOOTH" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_CONNECT" /> + </split-permission> + <split-permission name="android.permission.BLUETOOTH_ADMIN" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_SCAN" /> + </split-permission> + <split-permission name="android.permission.BLUETOOTH_ADMIN" + targetSdk="31"> + <new-permission name="android.permission.BLUETOOTH_CONNECT" /> + </split-permission> <!-- This is a list of all the libraries available for application code to link against. --> diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 2a6bbf36ef76..c48fd8b8b6ae 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -34,9 +34,11 @@ import android.graphics.fonts.FontFamily; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; import android.graphics.fonts.SystemFonts; +import android.icu.util.ULocale; import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.SharedMemory; +import android.os.SystemProperties; import android.os.Trace; import android.provider.FontRequest; import android.provider.FontsContract; @@ -45,6 +47,7 @@ import android.system.OsConstants; import android.text.FontConfig; import android.util.ArrayMap; import android.util.Base64; +import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; import android.util.SparseArray; @@ -1375,13 +1378,35 @@ public class Typeface { static { // Preload Roboto-Regular.ttf in Zygote for improving app launch performance. - // TODO: add new attribute to fonts.xml to preload fonts in Zygote. preloadFontFile("/system/fonts/Roboto-Regular.ttf"); + + String locale = SystemProperties.get("persist.sys.locale", "en-US"); + String script = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale)).getScript(); + + FontConfig config = SystemFonts.getSystemPreinstalledFontConfig(); + for (int i = 0; i < config.getFontFamilies().size(); ++i) { + FontConfig.FontFamily family = config.getFontFamilies().get(i); + boolean loadFamily = false; + for (int j = 0; j < family.getLocaleList().size(); ++j) { + String fontScript = ULocale.addLikelySubtags( + ULocale.forLocale(family.getLocaleList().get(j))).getScript(); + loadFamily = fontScript.equals(script); + if (loadFamily) { + break; + } + } + if (loadFamily) { + for (int j = 0; j < family.getFontList().size(); ++j) { + preloadFontFile(family.getFontList().get(j).getFile().getAbsolutePath()); + } + } + } } private static void preloadFontFile(String filePath) { File file = new File(filePath); if (file.exists()) { + Log.i(TAG, "Preloading " + file.getAbsolutePath()); nativeWarmUpCache(filePath); } } @@ -1441,13 +1466,11 @@ public class Typeface { /** @hide */ public boolean isSupportedAxes(int axis) { - if (mSupportedAxes == null) { - synchronized (this) { + synchronized (this) { + if (mSupportedAxes == null) { + mSupportedAxes = nativeGetSupportedAxes(native_instance); if (mSupportedAxes == null) { - mSupportedAxes = nativeGetSupportedAxes(native_instance); - if (mSupportedAxes == null) { - mSupportedAxes = EMPTY_AXES; - } + mSupportedAxes = EMPTY_AXES; } } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 24d73efebe54..3616a4d66399 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -276,7 +276,11 @@ public class GradientDrawable extends Drawable { */ @Nullable public float[] getCornerRadii() { - return mGradientState.mRadiusArray.clone(); + float[] radii = mGradientState.mRadiusArray; + if (radii == null) { + return null; + } + return radii.clone(); } /** diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 5dd250c8239e..98b9584f7b4a 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -60,17 +60,13 @@ final class RippleShader extends RuntimeShader { + " float d = distance(uv, xy);\n" + " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n" + "}\n" - + "\n" - + "float getRingMask(vec2 frag, vec2 center, float r, float progress) {\n" - + " float dist = distance(frag, center);\n" - + " float expansion = r * .6;\n" - + " r = r * min(1.,progress);\n" - + " float minD = max(r - expansion, 0.);\n" - + " float maxD = r + expansion;\n" - + " if (dist > maxD || dist < minD) return .0;\n" - + " return min(maxD - dist, dist - minD) / expansion; \n" + + "float softRing(vec2 uv, vec2 xy, float radius, float progress, float blur) {\n" + + " float thickness = 0.2 * radius;\n" + + " float currentRadius = radius * progress;\n" + + " float circle_outer = softCircle(uv, xy, currentRadius + thickness, blur);\n" + + " float circle_inner = softCircle(uv, xy, currentRadius - thickness, blur);\n" + + " return clamp(circle_outer - circle_inner, 0., 1.);\n" + "}\n" - + "\n" + "float subProgress(float start, float end, float progress) {\n" + " float sub = clamp(progress, start, end);\n" + " return (sub - start) / (end - start); \n" @@ -80,8 +76,8 @@ final class RippleShader extends RuntimeShader { + " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n" + " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n" + " vec2 center = mix(in_touch, in_origin, fadeIn);\n" - + " float ring = getRingMask(p, center, in_maxRadius, fadeIn);\n" - + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n" + + " float ring = softRing(p, center, in_maxRadius, fadeIn, 0.45);\n" + + " float alpha = 1. - fadeOutNoise;\n" + " vec2 uv = p * in_resolutionScale;\n" + " vec2 densityUv = uv - mod(uv, in_noiseScale);\n" + " float sparkle = sparkles(densityUv, in_noisePhase) * ring * alpha;\n" @@ -137,8 +133,7 @@ final class RippleShader extends RuntimeShader { } public void setResolution(float w, float h, int density) { - float noiseScale = 0.8f; - float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f * noiseScale; + float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f; setUniform("in_resolutionScale", new float[] {1f / w, 1f / h}); setUniform("in_noiseScale", new float[] {densityScale / w, densityScale / h}); } diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 35b1c169f283..72cea0cacd12 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -139,4 +139,18 @@ public class AndroidKeyStoreMaintenance { return SYSTEM_ERROR; } } + + /** + * Informs Keystore 2.0 that an off body event was detected. + */ + public static void onDeviceOffBody() { + if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return; + try { + getService().onDeviceOffBody(); + } catch (Exception e) { + // TODO This fails open. This is not a regression with respect to keystore1 but it + // should get fixed. + Log.e(TAG, "Error while reporting device off body event.", e); + } + } } diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java new file mode 100644 index 000000000000..a1a7aa85519f --- /dev/null +++ b/keystore/java/android/security/GenerateRkpKey.java @@ -0,0 +1,99 @@ +/* + * 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.security; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner + * app. There are two cases where Keystore should use this class. + * + * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the + * RemoteProvisioner app check if the state of the attestation key pool is getting low enough + * to warrant provisioning more attestation certificates early. + * + * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of + * attestation key pairs and cannot provide one for the given application. Keystore can then + * make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another + * attestation certificate chain provisioned. + * + * In most cases, the proper usage of (1) should preclude the need for (2). + * + * @hide + */ +public class GenerateRkpKey { + + private IGenerateRkpKeyService mBinder; + private Context mContext; + + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + mBinder = IGenerateRkpKeyService.Stub.asInterface(service); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + mBinder = null; + } + }; + + /** + * Constructor which takes a Context object. + */ + public GenerateRkpKey(Context context) { + mContext = context; + } + + /** + * Fulfills the use case of (2) described in the class documentation. Blocks until the + * RemoteProvisioner application can get new attestation keys signed by the server. + */ + public void notifyEmpty(int securityLevel) throws RemoteException { + Intent intent = new Intent(IGenerateRkpKeyService.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) { + throw new RemoteException("Failed to bind to GenerateKeyService"); + } + if (mBinder != null) { + mBinder.generateKey(securityLevel); + } + mContext.unbindService(mConnection); + } + + /** + * FUlfills the use case of (1) described in the class documentation. Non blocking call. + */ + public void notifyKeyGenerated(int securityLevel) throws RemoteException { + Intent intent = new Intent(IGenerateRkpKeyService.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) { + throw new RemoteException("Failed to bind to GenerateKeyService"); + } + if (mBinder != null) { + mBinder.notifyKeyGenerated(securityLevel); + } + mContext.unbindService(mConnection); + } +} diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/keystore/java/android/security/GenerateRkpKeyException.java new file mode 100644 index 000000000000..a2d65e4e7119 --- /dev/null +++ b/keystore/java/android/security/GenerateRkpKeyException.java @@ -0,0 +1,31 @@ +/* + * 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.security; + +/** + * Thrown on problems in attempting to attest to a key using a remotely provisioned key. + * + * @hide + */ +public class GenerateRkpKeyException extends Exception { + + /** + * Constructs a new {@code GenerateRkpKeyException}. + */ + public GenerateRkpKeyException() { + } +} diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl new file mode 100644 index 000000000000..5f1d6693c23a --- /dev/null +++ b/keystore/java/android/security/IGenerateRkpKeyService.aidl @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/** + * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This + * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an + * attestation request. The framework can then synchronously call generateKey() to get more + * attestation keys generated and signed. Upon return, the caller can be certain an attestation key + * is available. + * + * @hide + */ +interface IGenerateRkpKeyService { + /** + * Ping the provisioner service to let it know an app generated a key. This may or may not have + * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check. + */ + oneway void notifyKeyGenerated(in int securityLevel); + /** Ping the provisioner service to indicate there are no remaining attestation keys left. */ + void generateKey(in int securityLevel); +} diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index a08f390c9fd3..b05149ef75bc 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -1204,6 +1204,7 @@ public class KeyStore { * Notify keystore that the device went off-body. */ public void onDeviceOffBody() { + AndroidKeyStoreMaintenance.onDeviceOffBody(); try { mBinder.onDeviceOffBody(); } catch (RemoteException e) { diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index 6ac3821d0f9c..75e248e06b2b 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -18,8 +18,7 @@ package android.security; import android.annotation.NonNull; import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; -import android.os.Build; +import android.compat.annotation.Disabled; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -86,7 +85,7 @@ public class KeyStore2 { * successfully conclude an operation. */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + @Disabled // See b/180133780 static final long KEYSTORE_OPERATION_CREATION_MAY_FAIL = 169897160L; // Never use mBinder directly, use KeyStore2.getService() instead or better yet diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java index cd77d9c2723f..ec3b102c5af9 100644 --- a/keystore/java/android/security/keystore/AttestationUtils.java +++ b/keystore/java/android/security/keystore/AttestationUtils.java @@ -254,6 +254,8 @@ public abstract class AttestationUtils { keyStore.deleteEntry(keystoreAlias); return certificateChain; + } catch (SecurityException e) { + throw e; } catch (Exception e) { throw new DeviceIdAttestationException("Unable to perform attestation", e); } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 5cb2c3b41517..9ca551b26aab 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -288,7 +288,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 private final String mKeystoreAlias; - private final int mNamespace; + private final @KeyProperties.Namespace int mNamespace; private final int mKeySize; private final AlgorithmParameterSpec mSpec; private final X500Principal mCertificateSubject; @@ -331,7 +331,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu */ public KeyGenParameterSpec( String keyStoreAlias, - int namespace, + @KeyProperties.Namespace int namespace, int keySize, AlgorithmParameterSpec spec, X500Principal certificateSubject, @@ -472,7 +472,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @hide */ @SystemApi - public int getNamespace() { + public @KeyProperties.Namespace int getNamespace() { return mNamespace; } @@ -896,7 +896,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final String mKeystoreAlias; private @KeyProperties.PurposeEnum int mPurposes; - private int mNamespace = KeyProperties.NAMESPACE_APPLICATION; + private @KeyProperties.Namespace int mNamespace = KeyProperties.NAMESPACE_APPLICATION; private int mKeySize = -1; private AlgorithmParameterSpec mSpec; private X500Principal mCertificateSubject; @@ -1051,7 +1051,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu */ @SystemApi @NonNull - public Builder setNamespace(int namespace) { + public Builder setNamespace(@KeyProperties.Namespace int namespace) { mNamespace = namespace; return this; } diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 7b0fa91380e1..682d12a65ea3 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -892,6 +892,22 @@ public abstract class KeyProperties { } /** + * Namespaces provide system developers and vendors with a way to use keystore without + * requiring an applications uid. Namespaces can be configured using SEPolicy. + * See <a href="https://source.android.com/security/keystore#access-control"> + * Keystore 2.0 access-control</a> + * {@See KeyGenParameterSpec.Builder#setNamespace} + * {@See android.security.keystore2.AndroidKeyStoreLoadStoreParameter} + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "NAMESPACE_" }, value = { + NAMESPACE_APPLICATION, + NAMESPACE_WIFI, + }) + public @interface Namespace {} + + /** * This value indicates the implicit keystore namespace of the calling application. * It is used by default. Only select system components can choose a different namespace * which it must be configured in SEPolicy. @@ -912,14 +928,12 @@ public abstract class KeyProperties { * For legacy support, translate namespaces into known UIDs. * @hide */ - public static int namespaceToLegacyUid(int namespace) { + public static int namespaceToLegacyUid(@Namespace int namespace) { switch (namespace) { case NAMESPACE_APPLICATION: return KeyStore.UID_SELF; case NAMESPACE_WIFI: return Process.WIFI_UID; - // TODO Translate WIFI and VPN UIDs once the namespaces are defined. - // b/171305388 and b/171305607 default: throw new IllegalArgumentException("No UID corresponding to namespace " + namespace); @@ -930,14 +944,12 @@ public abstract class KeyProperties { * For legacy support, translate namespaces into known UIDs. * @hide */ - public static int legacyUidToNamespace(int uid) { + public static @Namespace int legacyUidToNamespace(int uid) { switch (uid) { case KeyStore.UID_SELF: return NAMESPACE_APPLICATION; case Process.WIFI_UID: return NAMESPACE_WIFI; - // TODO Translate WIFI and VPN UIDs once the namespaces are defined. - // b/171305388 and b/171305607 default: throw new IllegalArgumentException("No namespace corresponding to uid " + uid); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index e401add9ece7..2d8901a37c05 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -24,6 +24,9 @@ import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; import android.hardware.security.keymint.Tag; import android.os.Build; +import android.os.RemoteException; +import android.security.GenerateRkpKey; +import android.security.GenerateRkpKeyException; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore; import android.security.KeyStore2; @@ -520,6 +523,18 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato @Override public KeyPair generateKeyPair() { + try { + return generateKeyPairHelper(); + } catch (GenerateRkpKeyException e) { + try { + return generateKeyPairHelper(); + } catch (GenerateRkpKeyException f) { + throw new ProviderException("Failed to provision new attestation keys."); + } + } + } + + private KeyPair generateKeyPairHelper() throws GenerateRkpKeyException { if (mKeyStore == null || mSpec == null) { throw new IllegalStateException("Not initialized"); } @@ -557,13 +572,30 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato AndroidKeyStorePublicKey publicKey = AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse( descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm); - + GenerateRkpKey keyGen = new GenerateRkpKey(KeyStore.getApplicationContext()); + try { + if (mSpec.getAttestationChallenge() != null) { + keyGen.notifyKeyGenerated(securityLevel); + } + } catch (RemoteException e) { + // This is not really an error state, and necessarily does not apply to non RKP + // systems or hybrid systems where RKP is not currently turned on. + Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend."); + } success = true; return new KeyPair(publicKey, publicKey.getPrivateKey()); } catch (android.security.KeyStoreException e) { switch(e.getErrorCode()) { case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: throw new StrongBoxUnavailableException("Failed to generated key pair.", e); + case ResponseCode.OUT_OF_KEYS: + GenerateRkpKey keyGen = new GenerateRkpKey(KeyStore.getApplicationContext()); + try { + keyGen.notifyEmpty(securityLevel); + } catch (RemoteException f) { + throw new ProviderException("Failed to talk to RemoteProvisioner", f); + } + throw new GenerateRkpKeyException(); default: ProviderException p = new ProviderException("Failed to generate key pair.", e); if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java index 0c6744f9822c..25b1c864b5d1 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java @@ -16,6 +16,8 @@ package android.security.keystore2; +import android.security.keystore.KeyProperties; + import java.security.KeyStore; import java.security.KeyStore.ProtectionParameter; @@ -24,9 +26,9 @@ import java.security.KeyStore.ProtectionParameter; */ public class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter { - private final int mNamespace; + private final @KeyProperties.Namespace int mNamespace; - public AndroidKeyStoreLoadStoreParameter(int namespace) { + public AndroidKeyStoreLoadStoreParameter(@KeyProperties.Namespace int namespace) { mNamespace = namespace; } @@ -35,7 +37,7 @@ public class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStorePara return null; } - int getNamespace() { + @KeyProperties.Namespace int getNamespace() { return mNamespace; } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 39607aeb3852..32f98a2538f3 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -100,7 +100,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { public static final String NAME = "AndroidKeyStore"; private KeyStore2 mKeyStore; - private int mNamespace = KeyProperties.NAMESPACE_APPLICATION; + private @KeyProperties.Namespace int mNamespace = KeyProperties.NAMESPACE_APPLICATION; @Override public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, @@ -1125,7 +1125,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { @Override public void engineLoad(LoadStoreParameter param) throws IOException, NoSuchAlgorithmException, CertificateException { - int namespace = KeyProperties.NAMESPACE_APPLICATION; + @KeyProperties.Namespace int namespace = KeyProperties.NAMESPACE_APPLICATION; if (param != null) { if (param instanceof AndroidKeyStoreLoadStoreParameter) { namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace(); 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/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 18c6b6697821..28c8f53c7f26 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -121,8 +121,8 @@ public class SplashscreenContentDrawer { } else if (mTmpAttrs.mWindowBgResId != 0) { themeBGDrawable = context.getDrawable(mTmpAttrs.mWindowBgResId); } else { - Slog.w(TAG, "Window background not exist!"); themeBGDrawable = createDefaultBackgroundDrawable(); + Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable); } final int estimatedWindowBGColor = estimateWindowBGColor(themeBGDrawable); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index ade63e5b832c..5d9fad5b676e 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -45,7 +45,8 @@ sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const { return SkColorSpace::MakeSRGB(); } -ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker) +ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker, + SkCodec::ZeroInitialized zeroInit) : mCodec(std::move(codec)) , mPeeker(std::move(peeker)) , mDecodeSize(mCodec->codec()->dimensions()) @@ -57,6 +58,7 @@ ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChu mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() } : mDecodeSize; this->rewind(); + mOptions.fZeroInitialized = zeroInit; } ImageDecoder::~ImageDecoder() = default; @@ -446,10 +448,17 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) { ALOGE("Failed to invert matrix!"); } } + + // Even if the client did not provide zero initialized memory, the + // memory we decode into is. + mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized; } auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions); + // The next call to decode() may not provide zero initialized memory. + mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized; + if (scale || handleOrigin || mCropRect) { SkBitmap scaledBm; if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) { diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h index cbfffd5e9291..cef2233fc371 100644 --- a/libs/hwui/hwui/ImageDecoder.h +++ b/libs/hwui/hwui/ImageDecoder.h @@ -34,8 +34,8 @@ public: std::unique_ptr<SkAndroidCodec> mCodec; sk_sp<SkPngChunkReader> mPeeker; - ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, - sk_sp<SkPngChunkReader> peeker = nullptr); + ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker = nullptr, + SkCodec::ZeroInitialized zeroInit = SkCodec::kNo_ZeroInitialized); ~ImageDecoder(); SkISize getSampledDimensions(int sampleSize) const; diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp index ad7741b61e9f..f7b8c014be6e 100644 --- a/libs/hwui/jni/ImageDecoder.cpp +++ b/libs/hwui/jni/ImageDecoder.cpp @@ -141,7 +141,8 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, } const bool isNinePatch = peeker->mPatch != nullptr; - ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker)); + ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker), + SkCodec::kYes_ZeroInitialized); return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID, reinterpret_cast<jlong>(decoder), decoder->width(), decoder->height(), animated, isNinePatch); 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/location/java/android/location/Location.java b/location/java/android/location/Location.java index 5e2e5595ba9c..5f8d795cd654 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -1246,20 +1246,42 @@ public class Location implements Parcelable { * Returns true if the Location came from a mock provider. * * @return true if this Location came from a mock provider, false otherwise + * @deprecated Prefer {@link #isMock()} instead. */ + @Deprecated public boolean isFromMockProvider() { - return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0; + return isMock(); } /** * Flag this Location as having come from a mock provider or not. * * @param isFromMockProvider true if this Location came from a mock provider, false otherwise + * @deprecated Prefer {@link #setMock(boolean)} instead. * @hide */ + @Deprecated @SystemApi public void setIsFromMockProvider(boolean isFromMockProvider) { - if (isFromMockProvider) { + setMock(isFromMockProvider); + } + + /** + * Returns true if this location is marked as a mock location. If this location comes from the + * Android framework, this indicates that the location was provided by a test location provider, + * and thus may not be related to the actual location of the device. + * + * @see LocationManager#addTestProvider + */ + public boolean isMock() { + return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0; + } + + /** + * Sets whether this location is marked as a mock location. + */ + public void setMock(boolean mock) { + if (mock) { mFieldsMask |= HAS_MOCK_PROVIDER_MASK; } else { mFieldsMask &= ~HAS_MOCK_PROVIDER_MASK; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f957a73144c8..343d04fd3214 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, @@ -3185,6 +3221,23 @@ public class AudioManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static final int NUM_SOUND_EFFECTS = 16; + /** @hide */ + @IntDef(prefix = { "FX_" }, value = { + FX_KEY_CLICK, + FX_FOCUS_NAVIGATION_UP, + FX_FOCUS_NAVIGATION_DOWN, + FX_FOCUS_NAVIGATION_LEFT, + FX_FOCUS_NAVIGATION_RIGHT, + FX_KEYPRESS_STANDARD, + FX_KEYPRESS_SPACEBAR, + FX_KEYPRESS_DELETE, + FX_KEYPRESS_RETURN, + FX_KEYPRESS_INVALID, + FX_BACK + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SystemSoundEffect {} + /** * @hide Number of FX_FOCUS_NAVIGATION_REPEAT_* sound effects */ @@ -3260,22 +3313,11 @@ public class AudioManager { /** * Plays a sound effect (Key clicks, lid open/close...) - * @param effectType The type of sound effect. One of - * {@link #FX_KEY_CLICK}, - * {@link #FX_FOCUS_NAVIGATION_UP}, - * {@link #FX_FOCUS_NAVIGATION_DOWN}, - * {@link #FX_FOCUS_NAVIGATION_LEFT}, - * {@link #FX_FOCUS_NAVIGATION_RIGHT}, - * {@link #FX_KEYPRESS_STANDARD}, - * {@link #FX_KEYPRESS_SPACEBAR}, - * {@link #FX_KEYPRESS_DELETE}, - * {@link #FX_KEYPRESS_RETURN}, - * {@link #FX_KEYPRESS_INVALID}, - * {@link #FX_BACK}, + * @param effectType The type of sound effect. * NOTE: This version uses the UI settings to determine * whether sounds are heard or not. */ - public void playSoundEffect(int effectType) { + public void playSoundEffect(@SystemSoundEffect int effectType) { if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) { return; } @@ -3294,24 +3336,13 @@ public class AudioManager { /** * Plays a sound effect (Key clicks, lid open/close...) - * @param effectType The type of sound effect. One of - * {@link #FX_KEY_CLICK}, - * {@link #FX_FOCUS_NAVIGATION_UP}, - * {@link #FX_FOCUS_NAVIGATION_DOWN}, - * {@link #FX_FOCUS_NAVIGATION_LEFT}, - * {@link #FX_FOCUS_NAVIGATION_RIGHT}, - * {@link #FX_KEYPRESS_STANDARD}, - * {@link #FX_KEYPRESS_SPACEBAR}, - * {@link #FX_KEYPRESS_DELETE}, - * {@link #FX_KEYPRESS_RETURN}, - * {@link #FX_KEYPRESS_INVALID}, - * {@link #FX_BACK}, + * @param effectType The type of sound effect. * @param userId The current user to pull sound settings from * NOTE: This version uses the UI settings to determine * whether sounds are heard or not. * @hide */ - public void playSoundEffect(int effectType, int userId) { + public void playSoundEffect(@SystemSoundEffect int effectType, int userId) { if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) { return; } @@ -3330,25 +3361,14 @@ public class AudioManager { /** * Plays a sound effect (Key clicks, lid open/close...) - * @param effectType The type of sound effect. One of - * {@link #FX_KEY_CLICK}, - * {@link #FX_FOCUS_NAVIGATION_UP}, - * {@link #FX_FOCUS_NAVIGATION_DOWN}, - * {@link #FX_FOCUS_NAVIGATION_LEFT}, - * {@link #FX_FOCUS_NAVIGATION_RIGHT}, - * {@link #FX_KEYPRESS_STANDARD}, - * {@link #FX_KEYPRESS_SPACEBAR}, - * {@link #FX_KEYPRESS_DELETE}, - * {@link #FX_KEYPRESS_RETURN}, - * {@link #FX_KEYPRESS_INVALID}, - * {@link #FX_BACK}, + * @param effectType The type of sound effect. * @param volume Sound effect volume. * The volume value is a raw scalar so UI controls should be scaled logarithmically. * If a volume of -1 is specified, the AudioManager.STREAM_MUSIC stream volume minus 3dB will be used. * NOTE: This version is for applications that have their own * settings panel for enabling and controlling volume. */ - public void playSoundEffect(int effectType, float volume) { + public void playSoundEffect(@SystemSoundEffect int effectType, float volume) { if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) { return; } @@ -6754,6 +6774,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 +6819,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 +6832,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/AudioProfile.java b/media/java/android/media/AudioProfile.java index 9774e8032c95..ac96e6f80a5a 100644 --- a/media/java/android/media/AudioProfile.java +++ b/media/java/android/media/AudioProfile.java @@ -88,7 +88,7 @@ public class AudioProfile { if (ints == null || ints.length == 0) { return ""; } - return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X, ", anInt)) - .collect(Collectors.joining()); + return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X", anInt)) + .collect(Collectors.joining(", ")); } } 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 cf31e4141a6d..b51777c3135a 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; @@ -46,6 +45,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -1539,7 +1539,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 @@ -1674,9 +1674,11 @@ final public class MediaCodec implements PlaybackComponent { public @interface BufferFlag {} private EventHandler mEventHandler; + private EventHandler mOnFirstTunnelFrameReadyHandler; private EventHandler mOnFrameRenderedHandler; private EventHandler mCallbackHandler; private Callback mCallback; + private OnFirstTunnelFrameReadyListener mOnFirstTunnelFrameReadyListener; private OnFrameRenderedListener mOnFrameRenderedListener; private final Object mListenerLock = new Object(); private MediaCodecInfo mCodecInfo; @@ -1687,6 +1689,7 @@ final public class MediaCodec implements PlaybackComponent { private static final int EVENT_CALLBACK = 1; private static final int EVENT_SET_CALLBACK = 2; private static final int EVENT_FRAME_RENDERED = 3; + private static final int EVENT_FIRST_TUNNEL_FRAME_READY = 4; private static final int CB_INPUT_AVAILABLE = 1; private static final int CB_OUTPUT_AVAILABLE = 2; @@ -1694,22 +1697,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; @@ -1748,6 +1735,16 @@ final public class MediaCodec implements PlaybackComponent { mCodec, (long)mediaTimeUs, (long)systemNano); } break; + case EVENT_FIRST_TUNNEL_FRAME_READY: + OnFirstTunnelFrameReadyListener onFirstTunnelFrameReadyListener; + synchronized (mListenerLock) { + onFirstTunnelFrameReadyListener = mOnFirstTunnelFrameReadyListener; + } + if (onFirstTunnelFrameReadyListener == null) { + break; + } + onFirstTunnelFrameReadyListener.onFirstTunnelFrameReady(mCodec); + break; default: { break; @@ -1923,6 +1920,7 @@ final public class MediaCodec implements PlaybackComponent { mEventHandler = null; } mCallbackHandler = mEventHandler; + mOnFirstTunnelFrameReadyHandler = mEventHandler; mOnFrameRenderedHandler = mEventHandler; mBufferLock = new Object(); @@ -2277,6 +2275,9 @@ final public class MediaCodec implements PlaybackComponent { mCallbackHandler.removeMessages(EVENT_SET_CALLBACK); mCallbackHandler.removeMessages(EVENT_CALLBACK); } + if (mOnFirstTunnelFrameReadyHandler != null) { + mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY); + } if (mOnFrameRenderedHandler != null) { mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED); } @@ -4447,6 +4448,41 @@ final public class MediaCodec implements PlaybackComponent { MediaFormat.KEY_LOW_LATENCY; /** + * Control video peek of the first frame when a codec is configured for tunnel mode with + * {@link MediaFormat#KEY_AUDIO_SESSION_ID} while the {@link AudioTrack} is paused. + *<p> + * When disabled (1) after a {@link #flush} or {@link #start}, (2) while the corresponding + * {@link AudioTrack} is paused and (3) before any buffers are queued, the first frame is not to + * be rendered until either this parameter is enabled or the corresponding {@link AudioTrack} + * has begun playback. Once the frame is decoded and ready to be rendered, + * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called but the frame is + * not rendered. The surface continues to show the previously-rendered content, or black if the + * surface is new. A subsequent call to {@link AudioTrack#play} renders this frame and triggers + * a callback to {@link OnFrameRenderedListener#onFrameRendered}, and video playback begins. + *<p> + * <b>Note</b>: To clear any previously rendered content and show black, configure the + * MediaCodec with {@code KEY_PUSH_BLANK_BUFFERS_ON_STOP(1)}, and call {@link #stop} before + * pushing new video frames to the codec. + *<p> + * When enabled (1) after a {@link #flush} or {@link #start} and (2) while the corresponding + * {@link AudioTrack} is paused, the first frame is rendered as soon as it is decoded, or + * immediately, if it has already been decoded. If not already decoded, when the frame is + * decoded and ready to be rendered, + * {@link OnFirstTunnelFrameReadyListener#onFirstTunnelFrameReady} is called. The frame is then + * immediately rendered and {@link OnFrameRenderedListener#onFrameRendered} is subsequently + * called. + *<p> + * The value is an Integer object containing the value 1 to enable or the value 0 to disable. + *<p> + * The default for this parameter is <b>enabled</b>. Once a frame has been rendered, changing + * this parameter has no effect until a subsequent {@link #flush} or + * {@link #stop}/{@link #start}. + * + * @see #setParameters(Bundle) + */ + public static final String PARAMETER_KEY_TUNNEL_PEEK = "tunnel-peek"; + + /** * Communicate additional parameter changes to the component instance. * <b>Note:</b> Some of these parameter changes may silently fail to apply. * @@ -4545,6 +4581,55 @@ final public class MediaCodec implements PlaybackComponent { } /** + * Listener to be called when the first output frame has been decoded + * and is ready to be rendered for a codec configured for tunnel mode with + * {@code KEY_AUDIO_SESSION_ID}. + * + * @see MediaCodec#setOnFirstTunnelFrameReadyListener + */ + public interface OnFirstTunnelFrameReadyListener { + + /** + * Called when the first output frame has been decoded and is ready to be + * rendered. + */ + void onFirstTunnelFrameReady(@NonNull MediaCodec codec); + } + + /** + * Registers a callback to be invoked when the first output frame has been decoded + * and is ready to be rendered on a codec configured for tunnel mode with {@code + * KEY_AUDIO_SESSION_ID}. + * + * @param handler the callback will be run on the handler's thread. If {@code + * null}, the callback will be run on the default thread, which is the looper from + * which the codec was created, or a new thread if there was none. + * + * @param listener the callback that will be run. If {@code null}, clears any registered + * listener. + */ + public void setOnFirstTunnelFrameReadyListener( + @Nullable Handler handler, @Nullable OnFirstTunnelFrameReadyListener listener) { + synchronized (mListenerLock) { + mOnFirstTunnelFrameReadyListener = listener; + if (listener != null) { + EventHandler newHandler = getEventHandlerOn( + handler, + mOnFirstTunnelFrameReadyHandler); + if (newHandler != mOnFirstTunnelFrameReadyHandler) { + mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY); + } + mOnFirstTunnelFrameReadyHandler = newHandler; + } else if (mOnFirstTunnelFrameReadyHandler != null) { + mOnFirstTunnelFrameReadyHandler.removeMessages(EVENT_FIRST_TUNNEL_FRAME_READY); + } + native_enableOnFirstTunnelFrameReadyListener(listener != null); + } + } + + private native void native_enableOnFirstTunnelFrameReadyListener(boolean enable); + + /** * Listener to be called when an output frame has rendered on the output surface * * @see MediaCodec#setOnFrameRenderedListener @@ -4606,6 +4691,128 @@ final public class MediaCodec implements PlaybackComponent { private native void native_enableOnFrameRenderedListener(boolean enable); + /** + * Returns a list of vendor parameter names. + * <p> + * This method can be called in any codec state except for released state. + * + * @return a list containing supported vendor parameters; an empty + * list if no vendor parameters are supported. The order of the + * parameters is arbitrary. + * @throws IllegalStateException if in the Released state. + */ + @NonNull + public List<String> getSupportedVendorParameters() { + return native_getSupportedVendorParameters(); + } + + @NonNull + private native List<String> native_getSupportedVendorParameters(); + + /** + * Contains description of a parameter. + */ + public static class ParameterDescriptor { + private ParameterDescriptor() {} + + /** + * Returns the name of the parameter. + */ + @NonNull + public String getName() { + return mName; + } + + /** + * Returns the type of the parameter. + * {@link MediaFormat#TYPE_NULL} is never returned. + */ + @MediaFormat.Type + public int getType() { + return mType; + } + + private String mName; + private @MediaFormat.Type int mType; + } + + /** + * Describe a parameter with the name. + * <p> + * This method can be called in any codec state except for released state. + * + * @param name name of the parameter to describe, typically one from + * {@link #getSupportedVendorParameters}. + * @return {@link ParameterDescriptor} object that describes the parameter. + * {@code null} if unrecognized / not able to describe. + * @throws IllegalStateException if in the Released state. + */ + @Nullable + public ParameterDescriptor getParameterDescriptor(@NonNull String name) { + return native_getParameterDescriptor(name); + } + + @Nullable + private native ParameterDescriptor native_getParameterDescriptor(@NonNull String name); + + /** + * Subscribe to vendor parameters, so that changes to these parameters generate + * output format change event. + * <p> + * Unrecognized parameter names or standard (non-vendor) parameter names will be ignored. + * {@link #reset} also resets the list of subscribed parameters. + * If a parameter in {@code names} is already subscribed, it will remain subscribed. + * <p> + * This method can be called in any codec state except for released state. When called in + * running state with newly subscribed parameters, it takes effect no later than the + * processing of the subsequently queued buffer. For the new parameters, the codec will generate + * output format change event. + * <p> + * Note that any vendor parameters set in a {@link #configure} or + * {@link #setParameters} call are automatically subscribed. + * <p> + * See also {@link #INFO_OUTPUT_FORMAT_CHANGED} or {@link Callback#onOutputFormatChanged} + * for output format change events. + * + * @param names names of the vendor parameters to subscribe. This may be an empty list, + * and in that case this method will not change the list of subscribed parameters. + * @throws IllegalStateException if in the Released state. + */ + public void subscribeToVendorParameters(@NonNull List<String> names) { + native_subscribeToVendorParameters(names); + } + + private native void native_subscribeToVendorParameters(@NonNull List<String> names); + + /** + * Unsubscribe from vendor parameters, so that changes to these parameters + * no longer generate output format change event. + * <p> + * Unrecognized parameter names, standard (non-vendor) parameter names will be ignored. + * {@link #reset} also resets the list of subscribed parameters. + * If a parameter in {@code names} is already unsubscribed, it will remain unsubscribed. + * <p> + * This method can be called in any codec state except for released state. When called in + * running state with newly unsubscribed parameters, it takes effect no later than the + * processing of the subsequently queued buffer. + * <p> + * Note that any vendor parameters set in a {@link #configure} or + * {@link #setParameters} call are automatically subscribed, and with this method + * they can be unsubscribed. + * <p> + * See also {@link #INFO_OUTPUT_FORMAT_CHANGED} or {@link Callback#onOutputFormatChanged} + * for output format change events. + * + * @param names names of the vendor parameters to unsubscribe. This may be an empty list, + * and in that case this method will not change the list of subscribed parameters. + * @throws IllegalStateException if in the Released state. + */ + public void unsubscribeFromVendorParameters(@NonNull List<String> names) { + native_unsubscribeFromVendorParameters(names); + } + + private native void native_unsubscribeFromVendorParameters(@NonNull List<String> names); + private EventHandler getEventHandlerOn( @Nullable Handler handler, @NonNull EventHandler lastHandler) { if (handler == null) { @@ -4667,6 +4874,8 @@ final public class MediaCodec implements PlaybackComponent { EventHandler handler = mEventHandler; if (what == EVENT_CALLBACK) { handler = mCallbackHandler; + } else if (what == EVENT_FIRST_TUNNEL_FRAME_READY) { + handler = mOnFirstTunnelFrameReadyHandler; } else if (what == EVENT_FRAME_RENDERED) { handler = mOnFrameRenderedHandler; } diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 06d0eb0b5423..a9e8c26ecb77 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -3434,11 +3434,14 @@ public final class MediaCodecInfo { public static final int BITRATE_MODE_VBR = 1; /** Constant bitrate mode */ public static final int BITRATE_MODE_CBR = 2; + /** Constant bitrate mode with frame drops */ + public static final int BITRATE_MODE_CBR_FD = 3; private static final Feature[] bitrates = new Feature[] { new Feature("VBR", BITRATE_MODE_VBR, true), new Feature("CBR", BITRATE_MODE_CBR, false), - new Feature("CQ", BITRATE_MODE_CQ, false) + new Feature("CQ", BITRATE_MODE_CQ, false), + new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false) }; private static int parseBitrateMode(String mode) { 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/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 02fa0401e586..1f6855a4ae8e 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -194,6 +194,14 @@ public final class MediaRouter2 { /** * Starts scanning remote routes. + * <p> + * Route discovery can happen even when the {@link #startScan()} is not called. + * This is because the scanning could be started before by other apps. + * Therefore, calling this method after calling {@link #stopScan()} does not necessarily mean + * that the routes found before are removed and added again. + * <p> + * Use {@link RouteCallback} to get the route related events. + * <p> * Note that calling start/stopScan is applied to all system routers in the same process. * * @see #stopScan() @@ -208,6 +216,15 @@ public final class MediaRouter2 { /** * Stops scanning remote routes to reduce resource consumption. + * <p> + * Route discovery can be continued even after this method is called. + * This is because the scanning is only turned off when all the apps stop scanning. + * Therefore, calling this method does not necessarily mean the routes are removed. + * Also, for the same reason it does not mean that {@link RouteCallback#onRoutesAdded(List)} + * is not called afterwards. + * <p> + * Use {@link RouteCallback} to get the route related events. + * <p> * Note that calling start/stopScan is applied to all system routers in the same process. * * @see #startScan() diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 6fefbe15abae..758a8130a62e 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -148,6 +148,14 @@ public final class MediaRouter2Manager { /** * Starts scanning remote routes. + * <p> + * Route discovery can happen even when the {@link #startScan()} is not called. + * This is because the scanning could be started before by other apps. + * Therefore, calling this method after calling {@link #stopScan()} does not necessarily mean + * that the routes found before are removed and added again. + * <p> + * Use {@link Callback} to get the route related events. + * <p> * @see #stopScan() */ public void startScan() { @@ -163,6 +171,15 @@ public final class MediaRouter2Manager { /** * Stops scanning remote routes to reduce resource consumption. + * <p> + * Route discovery can be continued even after this method is called. + * This is because the scanning is only turned off when all the apps stop scanning. + * Therefore, calling this method does not necessarily mean the routes are removed. + * Also, for the same reason it does not mean that {@link Callback#onRoutesAdded(List)} + * is not called afterwards. + * <p> + * Use {@link Callback} to get the route related events. + * * @see #startScan() */ public void stopScan() { diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java index 2f952474b7f0..37fee8466859 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -68,9 +69,10 @@ public final class RouteDiscoveryPreference implements Parcelable { private final Bundle mExtras; /** - * An empty discovery preference + * An empty discovery preference. * @hide */ + @SystemApi public static final RouteDiscoveryPreference EMPTY = new Builder(Collections.emptyList(), false).build(); 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/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 96bffee117ea..3b0f577d84cb 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -39,6 +39,8 @@ interface ISessionManager { ISession createSession(String packageName, in ISessionCallback sessionCb, String tag, in Bundle sessionInfo, int userId); List<MediaSession.Token> getSessions(in ComponentName compName, int userId); + MediaSession.Token getMediaKeyEventSession(); + String getMediaKeyEventSessionPackageName(); void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent, boolean needWakeLock); boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName, diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 78db750d4a64..269b70b667a0 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -195,6 +195,44 @@ public final class MediaSessionManager { } /** + * Gets the media key event session, which would receive a media key event unless specified. + * @return The media key event session, which would receive key events by default, unless + * the caller has specified the target. Can be {@code null}. + * @hide + */ + @SystemApi + @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) + @Nullable + public MediaSession.Token getMediaKeyEventSession() { + try { + return mService.getMediaKeyEventSession(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to get media key event session", ex); + } + return null; + } + + /** + * Gets the package name of the media key event session. + * @return The package name of the media key event session or the last session's media button + * receiver if the media key event session is {@code null}. + * @see #getMediaKeyEventSession() + * @hide + */ + @SystemApi + @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL) + @NonNull + public String getMediaKeyEventSessionPackageName() { + try { + String packageName = mService.getMediaKeyEventSessionPackageName(); + return (packageName != null) ? packageName : ""; + } catch (RemoteException ex) { + Log.e(TAG, "Failed to get media key event session", ex); + } + return ""; + } + + /** * Get active sessions for the given user. * <p> * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be @@ -856,7 +894,7 @@ public final class MediaSessionManager { } /** - * Add a {@link OnMediaKeyEventDispatchedListener}. + * Add a {@link OnMediaKeyEventSessionChangedListener}. * * @param executor The executor on which the listener should be invoked * @param listener A {@link OnMediaKeyEventSessionChangedListener}. diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 7f5dd5d15dbe..f6944823feb5 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -53,6 +53,7 @@ #include <media/MediaCodecBuffer.h> #include <media/hardware/VideoAPI.h> +#include <media/stagefright/CodecBase.h> #include <media/stagefright/MediaCodec.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> @@ -81,6 +82,17 @@ enum { EVENT_CALLBACK = 1, EVENT_SET_CALLBACK = 2, EVENT_FRAME_RENDERED = 3, + EVENT_FIRST_TUNNEL_FRAME_READY = 4, +}; + +// From MediaFormat.java +enum { + TYPE_NULL = 0, + TYPE_INTEGER = 1, + TYPE_LONG = 2, + TYPE_FLOAT = 3, + TYPE_STRING = 4, + TYPE_BYTE_BUFFER = 5, }; static struct CryptoErrorCodes { @@ -139,6 +151,8 @@ static struct { } gByteBufferInfo; static struct { + jclass clazz; + jmethodID ctorId; jmethodID sizeId; jmethodID getId; jmethodID addId; @@ -153,6 +167,13 @@ static struct { jfieldID lockId; } gLinearBlockInfo; +static struct { + jclass clazz; + jmethodID ctorId; + jfieldID nameId; + jfieldID typeId; +} gDescriptorInfo; + struct fields_t { jmethodID postEventFromNativeID; jmethodID lockAndGetContextID; @@ -269,6 +290,18 @@ JMediaCodec::~JMediaCodec() { mClass = NULL; } +status_t JMediaCodec::enableOnFirstTunnelFrameReadyListener(jboolean enable) { + if (enable) { + if (mOnFirstTunnelFrameReadyNotification == NULL) { + mOnFirstTunnelFrameReadyNotification = new AMessage(kWhatFirstTunnelFrameReady, this); + } + } else { + mOnFirstTunnelFrameReadyNotification.clear(); + } + + return mCodec->setOnFirstTunnelFrameReadyNotification(mOnFirstTunnelFrameReadyNotification); +} + status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) { if (enable) { if (mOnFrameRenderedNotification == NULL) { @@ -924,6 +957,74 @@ void JMediaCodec::selectAudioPresentation(const int32_t presentationId, const in (void)mCodec->setParameters(msg); } +status_t JMediaCodec::querySupportedVendorParameters(JNIEnv *env, jobject *namesObj) { + std::vector<std::string> names; + status_t status = mCodec->querySupportedVendorParameters(&names); + if (status != OK) { + return status; + } + *namesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId); + for (const std::string &name : names) { + ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(name.c_str())}; + (void)env->CallBooleanMethod(*namesObj, gArrayListInfo.addId, nameStr.get()); + } + return OK; +} + +status_t JMediaCodec::describeParameter(JNIEnv *env, jstring name, jobject *descObj) { + const char *tmp = env->GetStringUTFChars(name, nullptr); + CodecParameterDescriptor desc; + status_t status = mCodec->describeParameter(tmp, &desc); + env->ReleaseStringUTFChars(name, tmp); + if (status != OK) { + return status; + } + jint type = TYPE_NULL; + switch (desc.type) { + case AMessage::kTypeInt32: type = TYPE_INTEGER; break; + case AMessage::kTypeSize: + case AMessage::kTypeInt64: type = TYPE_LONG; break; + case AMessage::kTypeFloat: type = TYPE_FLOAT; break; + case AMessage::kTypeString: type = TYPE_STRING; break; + case AMessage::kTypeBuffer: type = TYPE_BYTE_BUFFER; break; + default: type = TYPE_NULL; break; + } + if (type == TYPE_NULL) { + return BAD_VALUE; + } + *descObj = env->NewObject(gDescriptorInfo.clazz, gDescriptorInfo.ctorId); + env->SetObjectField(*descObj, gDescriptorInfo.nameId, name); + env->SetIntField(*descObj, gDescriptorInfo.typeId, type); + return OK; +} + +static void BuildVectorFromList(JNIEnv *env, jobject list, std::vector<std::string> *vec) { + ScopedLocalRef<jclass> listClazz{env, env->FindClass("java/util/List")}; + ScopedLocalRef<jclass> iterClazz{env, env->FindClass("java/util/Iterator")}; + jmethodID hasNextID = env->GetMethodID(iterClazz.get(), "hasNext", "()Z"); + jmethodID nextID = env->GetMethodID(iterClazz.get(), "next", "()Ljava/lang/Object;"); + jobject it = env->CallObjectMethod( + list, env->GetMethodID(listClazz.get(), "iterator", "()Ljava/util/Iterator;")); + while (env->CallBooleanMethod(it, hasNextID)) { + jstring name = (jstring)env->CallObjectMethod(it, nextID); + const char *tmp = env->GetStringUTFChars(name, nullptr); + vec->push_back(tmp); + env->ReleaseStringUTFChars(name, tmp); + } +} + +status_t JMediaCodec::subscribeToVendorParameters(JNIEnv *env, jobject namesObj) { + std::vector<std::string> names; + BuildVectorFromList(env, namesObj, &names); + return mCodec->subscribeToVendorParameters(names); +} + +status_t JMediaCodec::unsubscribeFromVendorParameters(JNIEnv *env, jobject namesObj) { + std::vector<std::string> names; + BuildVectorFromList(env, namesObj, &names); + return mCodec->unsubscribeFromVendorParameters(names); +} + static jthrowable createCodecException( JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) { ScopedLocalRef<jclass> clazz( @@ -1058,6 +1159,27 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { env->DeleteLocalRef(obj); } +void JMediaCodec::handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg) { + int32_t arg1 = 0, arg2 = 0; + jobject obj = NULL; + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + sp<AMessage> data; + CHECK(msg->findMessage("data", &data)); + + status_t err = ConvertMessageToMap(env, data, &obj); + if (err != OK) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + env->CallVoidMethod( + mObject, gFields.postEventFromNativeID, + EVENT_FIRST_TUNNEL_FRAME_READY, arg1, arg2, obj); + + env->DeleteLocalRef(obj); +} + void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) { int32_t arg1 = 0, arg2 = 0; jobject obj = NULL; @@ -1100,6 +1222,11 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { } break; } + case kWhatFirstTunnelFrameReady: + { + handleFirstTunnelFrameReadyNotification(msg); + break; + } default: TRESPASS(); } @@ -1256,6 +1383,22 @@ static jint throwExceptionAsNecessary( } } +static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener( + JNIEnv *env, + jobject thiz, + jboolean enabled) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL || codec->initCheck() != OK) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled); + + throwExceptionAsNecessary(env, err); +} + static void android_media_MediaCodec_native_enableOnFrameRenderedListener( JNIEnv *env, jobject thiz, @@ -2616,6 +2759,73 @@ static void android_media_MediaCodec_setAudioPresentation( codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId); } +static jobject android_media_MediaCodec_getSupportedVendorParameters( + JNIEnv *env, jobject thiz) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL || codec->initCheck() != OK) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return NULL; + } + + jobject ret = NULL; + status_t status = codec->querySupportedVendorParameters(env, &ret); + if (status != OK) { + throwExceptionAsNecessary(env, status); + } + + return ret; +} + +static jobject android_media_MediaCodec_getParameterDescriptor( + JNIEnv *env, jobject thiz, jstring name) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL || codec->initCheck() != OK) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return NULL; + } + + jobject ret = NULL; + status_t status = codec->describeParameter(env, name, &ret); + if (status != OK) { + ret = NULL; + } + return ret; +} + +static void android_media_MediaCodec_subscribeToVendorParameters( + JNIEnv *env, jobject thiz, jobject names) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL || codec->initCheck() != OK) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + status_t status = codec->subscribeToVendorParameters(env, names); + if (status != OK) { + throwExceptionAsNecessary(env, status); + } + return; +} + +static void android_media_MediaCodec_unsubscribeFromVendorParameters( + JNIEnv *env, jobject thiz, jobject names) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL || codec->initCheck() != OK) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + status_t status = codec->unsubscribeFromVendorParameters(env, names); + if (status != OK) { + throwExceptionAsNecessary(env, status); + } + return; +} + static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { ScopedLocalRef<jclass> clazz( env, env->FindClass("android/media/MediaCodec")); @@ -2875,6 +3085,10 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { clazz.reset(env->FindClass("java/util/ArrayList")); CHECK(clazz.get() != NULL); + gArrayListInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + gArrayListInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V"); + CHECK(gArrayListInfo.ctorId != NULL); gArrayListInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I"); CHECK(gArrayListInfo.sizeId != NULL); @@ -2905,6 +3119,19 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { gLinearBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;"); CHECK(gLinearBlockInfo.lockId != NULL); + + clazz.reset(env->FindClass("android/media/MediaCodec$ParameterDescriptor")); + CHECK(clazz.get() != NULL); + gDescriptorInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + gDescriptorInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V"); + CHECK(gDescriptorInfo.ctorId != NULL); + + gDescriptorInfo.nameId = env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;"); + CHECK(gDescriptorInfo.nameId != NULL); + + gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I"); + CHECK(gDescriptorInfo.typeId != NULL); } static void android_media_MediaCodec_native_setup( @@ -3138,6 +3365,9 @@ static const JNINativeMethod gMethods[] = { { "native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaCodec_setInputSurface }, + { "native_enableOnFirstTunnelFrameReadyListener", "(Z)V", + (void *)android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener }, + { "native_enableOnFrameRenderedListener", "(Z)V", (void *)android_media_MediaCodec_native_enableOnFrameRenderedListener }, @@ -3231,6 +3461,21 @@ static const JNINativeMethod gMethods[] = { { "native_setAudioPresentation", "(II)V", (void *)android_media_MediaCodec_setAudioPresentation }, + { "native_getSupportedVendorParameters", "()Ljava/util/List;", + (void *)android_media_MediaCodec_getSupportedVendorParameters }, + + { "native_getParameterDescriptor", + "(Ljava/lang/String;)Landroid/media/MediaCodec$ParameterDescriptor;", + (void *)android_media_MediaCodec_getParameterDescriptor }, + + { "native_subscribeToVendorParameters", + "(Ljava/util/List;)V", + (void *)android_media_MediaCodec_subscribeToVendorParameters}, + + { "native_unsubscribeFromVendorParameters", + "(Ljava/util/List;)V", + (void *)android_media_MediaCodec_unsubscribeFromVendorParameters}, + { "native_init", "()V", (void *)android_media_MediaCodec_native_init }, { "native_setup", "(Ljava/lang/String;ZZ)V", diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index f16bcf3c88e4..ee456c9ba82d 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -63,6 +63,8 @@ struct JMediaCodec : public AHandler { void release(); void releaseAsync(); + status_t enableOnFirstTunnelFrameReadyListener(jboolean enable); + status_t enableOnFrameRenderedListener(jboolean enable); status_t setCallback(jobject cb); @@ -162,6 +164,14 @@ struct JMediaCodec : public AHandler { void selectAudioPresentation(const int32_t presentationId, const int32_t programId); + status_t querySupportedVendorParameters(JNIEnv *env, jobject *names); + + status_t describeParameter(JNIEnv *env, jstring name, jobject *desc); + + status_t subscribeToVendorParameters(JNIEnv *env, jobject names); + + status_t unsubscribeFromVendorParameters(JNIEnv *env, jobject names); + bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; } const sp<ICrypto> &getCrypto() { return mCrypto; } @@ -176,6 +186,7 @@ private: kWhatCallbackNotify, kWhatFrameRendered, kWhatAsyncReleaseComplete, + kWhatFirstTunnelFrameReady, }; jclass mClass; @@ -191,6 +202,7 @@ private: std::once_flag mAsyncReleaseFlag; sp<AMessage> mCallbackNotification; + sp<AMessage> mOnFirstTunnelFrameReadyNotification; sp<AMessage> mOnFrameRenderedNotification; status_t mInitStatus; @@ -203,6 +215,7 @@ private: jobject *buf) const; void handleCallback(const sp<AMessage> &msg); + void handleFirstTunnelFrameReadyNotification(const sp<AMessage> &msg); void handleFrameRenderedNotification(const sp<AMessage> &msg); DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec); diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml index fc96fd926e2d..3794ccddb48f 100644 --- a/media/packages/BluetoothMidiService/AndroidManifest.xml +++ b/media/packages/BluetoothMidiService/AndroidManifest.xml @@ -27,6 +27,9 @@ <uses-feature android:name="android.software.midi" android:required="true"/> <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <application tools:replace="android:label" diff --git a/media/tests/ScoAudioTest/AndroidManifest.xml b/media/tests/ScoAudioTest/AndroidManifest.xml index a0fba733370e..5af77ee9d35d 100644 --- a/media/tests/ScoAudioTest/AndroidManifest.xml +++ b/media/tests/ScoAudioTest/AndroidManifest.xml @@ -22,6 +22,9 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.BROADCAST_STICKY"/> <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <application> <activity android:label="@string/app_name" diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 85513cad489c..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 @@ -287,6 +290,8 @@ LIBANDROID { android_getaddrinfofornetwork; # introduced=23 android_getprocnetwork; # introduced=31 android_setprocnetwork; # introduced=23 + android_getprocdns; # introduced=31 + android_setprocdns; # introduced=31 android_setsocknetwork; # introduced=23 android_res_cancel; # introduced=29 android_res_nquery; # introduced=29 diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt index cc8dd727408f..a6c1b5098066 100644 --- a/native/android/libandroid_net.map.txt +++ b/native/android/libandroid_net.map.txt @@ -16,6 +16,8 @@ LIBANDROID_NET { android_res_nsend; # llndk # These functions have been part of the NDK since API 31. android_getprocnetwork; # llndk + android_setprocdns; # llndk + android_getprocdns; # llndk local: *; }; diff --git a/native/android/net.c b/native/android/net.c index d4b888845b27..e2f36a77b7c6 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -90,6 +90,38 @@ int android_getprocnetwork(net_handle_t *network) { return 0; } +int android_setprocdns(net_handle_t network) { + unsigned netid; + if (!getnetidfromhandle(network, &netid)) { + errno = EINVAL; + return -1; + } + + int rval = setNetworkForResolv(netid); + if (rval < 0) { + errno = -rval; + rval = -1; + } + return rval; +} + +int android_getprocdns(net_handle_t *network) { + if (network == NULL) { + errno = EINVAL; + return -1; + } + + unsigned netid; + int rval = getNetworkForDns(&netid); + if (rval < 0) { + errno = -rval; + return -1; + } + + *network = gethandlefromnetid(netid); + return 0; +} + int android_getaddrinfofornetwork(net_handle_t network, const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { 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/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index f9795c601431..d36836c1af19 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -25,6 +25,8 @@ <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index 3553c1f05310..fdfb1086de0b 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", }, @@ -81,11 +82,13 @@ filegroup { java_sdk_library { name: "framework-connectivity", - api_only: true, + sdk_version: "module_current", + min_sdk_version: "30", defaults: ["framework-module-defaults"], installable: true, srcs: [ ":framework-connectivity-sources", + ":net-utils-framework-common-srcs", ], aidl: { include_dirs: [ @@ -97,10 +100,34 @@ java_sdk_library { "frameworks/native/aidl/binder", // For PersistableBundle.aidl ], }, + impl_only_libs: [ + // TODO (b/183097033) remove once module_current includes core_platform + "stable.core.platform.api.stubs", + "framework-tethering.stubs.module_lib", + "framework-wifi.stubs.module_lib", + "net-utils-device-common", + ], libs: [ "unsupportedappusage", ], + static_libs: [ + "framework-connectivity-protos", + ], + jarjar_rules: "jarjar-rules.txt", permitted_packages: ["android.net"], + impl_library_visibility: [ + "//packages/modules/Connectivity/Tethering/apex", + // In preparation for future move + "//packages/modules/Connectivity/apex", + "//packages/modules/Connectivity/service", + "//frameworks/base/packages/Connectivity/service", + "//frameworks/base", + "//packages/modules/Connectivity/Tethering/tests/unit", + ], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], } cc_defaults { @@ -109,13 +136,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,43 +166,15 @@ 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", ], } - -java_library { - name: "framework-connectivity.impl", - sdk_version: "module_current", - srcs: [ - ":framework-connectivity-sources", - ], - aidl: { - include_dirs: [ - "frameworks/base/core/java", // For framework parcelables - "frameworks/native/aidl/binder", // For PersistableBundle.aidl - ], - }, - libs: [ - // TODO (b/183097033) remove once module_current includes core_current - "stable.core.platform.api.stubs", - "framework-tethering", - "framework-wifi", - "unsupportedappusage", - ], - static_libs: [ - "framework-connectivity-protos", - "net-utils-device-common", - ], - jarjar_rules: "jarjar-rules.txt", - apex_available: ["com.android.tethering"], - installable: true, - permitted_packages: ["android.net"], -} diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt index ad44b27f6d0b..0a9560a5c56d 100644 --- a/packages/Connectivity/framework/api/current.txt +++ b/packages/Connectivity/framework/api/current.txt @@ -68,6 +68,7 @@ package android.net { method public boolean bindProcessToNetwork(@Nullable android.net.Network); method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); + method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public android.net.Network getActiveNetworkForUid(int); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); @@ -387,7 +388,9 @@ package android.net { public class NetworkRequest implements android.os.Parcelable { method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); method public int describeContents(); + method @NonNull public int[] getCapabilities(); method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method @NonNull public int[] getTransportTypes(); method public boolean hasCapability(int); method public boolean hasTransport(int); method public void writeToParcel(android.os.Parcel, int); diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt index b179c6da294d..94845bf590c7 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -11,6 +11,7 @@ package android.net { method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange(); method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress); @@ -19,6 +20,7 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean); + method public static void setPrivateDnsMode(@NonNull android.content.Context, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); @@ -27,6 +29,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"; @@ -34,11 +44,57 @@ package android.net { field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 } + public class ConnectivitySettingsManager { + method public static void clearGlobalProxy(@NonNull android.content.Context); + method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context); + method public static int getCaptivePortalMode(@NonNull android.content.Context, int); + method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method @NonNull public static android.util.Range<java.lang.Integer> getDnsResolverSampleRanges(@NonNull android.content.Context); + method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int); + method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context); + method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean); + method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context); + method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context); + method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int); + method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context); + method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context); + method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean); + method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String); + method public static void setCaptivePortalMode(@NonNull android.content.Context, int); + method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range<java.lang.Integer>); + method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int); + method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo); + method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean); + method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int); + method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String); + method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int); + method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull String); + method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String); + method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean); + method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2 + field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0 + field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1 + field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2 + field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0 + field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1 + } + public final class NetworkAgentConfig implements android.os.Parcelable { method @Nullable public String getSubscriberId(); + method public boolean isBypassableVpn(); } public static final class NetworkAgentConfig.Builder { + method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); } @@ -59,6 +115,7 @@ package android.net { } public class NetworkRequest implements android.os.Parcelable { + method @NonNull public int[] getUnwantedCapabilities(); method public boolean hasUnwantedCapability(int); } diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index c19fcdd2a728..593698e097cd 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -212,11 +212,14 @@ package android.net { public abstract class NetworkAgent { ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); + ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); method @Nullable public android.net.Network getNetwork(); method public void markConnected(); method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); method public void onAutomaticReconnectDisabled(); method public void onBandwidthUpdateRequested(); + method public void onNetworkCreated(); + method public void onNetworkDisconnected(); method public void onNetworkUnwanted(); method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); method public void onQosCallbackUnregistered(int); @@ -231,8 +234,8 @@ package android.net { method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); method public final void sendNetworkScore(@IntRange(from=0, to=99) int); method public final void sendQosCallbackError(int, int); - method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); - method public final void sendQosSessionLost(int, int); + method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes); + method public final void sendQosSessionLost(int, int, int); method public final void sendSocketKeepaliveEvent(int, int); method @Deprecated public void setLegacySubtype(int, @NonNull String); method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); @@ -269,6 +272,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(); @@ -323,6 +327,19 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); } + public final class NetworkScore implements android.os.Parcelable { + method public int describeContents(); + method public int getLegacyInt(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR; + } + + public static final class NetworkScore.Builder { + ctor public NetworkScore.Builder(); + method @NonNull public android.net.NetworkScore build(); + method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int); + } + public final class OemNetworkPreferences implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences(); @@ -370,6 +387,7 @@ package android.net { method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR; field public static final int TYPE_EPS_BEARER = 1; // 0x1 + field public static final int TYPE_NR_BEARER = 2; // 0x2 } public interface QosSessionAttributes { diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp index c7c0beee5ba2..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,19 +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 jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId) -{ - return (jboolean) !queryUserAccess(uid, netId); +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) @@ -137,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); @@ -147,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); @@ -157,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); @@ -177,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); @@ -201,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) { @@ -266,13 +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 }, - { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess }, + { "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 f2078307a59a..a9f8b8dda340 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; @@ -1083,8 +1171,7 @@ public class ConnectivityManager { * * @return a {@link Network} object for the current default network for the * given UID or {@code null} if no default network is currently active - * - * @hide + * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) @Nullable @@ -3235,7 +3322,61 @@ 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. A callback identifies an offer + * uniquely, and later calls with the same callback update the offer. 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, @@ -3705,8 +3846,9 @@ public class ConnectivityManager { private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>(); private static CallbackHandler sCallbackHandler; - private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, - int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + private NetworkRequest sendRequestForNetwork(int asUid, NetworkCapabilities need, + NetworkCallback callback, int timeoutMs, NetworkRequest.Type reqType, int legacyType, + CallbackHandler handler) { printStackTrace(); checkCallbackNotNull(callback); if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) { @@ -3731,8 +3873,8 @@ public class ConnectivityManager { getAttributionTag()); } else { request = mService.requestNetwork( - need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, - callbackFlags, callingPackageName, getAttributionTag()); + asUid, need, reqType.ordinal(), messenger, timeoutMs, binder, + legacyType, callbackFlags, callingPackageName, getAttributionTag()); } if (request != null) { sCallbacks.put(request, callback); @@ -3747,6 +3889,12 @@ public class ConnectivityManager { return request; } + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, + int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType, + legacyType, handler); + } + /** * Helper function to request a network with a particular legacy type. * @@ -4232,8 +4380,40 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + registerDefaultNetworkCallbackAsUid(Process.INVALID_UID, networkCallback, handler); + } + + /** + * Registers to receive notifications about changes in the default network for the specified + * UID. This may be a physical network or a virtual network, such as a VPN that applies to the + * UID. The callbacks will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param uid the UID for which to track default network changes. + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * UID's default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void registerDefaultNetworkCallbackAsUid(int uid, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { CallbackHandler cbHandler = new CallbackHandler(handler); - sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + sendRequestForNetwork(uid, null /* need */, networkCallback, 0 /* timeoutMs */, TRACK_DEFAULT, TYPE_NONE, cbHandler); } @@ -5247,4 +5427,23 @@ public class ConnectivityManager { if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC; return mode; } + + /** + * Set private DNS mode to settings. + * + * @param context The {@link Context} to set the private DNS mode. + * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static void setPrivateDnsMode(@NonNull Context context, + @NonNull @PrivateDnsMode String mode) { + if (!(mode == PRIVATE_DNS_MODE_OFF + || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC + || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + throw new IllegalArgumentException("Invalid private dns mode"); + } + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, mode); + } } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java index bbd83931ee0d..e133d5e53f42 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java @@ -16,16 +16,38 @@ package android.net; +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER; +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE; +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY; +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; + import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.net.ConnectivityManager.MultipathPreference; +import android.net.ConnectivityManager.PrivateDnsMode; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Range; + +import com.android.net.module.util.ProxyUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Duration; +import java.util.List; /** * A manager class for connectivity module settings. * * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public class ConnectivitySettingsManager { private ConnectivitySettingsManager() {} @@ -45,12 +67,16 @@ public class ConnectivitySettingsManager { * Network activity refers to transmitting or receiving data on the network interfaces. * * Tracking is disabled if set to zero or negative value. + * + * @hide */ public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile"; /** * Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE} * but for Wifi network. + * + * @hide */ public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi"; @@ -58,12 +84,16 @@ public class ConnectivitySettingsManager { /** * Sample validity in seconds to configure for the system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS = "dns_resolver_sample_validity_seconds"; /** * Success threshold in percent for use with the system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT = "dns_resolver_success_threshold_percent"; @@ -71,24 +101,35 @@ public class ConnectivitySettingsManager { /** * Minimum number of samples needed for statistics to be considered meaningful in the * system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples"; /** * Maximum number taken into account for statistics purposes in the system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples"; + private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8; + private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64; + /** Network switch notification settings */ /** * The maximum number of notifications shown in 24 hours when switching networks. + * + * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT = "network_switch_notification_daily_limit"; /** * The minimum time in milliseconds between notifications when switching networks. + * + * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS = "network_switch_notification_rate_limit_millis"; @@ -98,14 +139,18 @@ public class ConnectivitySettingsManager { /** * The URL used for HTTP captive portal detection upon a new connection. * A 204 response code from the server is used for validation. + * + * @hide */ public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url"; /** * What to do when connecting a network that presents a captive portal. - * Must be one of the CAPTIVE_PORTAL_MODE_* constants above. + * Must be one of the CAPTIVE_PORTAL_MODE_* constants below. * * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT. + * + * @hide */ public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode"; @@ -139,11 +184,15 @@ public class ConnectivitySettingsManager { /** * Host name for global http proxy. Set via ConnectivityManager. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host"; /** * Integer host port for global http proxy. Set via ConnectivityManager. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port"; @@ -153,12 +202,16 @@ public class ConnectivitySettingsManager { * Domains should be listed in a comma- separated list. Example of * acceptable formats: ".domain1.com,my.domain2.com" Use * ConnectivityManager to set/get. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST = "global_http_proxy_exclusion_list"; /** * The location PAC File for the proxy. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url"; @@ -171,11 +224,15 @@ public class ConnectivitySettingsManager { * a specific provider. It may be used to store the provider name even when the * mode changes so that temporarily disabling and re-enabling the specific * provider mode does not necessitate retyping the provider hostname. + * + * @hide */ public static final String PRIVATE_DNS_MODE = "private_dns_mode"; /** * The specific Private DNS provider name. + * + * @hide */ public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier"; @@ -185,6 +242,8 @@ public class ConnectivitySettingsManager { * all of which require explicit user action to enable/configure. See also b/79719289. * * Value is a string, suitable for assignment to PRIVATE_DNS_MODE above. + * + * @hide */ public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode"; @@ -194,6 +253,8 @@ public class ConnectivitySettingsManager { * The number of milliseconds to hold on to a PendingIntent based request. This delay gives * the receivers of the PendingIntent an opportunity to make a new network request before * the Network satisfying the request is potentially removed. + * + * @hide */ public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS = "connectivity_release_pending_intent_delay_ms"; @@ -205,6 +266,8 @@ public class ConnectivitySettingsManager { * See ConnectivityService for more info. * * (0 = disabled, 1 = enabled) + * + * @hide */ public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; @@ -217,6 +280,8 @@ public class ConnectivitySettingsManager { * See ConnectivityService for more info. * * (0 = disabled, 1 = enabled) + * + * @hide */ public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested"; @@ -228,14 +293,604 @@ public class ConnectivitySettingsManager { * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013. * null: Ask the user whether to switch away from bad wifi. * 1: Avoid bad wifi. + * + * @hide */ public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi"; /** + * Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013. + */ + public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; + + /** + * Ask the user whether to switch away from bad wifi. + */ + public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; + + /** + * Avoid bad wifi. + */ + public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + NETWORK_AVOID_BAD_WIFI_IGNORE, + NETWORK_AVOID_BAD_WIFI_PROMPT, + NETWORK_AVOID_BAD_WIFI_AVOID, + }) + public @interface NetworkAvoidBadWifi {} + + /** * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be * overridden by the system based on device or application state. If null, the value * specified by config_networkMeteredMultipathPreference is used. + * + * @hide */ public static final String NETWORK_METERED_MULTIPATH_PREFERENCE = "network_metered_multipath_preference"; + + /** + * Get mobile data activity timeout from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default timeout if no setting value. + * @return The {@link Duration} of timeout to track mobile data activity. + */ + @NonNull + public static Duration getMobileDataActivityTimeout(@NonNull Context context, + @NonNull Duration def) { + final int timeout = Settings.Global.getInt( + context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, (int) def.getSeconds()); + return Duration.ofSeconds(timeout); + } + + /** + * Set mobile data activity timeout to {@link Settings}. + * Tracking is disabled if set to zero or negative value. + * + * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be + * ignored. + * + * @param context The {@link Context} to set the setting. + * @param timeout The mobile data activity timeout. + */ + public static void setMobileDataActivityTimeout(@NonNull Context context, + @NonNull Duration timeout) { + Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, + (int) timeout.getSeconds()); + } + + /** + * Get wifi data activity timeout from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default timeout if no setting value. + * @return The {@link Duration} of timeout to track wifi data activity. + */ + @NonNull + public static Duration getWifiDataActivityTimeout(@NonNull Context context, + @NonNull Duration def) { + final int timeout = Settings.Global.getInt( + context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, (int) def.getSeconds()); + return Duration.ofSeconds(timeout); + } + + /** + * Set wifi data activity timeout to {@link Settings}. + * Tracking is disabled if set to zero or negative value. + * + * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be + * ignored. + * + * @param context The {@link Context} to set the setting. + * @param timeout The wifi data activity timeout. + */ + public static void setWifiDataActivityTimeout(@NonNull Context context, + @NonNull Duration timeout) { + Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, + (int) timeout.getSeconds()); + } + + /** + * Get dns resolver sample validity duration from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default duration if no setting value. + * @return The {@link Duration} of sample validity duration to configure for the system DNS + * resolver. + */ + @NonNull + public static Duration getDnsResolverSampleValidityDuration(@NonNull Context context, + @NonNull Duration def) { + final int duration = Settings.Global.getInt(context.getContentResolver(), + DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, (int) def.getSeconds()); + return Duration.ofSeconds(duration); + } + + /** + * Set dns resolver sample validity duration to {@link Settings}. The duration must be a + * positive number of seconds. + * + * @param context The {@link Context} to set the setting. + * @param duration The sample validity duration. + */ + public static void setDnsResolverSampleValidityDuration(@NonNull Context context, + @NonNull Duration duration) { + final int time = (int) duration.getSeconds(); + if (time <= 0) { + throw new IllegalArgumentException("Invalid duration"); + } + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, time); + } + + /** + * Get dns resolver success threshold percent from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return The success threshold in percent for use with the system DNS resolver. + */ + public static int getDnsResolverSuccessThresholdPercent(@NonNull Context context, int def) { + return Settings.Global.getInt( + context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, def); + } + + /** + * Set dns resolver success threshold percent to {@link Settings}. The threshold percent must + * be 0~100. + * + * @param context The {@link Context} to set the setting. + * @param percent The success threshold percent. + */ + public static void setDnsResolverSuccessThresholdPercent(@NonNull Context context, + @IntRange(from = 0, to = 100) int percent) { + if (percent < 0 || percent > 100) { + throw new IllegalArgumentException("Percent must be 0~100"); + } + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, percent); + } + + /** + * Get dns resolver samples range from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The {@link Range<Integer>} of samples needed for statistics to be considered + * meaningful in the system DNS resolver. + */ + @NonNull + public static Range<Integer> getDnsResolverSampleRanges(@NonNull Context context) { + final int minSamples = Settings.Global.getInt(context.getContentResolver(), + DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES); + final int maxSamples = Settings.Global.getInt(context.getContentResolver(), + DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES); + return new Range<>(minSamples, maxSamples); + } + + /** + * Set dns resolver samples range to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param range The samples range. The minimum number should be more than 0 and the maximum + * number should be less that 64. + */ + public static void setDnsResolverSampleRanges(@NonNull Context context, + @NonNull Range<Integer> range) { + if (range.getLower() < 0 || range.getUpper() > 64) { + throw new IllegalArgumentException("Argument must be 0~64"); + } + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_MIN_SAMPLES, range.getLower()); + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_MAX_SAMPLES, range.getUpper()); + } + + /** + * Get maximum count (from {@link Settings}) of switching network notifications shown in 24 + * hours. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return The maximum count of notifications shown in 24 hours when switching networks. + */ + public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context, + int def) { + return Settings.Global.getInt( + context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, def); + } + + /** + * Set maximum count (to {@link Settings}) of switching network notifications shown in 24 hours. + * The count must be at least 0. + * + * @param context The {@link Context} to set the setting. + * @param count The maximum count of switching network notifications shown in 24 hours. + */ + public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context, + @IntRange(from = 0) int count) { + if (count < 0) { + throw new IllegalArgumentException("Count must be 0~10."); + } + Settings.Global.putInt( + context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count); + } + + /** + * Get minimum duration (from {@link Settings}) between each switching network notifications. + * + * @param context The {@link Context} to query the setting. + * @param def The default time if no setting value. + * @return The minimum duration between notifications when switching networks. + */ + @NonNull + public static Duration getNetworkSwitchNotificationRateDuration(@NonNull Context context, + @NonNull Duration def) { + final int duration = Settings.Global.getInt(context.getContentResolver(), + NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, (int) def.toMillis()); + return Duration.ofMillis(duration); + } + + /** + * Set minimum duration (to {@link Settings}) between each switching network notifications. + * + * @param context The {@link Context} to set the setting. + * @param duration The minimum duration between notifications when switching networks. + */ + public static void setNetworkSwitchNotificationRateDuration(@NonNull Context context, + @NonNull Duration duration) { + final int time = (int) duration.toMillis(); + if (time < 0) { + throw new IllegalArgumentException("Invalid duration."); + } + Settings.Global.putInt(context.getContentResolver(), + NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, time); + } + + /** + * Get URL (from {@link Settings}) used for HTTP captive portal detection upon a new connection. + * + * @param context The {@link Context} to query the setting. + * @return The URL used for HTTP captive portal detection upon a new connection. + */ + @Nullable + public static String getCaptivePortalHttpUrl(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL); + } + + /** + * Set URL (to {@link Settings}) used for HTTP captive portal detection upon a new connection. + * This URL should respond with a 204 response to a GET request to indicate no captive portal is + * present. And this URL must be HTTP as redirect responses are used to find captive portal + * sign-in pages. If the URL set to null or be incorrect, it will result in captive portal + * detection failed and lost the connection. + * + * @param context The {@link Context} to set the setting. + * @param url The URL used for HTTP captive portal detection upon a new connection. + */ + public static void setCaptivePortalHttpUrl(@NonNull Context context, @Nullable String url) { + Settings.Global.putString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL, url); + } + + /** + * Get mode (from {@link Settings}) when connecting a network that presents a captive portal. + * + * @param context The {@link Context} to query the setting. + * @param def The default mode if no setting value. + * @return The mode when connecting a network that presents a captive portal. + */ + @CaptivePortalMode + public static int getCaptivePortalMode(@NonNull Context context, + @CaptivePortalMode int def) { + return Settings.Global.getInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, def); + } + + /** + * Set mode (to {@link Settings}) when connecting a network that presents a captive portal. + * + * @param context The {@link Context} to set the setting. + * @param mode The mode when connecting a network that presents a captive portal. + */ + public static void setCaptivePortalMode(@NonNull Context context, @CaptivePortalMode int mode) { + if (!(mode == CAPTIVE_PORTAL_MODE_IGNORE + || mode == CAPTIVE_PORTAL_MODE_PROMPT + || mode == CAPTIVE_PORTAL_MODE_AVOID)) { + throw new IllegalArgumentException("Invalid captive portal mode"); + } + Settings.Global.putInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, mode); + } + + /** + * Get the global HTTP proxy applied to the device, or null if none. + * + * @param context The {@link Context} to query the setting. + * @return The {@link ProxyInfo} which build from global http proxy settings. + */ + @Nullable + public static ProxyInfo getGlobalProxy(@NonNull Context context) { + final String host = Settings.Global.getString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST); + final int port = Settings.Global.getInt( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* def */); + final String exclusionList = Settings.Global.getString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST); + final String pacFileUrl = Settings.Global.getString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC); + + if (TextUtils.isEmpty(host) && TextUtils.isEmpty(pacFileUrl)) { + return null; // No global proxy. + } + + if (TextUtils.isEmpty(pacFileUrl)) { + return ProxyInfo.buildDirectProxy( + host, port, ProxyUtils.exclusionStringAsList(exclusionList)); + } else { + return ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl)); + } + } + + /** + * Set global http proxy settings from given {@link ProxyInfo}. + * + * @param context The {@link Context} to set the setting. + * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from + * {@link ProxyInfo#buildPacProxy(Uri)} or + * {@link ProxyInfo#buildDirectProxy(String, int, List)} + */ + public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) { + final String host = proxyInfo.getHost(); + final int port = proxyInfo.getPort(); + final String exclusionList = proxyInfo.getExclusionListAsString(); + final String pacFileUrl = proxyInfo.getPacFileUrl().toString(); + + if (TextUtils.isEmpty(pacFileUrl)) { + Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host); + Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclusionList); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */); + } else { + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, pacFileUrl); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */); + Settings.Global.putInt( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */); + } + } + + /** + * Clear all global http proxy settings. + * + * @param context The {@link Context} to set the setting. + */ + public static void clearGlobalProxy(@NonNull Context context) { + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */); + Settings.Global.putInt( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */); + } + + /** + * Get specific private dns provider name from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The specific private dns provider name, or null if no setting value. + */ + @Nullable + public static String getPrivateDnsHostname(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER); + } + + /** + * Set specific private dns provider name to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param specifier The specific private dns provider name. + */ + public static void setPrivateDnsHostname(@NonNull Context context, + @Nullable String specifier) { + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier); + } + + /** + * Get default private dns mode from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The default private dns mode. + */ + @PrivateDnsMode + @NonNull + public static String getPrivateDnsDefaultMode(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE); + } + + /** + * Set default private dns mode to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param mode The default private dns mode. This should be one of the PRIVATE_DNS_MODE_* + * constants. + */ + public static void setPrivateDnsDefaultMode(@NonNull Context context, + @NonNull @PrivateDnsMode String mode) { + if (!(mode == PRIVATE_DNS_MODE_OFF + || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC + || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + throw new IllegalArgumentException("Invalid private dns mode"); + } + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE, mode); + } + + /** + * Get duration (from {@link Settings}) to keep a PendingIntent-based request. + * + * @param context The {@link Context} to query the setting. + * @param def The default duration if no setting value. + * @return The duration to keep a PendingIntent-based request. + */ + @NonNull + public static Duration getConnectivityKeepPendingIntentDuration(@NonNull Context context, + @NonNull Duration def) { + final int duration = Settings.Secure.getInt(context.getContentResolver(), + CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, (int) def.toMillis()); + return Duration.ofMillis(duration); + } + + /** + * Set duration (to {@link Settings}) to keep a PendingIntent-based request. + * + * @param context The {@link Context} to set the setting. + * @param duration The duration to keep a PendingIntent-based request. + */ + public static void setConnectivityKeepPendingIntentDuration(@NonNull Context context, + @NonNull Duration duration) { + final int time = (int) duration.toMillis(); + if (time < 0) { + throw new IllegalArgumentException("Invalid duration."); + } + Settings.Secure.putInt( + context.getContentResolver(), CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, time); + } + + /** + * Read from {@link Settings} whether the mobile data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return Whether the mobile data connection should remain active even when higher + * priority networks are active. + */ + public static boolean getMobileDataAlwaysOn(@NonNull Context context, boolean def) { + final int enable = Settings.Global.getInt( + context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (def ? 1 : 0)); + return (enable != 0) ? true : false; + } + + /** + * Write into {@link Settings} whether the mobile data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to set the setting. + * @param enable Whether the mobile data connection should remain active even when higher + * priority networks are active. + */ + public static void setMobileDataAlwaysOn(@NonNull Context context, boolean enable) { + Settings.Global.putInt( + context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (enable ? 1 : 0)); + } + + /** + * Read from {@link Settings} whether the wifi data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return Whether the wifi data connection should remain active even when higher + * priority networks are active. + */ + public static boolean getWifiAlwaysRequested(@NonNull Context context, boolean def) { + final int enable = Settings.Global.getInt( + context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (def ? 1 : 0)); + return (enable != 0) ? true : false; + } + + /** + * Write into {@link Settings} whether the wifi data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to set the setting. + * @param enable Whether the wifi data connection should remain active even when higher + * priority networks are active + */ + public static void setWifiAlwaysRequested(@NonNull Context context, boolean enable) { + Settings.Global.putInt( + context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (enable ? 1 : 0)); + } + + /** + * Get avoid bad wifi setting from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The setting whether to automatically switch away from wifi networks that lose + * internet access. + */ + @NetworkAvoidBadWifi + public static int getNetworkAvoidBadWifi(@NonNull Context context) { + final String setting = + Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI); + if ("0".equals(setting)) { + return NETWORK_AVOID_BAD_WIFI_IGNORE; + } else if ("1".equals(setting)) { + return NETWORK_AVOID_BAD_WIFI_AVOID; + } else { + return NETWORK_AVOID_BAD_WIFI_PROMPT; + } + } + + /** + * Set avoid bad wifi setting to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param value Whether to automatically switch away from wifi networks that lose internet + * access. + */ + public static void setNetworkAvoidBadWifi(@NonNull Context context, + @NetworkAvoidBadWifi int value) { + final String setting; + if (value == NETWORK_AVOID_BAD_WIFI_IGNORE) { + setting = "0"; + } else if (value == NETWORK_AVOID_BAD_WIFI_AVOID) { + setting = "1"; + } else if (value == NETWORK_AVOID_BAD_WIFI_PROMPT) { + setting = null; + } else { + throw new IllegalArgumentException("Invalid avoid bad wifi setting"); + } + Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting); + } + + /** + * Get network metered multipath preference from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The network metered multipath preference which should be one of + * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value specified + * by config_networkMeteredMultipathPreference is used. + */ + @Nullable + public static String getNetworkMeteredMultipathPreference(@NonNull Context context) { + return Settings.Global.getString( + context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE); + } + + /** + * Set network metered multipath preference to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param preference The network metered multipath preference which should be one of + * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value + * specified by config_networkMeteredMultipathPreference is used. + */ + public static void setNetworkMeteredMultipathPreference(@NonNull Context context, + @NonNull @MultipathPreference String preference) { + if (!(Integer.valueOf(preference) == MULTIPATH_PREFERENCE_HANDOVER + || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_RELIABILITY + || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_PERFORMANCE)) { + throw new IllegalArgumentException("Invalid private dns mode"); + } + Settings.Global.putString( + context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference); + } } diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 3300fa8fd12a..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; @@ -142,7 +143,7 @@ interface IConnectivityManager in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config, in int factorySerialNumber); - NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, + NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType, in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, int callbackFlags, String callingPackageName, String callingAttributionTag); @@ -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/INetworkAgent.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl index 1f66e18717d8..f9d399459ebd 100644 --- a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl +++ b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl @@ -46,4 +46,6 @@ oneway interface INetworkAgent { void onRemoveKeepalivePacketFilter(int slot); void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel); void onQosCallbackUnregistered(int qosCallbackId); + void onNetworkCreated(); + void onNetworkDisconnected(); } diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl index c5464d32412b..cbd6193744b9 100644 --- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl @@ -22,6 +22,7 @@ import android.net.NetworkInfo; import android.net.NetworkScore; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; /** * Interface for NetworkAgents to send network properties. @@ -37,6 +38,7 @@ oneway interface INetworkAgentRegistry { void sendSocketKeepaliveEvent(int slot, int reason); void sendUnderlyingNetworks(in @nullable List<Network> networks); void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes); + void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes); void sendQosSessionLost(int qosCallbackId, in QosSession session); void sendQosCallbackError(int qosCallbackId, int exceptionType); } 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..a6de173fa33e --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl @@ -0,0 +1,63 @@ +/* + * 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 network for this offer is needed to satisfy some application or + * system component, connectivity will call onNetworkNeeded on this callback. + * When this happens, the provider should try and bring up the network. + * + * When the network for this 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 onNetworkUnneeded. 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. + * onNetworkNeeded 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 supply the best network for it. + * + * @hide + */ +oneway interface INetworkOfferCallback { + /** + * Called when a network for this 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 onNetworkNeeded(in NetworkRequest networkRequest, int providerId); + + /** + * Informs the registrant that the offer is no longer valuable to fulfill this request. + */ + void onNetworkUnneeded(in NetworkRequest networkRequest); +} diff --git a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl index 91c75759f85c..c9735419f7dd 100644 --- a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl +++ b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl @@ -19,6 +19,7 @@ package android.net; import android.os.Bundle; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; /** * AIDL interface for QosCallback @@ -29,6 +30,8 @@ oneway interface IQosCallback { void onQosEpsBearerSessionAvailable(in QosSession session, in EpsBearerQosSessionAttributes attributes); + void onNrQosSessionAvailable(in QosSession session, + in NrQosSessionAttributes attributes); void onQosSessionLost(in QosSession session); void onError(in int type); } diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java index 0741414ab3aa..1f490337de93 100644 --- a/packages/Connectivity/framework/src/android/net/Network.java +++ b/packages/Connectivity/framework/src/android/net/Network.java @@ -27,7 +27,6 @@ import android.os.Parcelable; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; @@ -93,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. @@ -190,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; } @@ -453,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 */); } /** @@ -486,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 @@ -526,11 +527,4 @@ public class Network implements Parcelable { public String toString() { return Integer.toString(netId); } - - /** @hide */ - public void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - proto.write(NetworkProto.NET_ID, netId); - proto.end(token); - } } diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java index 1ff0140006b9..6b55bb771c30 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java @@ -32,6 +32,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -361,6 +362,22 @@ public abstract class NetworkAgent { */ public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21; + /** + * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native + * network was created and the Network object is now valid. + * + * @hide + */ + public static final int CMD_NETWORK_CREATED = BASE + 22; + + /** + * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native + * network was destroyed. + * + * @hide + */ + public static final int CMD_NETWORK_DISCONNECTED = BASE + 23; + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType, config.legacyTypeName, config.legacySubTypeName); @@ -389,7 +406,6 @@ public abstract class NetworkAgent { * @param score the initial score of this network. Update with sendNetworkScore. * @param config an immutable {@link NetworkAgentConfig} for this agent. * @param provider the {@link NetworkProvider} managing this agent. - * @hide TODO : unhide when impl is complete */ public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, @@ -561,6 +577,14 @@ public abstract class NetworkAgent { msg.arg1 /* QoS callback id */); break; } + case CMD_NETWORK_CREATED: { + onNetworkCreated(); + break; + } + case CMD_NETWORK_DISCONNECTED: { + onNetworkDisconnected(); + break; + } } } } @@ -701,6 +725,16 @@ public abstract class NetworkAgent { mHandler.sendMessage(mHandler.obtainMessage( CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null)); } + + @Override + public void onNetworkCreated() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_CREATED)); + } + + @Override + public void onNetworkDisconnected() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DISCONNECTED)); + } } /** @@ -1011,6 +1045,17 @@ public abstract class NetworkAgent { } /** + * Called when ConnectivityService has successfully created this NetworkAgent's native network. + */ + public void onNetworkCreated() {} + + + /** + * Called when ConnectivityService has successfully destroy this NetworkAgent's native network. + */ + public void onNetworkDisconnected() {} + + /** * Requests that the network hardware send the specified packet at the specified interval. * * @param slot the hardware slot on which to start the keepalive. @@ -1161,29 +1206,37 @@ public abstract class NetworkAgent { /** - * Sends the attributes of Eps Bearer Qos Session back to the Application + * Sends the attributes of Qos Session back to the Application * * @param qosCallbackId the callback id that the session belongs to - * @param sessionId the unique session id across all Eps Bearer Qos Sessions - * @param attributes the attributes of the Eps Qos Session + * @param sessionId the unique session id across all Qos Sessions + * @param attributes the attributes of the Qos Session */ public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId, - @NonNull final EpsBearerQosSessionAttributes attributes) { + @NonNull final QosSessionAttributes attributes) { Objects.requireNonNull(attributes, "The attributes must be non-null"); - queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, - new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), - attributes)); + if (attributes instanceof EpsBearerQosSessionAttributes) { + queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), + (EpsBearerQosSessionAttributes)attributes)); + } else if (attributes instanceof NrQosSessionAttributes) { + queueOrSendMessage(ra -> ra.sendNrQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_NR_BEARER), + (NrQosSessionAttributes)attributes)); + } } /** - * Sends event that the Eps Qos Session was lost. + * Sends event that the Qos Session was lost. * * @param qosCallbackId the callback id that the session belongs to - * @param sessionId the unique session id across all Eps Bearer Qos Sessions + * @param sessionId the unique session id across all Qos Sessions + * @param qosSessionType the session type {@code QosSesson#QosSessionType} */ - public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) { + public final void sendQosSessionLost(final int qosCallbackId, + final int sessionId, final int qosSessionType) { queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId, - new QosSession(sessionId, QosSession.TYPE_EPS_BEARER))); + new QosSession(sessionId, qosSessionType))); } /** diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java index fb6fcc127406..3f058d8cbf12 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java @@ -64,6 +64,16 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * @return whether this VPN connection can be bypassed by the apps. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public boolean isBypassableVpn() { + return allowBypass; + } + + /** * Set if the user desires to use this network even if it is unvalidated. This field has meaning * only if {@link explicitlySelected} is true. If it is, this field must also be set to the * appropriate value based on previous user choice. @@ -382,6 +392,19 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * Sets whether the apps can bypass the VPN connection. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + public Builder setBypassableVpn(boolean allowBypass) { + mConfig.allowBypass = allowBypass; + return this; + } + + /** * Returns the constructed {@link NetworkAgentConfig} object. */ @NonNull diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 881fa8c2702c..f50b0187b8ad 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -35,7 +35,6 @@ import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; import android.util.Range; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.CollectionUtils; @@ -538,43 +537,6 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_VPN); /** - * Capabilities that suggest that a network is restricted. - * {@see #maybeMarkCapabilitiesRestricted}, {@see #FORCE_RESTRICTED_CAPABILITIES} - */ - @VisibleForTesting - /* package */ static final long RESTRICTED_CAPABILITIES = - (1 << NET_CAPABILITY_CBS) - | (1 << NET_CAPABILITY_DUN) - | (1 << NET_CAPABILITY_EIMS) - | (1 << NET_CAPABILITY_FOTA) - | (1 << NET_CAPABILITY_IA) - | (1 << NET_CAPABILITY_IMS) - | (1 << NET_CAPABILITY_MCX) - | (1 << NET_CAPABILITY_RCS) - | (1 << NET_CAPABILITY_VEHICLE_INTERNAL) - | (1 << NET_CAPABILITY_XCAP) - | (1 << NET_CAPABILITY_ENTERPRISE); - - /** - * Capabilities that force network to be restricted. - * {@see #maybeMarkCapabilitiesRestricted}. - */ - private static final long FORCE_RESTRICTED_CAPABILITIES = - (1 << NET_CAPABILITY_OEM_PAID) - | (1 << NET_CAPABILITY_OEM_PRIVATE); - - /** - * Capabilities that suggest that a network is unrestricted. - * {@see #maybeMarkCapabilitiesRestricted}. - */ - @VisibleForTesting - /* package */ static final long UNRESTRICTED_CAPABILITIES = - (1 << NET_CAPABILITY_INTERNET) - | (1 << NET_CAPABILITY_MMS) - | (1 << NET_CAPABILITY_SUPL) - | (1 << NET_CAPABILITY_WIFI_P2P); - - /** * Capabilities that are managed by ConnectivityService. */ private static final long CONNECTIVITY_MANAGED_CAPABILITIES = @@ -749,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 = @@ -811,37 +790,12 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Deduces that all the capabilities it provides are typically provided by restricted networks - * or not. - * - * @return {@code true} if the network should be restricted. - * @hide - */ - public boolean deduceRestrictedCapability() { - // Check if we have any capability that forces the network to be restricted. - final boolean forceRestrictedCapability = - (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0; - - // Verify there aren't any unrestricted capabilities. If there are we say - // the whole thing is unrestricted unless it is forced to be restricted. - final boolean hasUnrestrictedCapabilities = - (mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0; - - // Must have at least some restricted capabilities. - final boolean hasRestrictedCapabilities = - (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0; - - return forceRestrictedCapability - || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities); - } - - /** - * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted. + * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if inferring the network is restricted. * * @hide */ public void maybeMarkCapabilitiesRestricted() { - if (deduceRestrictedCapability()) { + if (NetworkCapabilitiesUtils.inferRestrictedCapability(this)) { removeCapability(NET_CAPABILITY_NOT_RESTRICTED); } } @@ -2087,34 +2041,6 @@ public final class NetworkCapabilities implements Parcelable { } } - /** @hide */ - public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - - for (int transport : getTransportTypes()) { - proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport); - } - - for (int capability : getCapabilities()) { - proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability); - } - - proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps); - proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps); - - if (mNetworkSpecifier != null) { - proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString()); - } - if (mTransportInfo != null) { - // TODO b/120653863: write transport-specific info to proto? - } - - proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength()); - proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength); - - proto.end(token); - } - /** * @hide */ diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java index 14cb51c85d06..d859022552c1 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,153 @@ 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 a network for this offer is needed to satisfy some + * networking request. */ + void onNetworkNeeded(@NonNull NetworkRequest request, int providerId); + /** Called by the system when this offer is no longer valuable for this request. */ + void onNetworkUnneeded(@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 onNetworkNeeded(final @NonNull NetworkRequest request, + final int providerId) { + mExecutor.execute(() -> callback.onNetworkNeeded(request, providerId)); + } + + @Override + public void onNetworkUnneeded(final @NonNull NetworkRequest request) { + mExecutor.execute(() -> callback.onNetworkUnneeded(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 capabilities and score. + * + * A NetworkProvider's role is to provide networks. This method 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 valuable. When an + * offer might be preferred over existing networks, the provider will receive a call to + * the associated callback's {@link NetworkOfferCallback#onNetworkNeeded} method. The provider + * should then try to bring up this network. When an offer is no longer useful, the stack + * will inform the provider by calling {@link NetworkOfferCallback#onNetworkUnneeded}. 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 valuable 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 connect a better network than any existing network for any + * particular request, that's when the stack decides the network is needed. If the current + * networking requests are all satisfied by networks that this offer couldn't possibly be a + * better match for, that's when the offer is no longer valuable. An offer starts out as + * unneeded ; the provider should not try to bring up the network until + * {@link NetworkOfferCallback#onNetworkNeeded} 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 would be valuable. 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 + * low-value 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 network 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/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index bcbc04f72efc..5313f08fffbe 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -47,7 +47,6 @@ import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; import android.util.Range; -import android.util.proto.ProtoOutputStream; import java.util.Arrays; import java.util.List; @@ -675,18 +674,6 @@ public class NetworkRequest implements Parcelable { } } - /** @hide */ - public void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - - proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type)); - proto.write(NetworkRequestProto.REQUEST_ID, requestId); - proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType); - networkCapabilities.dumpDebug(proto, NetworkRequestProto.NETWORK_CAPABILITIES); - - proto.end(token); - } - public boolean equals(@Nullable Object obj) { if (obj instanceof NetworkRequest == false) return false; NetworkRequest that = (NetworkRequest)obj; @@ -699,4 +686,43 @@ public class NetworkRequest implements Parcelable { public int hashCode() { return Objects.hash(requestId, legacyType, networkCapabilities, type); } + + /** + * Gets all the capabilities set on this {@code NetworkRequest} instance. + * + * @return an array of capability values for this instance. + */ + @NonNull + public @NetCapability int[] getCapabilities() { + // No need to make a defensive copy here as NC#getCapabilities() already returns + // a new array. + return networkCapabilities.getCapabilities(); + } + + /** + * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance. + * + * @return an array of unwanted capability values for this instance. + * + * @hide + */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public @NetCapability int[] getUnwantedCapabilities() { + // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns + // a new array. + return networkCapabilities.getUnwantedCapabilities(); + } + + /** + * Gets all the transports set on this {@code NetworkRequest} instance. + * + * @return an array of transport type values for this instance. + */ + @NonNull + public @Transport int[] getTransportTypes() { + // No need to make a defensive copy here as NC#getTransportTypes() already returns + // a new array. + return networkCapabilities.getTransportTypes(); + } } diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java index eadcb2d0a7f4..65849930fa4a 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkScore.java +++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -29,7 +30,7 @@ import com.android.internal.annotations.VisibleForTesting; * network is considered for a particular use. * @hide */ -// TODO : @SystemApi when the implementation is complete +@SystemApi public final class NetworkScore implements Parcelable { // This will be removed soon. Do *NOT* depend on it for any new code that is not part of // a migration. @@ -62,6 +63,8 @@ public final class NetworkScore implements Parcelable { /** * @return whether this score has a particular policy. + * + * @hide */ @VisibleForTesting public boolean hasPolicy(final int policy) { diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java index 16ae55f8c11e..16a49bcae7ee 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java +++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java @@ -92,18 +92,28 @@ 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}. * @return {@code true} if {@code uid} can access network, {@code false} otherwise. */ - public native static boolean queryUserAccess(int uid, int netId); + public static boolean queryUserAccess(int uid, int netId) { + // TODO (b/183485986): remove this method + return false; + } + + private static native FileDescriptor resNetworkSend( + long netHandle, byte[] msg, int msglen, int flags) throws ErrnoException; /** * DNS resolver series jni method. @@ -111,8 +121,13 @@ 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 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. @@ -121,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/framework/src/android/net/QosCallbackConnection.java b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java index bdb4ad68cd7b..de0fc243b43b 100644 --- a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java +++ b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.NonNull; import android.annotation.Nullable; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import com.android.internal.annotations.VisibleForTesting; @@ -84,6 +85,25 @@ class QosCallbackConnection extends android.net.IQosCallback.Stub { } /** + * Called when either the {@link NrQosSessionAttributes} has changed or on the first time + * the attributes have become available. + * + * @param session the session that is now available + * @param attributes the corresponding attributes of session + */ + @Override + public void onNrQosSessionAvailable(@NonNull final QosSession session, + @NonNull final NrQosSessionAttributes attributes) { + + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionAvailable(session, attributes); + } + }); + } + + /** * Called when the session is lost. * * @param session the session that was lost diff --git a/packages/Connectivity/framework/src/android/net/QosSession.java b/packages/Connectivity/framework/src/android/net/QosSession.java index 4f3bb77c5877..93f2ff2bf724 100644 --- a/packages/Connectivity/framework/src/android/net/QosSession.java +++ b/packages/Connectivity/framework/src/android/net/QosSession.java @@ -36,6 +36,11 @@ public final class QosSession implements Parcelable { */ public static final int TYPE_EPS_BEARER = 1; + /** + * The {@link QosSession} is a NR Session. + */ + public static final int TYPE_NR_BEARER = 2; + private final int mSessionId; private final int mSessionType; @@ -100,6 +105,7 @@ public final class QosSession implements Parcelable { */ @IntDef(value = { TYPE_EPS_BEARER, + TYPE_NR_BEARER, }) @interface QosSessionType {} diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index 1330e719e774..b44128b9ae2e 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", @@ -51,33 +51,46 @@ cc_library_shared { java_library { name: "service-connectivity-pre-jarjar", + sdk_version: "system_server_current", + min_sdk_version: "30", srcs: [ - ":framework-connectivity-shared-srcs", ":connectivity-service-srcs", + ":framework-connectivity-shared-srcs", + ":services-connectivity-shared-srcs", + // TODO: move to net-utils-device-common, enable shrink optimization to avoid extra classes + ":net-module-utils-srcs", ], libs: [ - "android.net.ipsec.ike", - "services.core", - "services.net", + // TODO (b/183097033) remove once system_server_current includes core_current + "stable.core.platform.api.stubs", + "android_system_server_stubs_current", + "framework-annotations-lib", + "framework-connectivity.impl", + "framework-tethering.stubs.module_lib", + "framework-wifi.stubs.module_lib", "unsupportedappusage", "ServiceConnectivityResources", ], static_libs: [ + "dnsresolver_aidl_interface-V7-java", "modules-utils-os", "net-utils-device-common", "net-utils-framework-common", "netd-client", + "netlink-client", + "networkstack-client", "PlatformProperties", "service-connectivity-protos", ], apex_available: [ - "//apex_available:platform", "com.android.tethering", ], } java_library { name: "service-connectivity-protos", + sdk_version: "system_current", + min_sdk_version: "30", proto: { type: "nano", }, @@ -86,20 +99,21 @@ java_library { ], libs: ["libprotobuf-java-nano"], apex_available: [ - "//apex_available:platform", "com.android.tethering", ], } java_library { name: "service-connectivity", + sdk_version: "system_server_current", + min_sdk_version: "30", installable: true, static_libs: [ "service-connectivity-pre-jarjar", ], jarjar_rules: "jarjar-rules.txt", apex_available: [ - "//apex_available:platform", + "//apex_available:platform", // For arc-services "com.android.tethering", ], } 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/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index 231babea97c2..dd9fc2c7c142 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -23,5 +23,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.cellbroadcast", + "com.android.permission", ], } diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml new file mode 100644 index 000000000000..24d53ab84653 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml @@ -0,0 +1,60 @@ +<?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. + --> +<androidx.coordinatorlayout.widget.CoordinatorLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/content_parent" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:transitionGroup="true"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/app_bar" + android:layout_width="match_parent" + android:layout_height="180dp" + android:theme="@style/Theme.CollapsingToolbar.Settings"> + + <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout + android:id="@+id/collapsing_toolbar" + android:background="?android:attr/colorPrimary" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:maxLines="3" + app:contentScrim="?android:attr/colorPrimary" + app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed" + app:statusBarScrim="?android:attr/colorPrimary" + app:layout_scrollFlags="scroll|exitUntilCollapsed" + app:expandedTitleMarginStart="18dp" + app:expandedTitleMarginEnd="18dp" + app:toolbarId="@id/action_bar"> + + <Toolbar + android:id="@+id/action_bar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:theme="?android:attr/actionBarTheme" + app:layout_collapseMode="pin"/> + + </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout> + </com.google.android.material.appbar.AppBarLayout> + + <FrameLayout + android:id="@+id/content_frame" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml index e376930645ce..c799b9962828 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml @@ -14,47 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. --> -<androidx.coordinatorlayout.widget.CoordinatorLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" +<!-- The main content view --> +<LinearLayout android:id="@+id/content_parent" + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:transitionGroup="true"> - - <com.google.android.material.appbar.AppBarLayout - android:id="@+id/app_bar" + android:fitsSystemWindows="true" + android:transitionGroup="true" + android:orientation="vertical"> + <Toolbar + android:id="@+id/action_bar" + style="?android:attr/actionBarStyle" android:layout_width="match_parent" - android:layout_height="180dp" - android:theme="@style/Theme.CollapsingToolbar.Settings"> - - <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout - android:id="@+id/collapsing_toolbar" - android:background="?android:attr/colorPrimary" - android:layout_width="match_parent" - android:layout_height="match_parent" - app:maxLines="3" - app:contentScrim="?android:attr/colorPrimary" - app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed" - app:statusBarScrim="?android:attr/colorPrimary" - app:layout_scrollFlags="scroll|exitUntilCollapsed" - app:expandedTitleMarginStart="18dp" - app:expandedTitleMarginEnd="18dp" - app:toolbarId="@id/action_bar"> - - <Toolbar - android:id="@+id/action_bar" - android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" - android:theme="?android:attr/actionBarTheme" - app:layout_collapseMode="pin"/> - - </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout> - </com.google.android.material.appbar.AppBarLayout> - + android:layout_height="wrap_content" + android:theme="?android:attr/actionBarTheme" /> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" - android:layout_height="wrap_content" - app:layout_behavior="@string/appbar_scrolling_view_behavior"/> -</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml deleted file mode 100644 index c799b9962828..000000000000 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml +++ /dev/null @@ -1,36 +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. ---> -<!-- The main content view --> -<LinearLayout - android:id="@+id/content_parent" - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true" - android:transitionGroup="true" - android:orientation="vertical"> - <Toolbar - android:id="@+id/action_bar" - style="?android:attr/actionBarStyle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="?android:attr/actionBarTheme" /> - <FrameLayout - android:id="@+id/content_frame" - android:layout_width="match_parent" - android:layout_height="match_parent"/> -</LinearLayout> 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/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java index ad94cd0318a7..957bac742703 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java @@ -24,7 +24,6 @@ import android.view.ViewGroup; import android.widget.Toolbar; import androidx.annotation.Nullable; -import androidx.core.os.BuildCompat; import androidx.fragment.app.FragmentActivity; import com.google.android.material.appbar.CollapsingToolbarLayout; @@ -41,15 +40,8 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // TODO(b/181723278): Update the version check after SDK for S is finalized - // The collapsing toolbar is only supported if the android platform version is S or higher. - // Otherwise the regular action bar will be shown. - if (BuildCompat.isAtLeastS()) { - super.setContentView(R.layout.collapsing_toolbar_base_layout); - mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); - } else { - super.setContentView(R.layout.toolbar_base_layout); - } + super.setContentView(R.layout.collapsing_toolbar_base_layout); + mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); final Toolbar toolbar = findViewById(R.id.action_bar); setActionBar(toolbar); diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java new file mode 100644 index 000000000000..c4c74ffc719b --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java @@ -0,0 +1,78 @@ +/* + * 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.collapsingtoolbar; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.Toolbar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.google.android.material.appbar.CollapsingToolbarLayout; + +/** + * A base fragment that has a collapsing toolbar layout for enabling the collapsing toolbar design. + */ +public abstract class CollapsingToolbarBaseFragment extends Fragment { + + @Nullable + private CollapsingToolbarLayout mCollapsingToolbarLayout; + @NonNull + private Toolbar mToolbar; + @NonNull + private FrameLayout mContentFrameLayout; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, + false); + mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar); + mToolbar = view.findViewById(R.id.action_bar); + mContentFrameLayout = view.findViewById(R.id.content_frame); + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + requireActivity().setActionBar(mToolbar); + } + + /** + * Return the collapsing toolbar layout. + */ + @Nullable + public CollapsingToolbarLayout getCollapsingToolbarLayout() { + return mCollapsingToolbarLayout; + } + + /** + * Return the content frame layout. + */ + @NonNull + public FrameLayout getContentFrameLayout() { + return mContentFrameLayout; + } +} 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/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 8d9a562fede7..ae9261c48419 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -63,7 +63,8 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - LayoutInflater.from(context).inflate(R.layout.main_switch_bar, this); + LayoutInflater.from(context).inflate(resourceId(context, "layout", "main_switch_bar"), + this); setFocusable(true); setClickable(true); @@ -255,4 +256,8 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec requestLayout(); } + + private int resourceId(Context context, String type, String name) { + return context.getResources().getIdentifier(name, type, context.getPackageName()); + } } 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/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index d801f1bbf5e5..927e9db89be8 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1272,6 +1272,22 @@ <!-- Button label for generic OK action [CHAR LIMIT=20] --> <string name="okay">OK</string> + <!-- Label for the settings activity for controlling apps that can schedule alarms [CHAR LIMIT=30] --> + <string name="alarms_and_reminders_label">Alarms and reminders</string> + <!-- Label for the switch to toggler the permission for scheduling alarms [CHAR LIMIT=50] --> + <string name="alarms_and_reminders_switch_title">Allow to set alarms or reminders</string> + <!-- Title for the setting screen for controlling apps that can schedule alarms [CHAR LIMIT=30] --> + <string name="alarms_and_reminders_title">Alarms and reminders</string> + <!-- Description that appears below the alarms_and_reminders switch [CHAR LIMIT=NONE] --> + <string name="alarms_and_reminders_footer_title"> + Allow this app to schedule alarms or other timing based events. + This will allow the app to wake up and run even when you are not using the device. + Note that revoking this permission may cause the app to malfunction, specifically any alarms + that the app has scheduled will no longer work. + </string> + <!-- Keywords for setting screen for controlling apps that can schedule alarms [CHAR LIMIT=100] --> + <string name="keywords_alarms_and_reminders">schedule, alarm, reminder, event</string> + <!-- Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] --> <string name="zen_mode_enable_dialog_turn_on">Turn on</string> <!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]--> @@ -1467,7 +1483,7 @@ <string name="data_connection_5g_plus" translatable="false">5G+</string> <!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] --> - <string name="data_connection_carrier_wifi">CWF</string> + <string name="data_connection_carrier_wifi">W+</string> <!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] --> <string name="cell_data_off_content_description">Mobile data off</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index 898796828131..a5da8b6bd15e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -31,6 +31,7 @@ import android.os.Environment; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; +import android.text.TextUtils; import android.util.Log; import com.android.settingslib.R; @@ -198,4 +199,17 @@ public class AppUtils { } return false; } + + /** + * Returns a boolean indicating whether a given package is a default browser. + * + * @param packageName a given package. + * @return true if the given package is default browser. + */ + public static boolean isDefaultBrowser(Context context, String packageName) { + final String defaultBrowserPackage = + context.getPackageManager().getDefaultBrowserPackageNameAsUser( + UserHandle.myUserId()); + return TextUtils.equals(packageName, defaultBrowserPackage); + } } 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/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index db9b83e04a47..53920f04cc7a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -165,6 +165,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"})); VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 7288371899ce..4119dc9f2206 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1881,6 +1881,12 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE, SecureSettingsProto.Assist.GESTURE_SETUP_COMPLETE); + dumpSetting(s, p, + Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, + SecureSettingsProto.Assist.TOUCH_GESTURE_ENABLED); + dumpSetting(s, p, + Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, + SecureSettingsProto.Assist.LONG_PRESS_HOME_ENABLED); p.end(assistToken); final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES); @@ -2150,6 +2156,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, SecureSettingsProto.InputMethods.SHOW_IME_WITH_HARD_KEYBOARD); + dumpSetting(s, p, + Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, + SecureSettingsProto.InputMethods.DEFAULT_VOICE_INPUT_METHOD); p.end(inputMethodsToken); dumpSetting(s, p, 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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 2b4fef0c9ba7..cff8ad1ed964 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -39,12 +39,16 @@ <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" /> <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> @@ -237,6 +241,9 @@ <!-- Permission needed to run keyguard manager tests in CTS --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" /> + <!-- Permission needed to set/clear/verify lockscreen credentials in CTS tests --> + <uses-permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS" /> + <!-- Permission needed to test wallpaper component --> <uses-permission android:name="android.permission.SET_WALLPAPER" /> <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 3904201d2ee8..4135bbe3e86d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -62,6 +62,8 @@ <!-- Networking and telephony --> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 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..95c2d2efcd89 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,31 @@ 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; + } + + /** + * @return resource id of the string to use for closing the detail panel. If + * {@code Resources.ID_NULL}, then use the default string: + * {@code com.android.systemui.R.string.quick_settings_done} + */ + default int getDoneText() { + 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/people_space_messages_count_background.xml b/packages/SystemUI/res/drawable/people_space_messages_count_background.xml new file mode 100644 index 000000000000..0fc112e16ee4 --- /dev/null +++ b/packages/SystemUI/res/drawable/people_space_messages_count_background.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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + <solid android:color="#9ED582" /> + <corners android:radius="@dimen/people_space_messages_count_radius" /> +</shape> 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_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml index e4e4cd8956a7..db1d46d2117a 100644 --- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml +++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml @@ -98,8 +98,8 @@ android:orientation="horizontal" android:paddingTop="4dp" android:layout_width="match_parent" - android:layout_height="wrap_content"> - + android:layout_height="wrap_content" + android:clipToOutline="true"> <TextView android:id="@+id/name" android:gravity="center_vertical" @@ -112,7 +112,21 @@ android:ellipsize="end" android:layout_width="wrap_content" android:layout_height="wrap_content" /> - + <TextView + android:id="@+id/messages_count" + android:gravity="end" + 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" + /> <ImageView android:id="@+id/predefined_icon" android:gravity="end|center_vertical" 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/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml index 730f24ff9dd2..d82151d30b9e 100644 --- a/packages/SystemUI/res/layout/wireless_charging_layout.xml +++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml @@ -22,6 +22,11 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + <com.android.systemui.statusbar.charging.ChargingRippleView + android:id="@+id/wireless_charging_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + <!-- Circle animation --> <ImageView android:id="@+id/wireless_charging_view" 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/attrs.xml b/packages/SystemUI/res/values/attrs.xml index be49e1f8c71e..886f98e55d67 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -124,6 +124,7 @@ <attr name="darkIconTheme" format="reference" /> <attr name="wallpaperTextColor" format="reference|color" /> <attr name="wallpaperTextColorSecondary" format="reference|color" /> + <attr name="wallpaperTextColorAccent" format="reference|color" /> <attr name="backgroundProtectedStyle" format="reference" /> <declare-styleable name="SmartReplyView"> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index af6df32a02b0..6e61148f4ac8 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -99,7 +99,7 @@ <!-- The default tiles to display in QuickSettings --> <string name="quick_settings_tiles_default" translatable="false"> - wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord + internet,wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord </string> <!-- The minimum number of tiles to display in QuickSettings --> @@ -107,7 +107,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm,wallet + internet,wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm,wallet </string> <!-- The tiles to display in QuickSettings --> @@ -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 ff4e8e002d88..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> @@ -1353,6 +1357,7 @@ <dimen name="people_space_widget_radius">28dp</dimen> <dimen name="people_space_image_radius">20dp</dimen> + <dimen name="people_space_messages_count_radius">12dp</dimen> <dimen name="people_space_widget_background_padding">6dp</dimen> <dimen name="required_width_for_medium">146dp</dimen> <dimen name="required_width_for_large">138dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 691d111089b8..3ca885a2c6b7 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -239,10 +239,8 @@ <string name="screenshot_edit_label">Edit</string> <!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] --> <string name="screenshot_edit_description">Edit screenshot</string> - <!-- Label for UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=30] --> - <string name="screenshot_scroll_label">Scroll</string> - <!-- Content description UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=NONE] --> - <string name="screenshot_scroll_description">Scroll screenshot</string> + <!-- Label for UI element which allows the user to capture additional off-screen content in a screenshot. [CHAR LIMIT=30] --> + <string name="screenshot_scroll_label">Capture more</string> <!-- Content description indicating that tapping a button will dismiss the screenshots UI [CHAR LIMIT=NONE] --> <string name="screenshot_dismiss_description">Dismiss screenshot</string> <!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] --> @@ -889,8 +887,12 @@ <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 button that dismisses user switcher control panel. [CHAR LIMIT=NONE] --> + <string name="quick_settings_close_user_panel">Close</string> <!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] --> <string name="quick_settings_connected">Connected</string> <!-- QuickSettings: Control panel: Label for connected device, showing remote device battery level. [CHAR LIMIT=NONE] --> @@ -1282,6 +1284,9 @@ <!-- Disclosure at the bottom of Quick Settings that indicates that the device has a managed profile which can be monitored by the profile owner [CHAR LIMIT=100] --> <string name="quick_settings_disclosure_named_managed_profile_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> may monitor network traffic in your work profile</string> + <!-- Disclosure at the bottom of Quick Settings that indicates that the user's device has a managed profile where network activity is visible to their IT admin [CHAR LIMIT=100] --> + <string name="quick_settings_disclosure_managed_profile_network_activity">Work profile network activity is visible to your IT admin</string> + <!-- Disclosure at the bottom of Quick Settings that indicates that a certificate authorithy is installed on this device and the traffic might be monitored [CHAR LIMIT=100] --> <string name="quick_settings_disclosure_monitoring">Network may be monitored</string> @@ -2865,6 +2870,8 @@ <string name="empty_status">Let’s chat tonight!</string> <!-- Default text for missed call notifications on their Conversation widget [CHAR LIMIT=20] --> <string name="missed_call">Missed call</string> + <!-- Text when a Notification may have more messages than the number indicated [CHAR LIMIT=5] --> + <string name="messages_count_overflow_indicator"><xliff:g id="number" example="7">%d</xliff:g>+</string> <!-- Description text for adding a Conversation widget [CHAR LIMIT=100] --> <string name="people_tile_description">See recent messages, missed calls, and status updates</string> <!-- Title text displayed for the Conversation widget [CHAR LIMIT=50] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4a661dcecce8..ecc1a5c831b8 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -321,6 +321,7 @@ <item name="darkIconTheme">@style/DualToneDarkTheme</item> <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item> <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item> + <item name="wallpaperTextColorAccent">@*android:color/system_accent1_100</item> <item name="android:colorError">@*android:color/error_color_material_dark</item> <item name="android:colorControlHighlight">@*android:color/primary_text_material_dark</item> <item name="*android:lockPatternStyle">@style/LockPatternStyle</item> @@ -337,6 +338,7 @@ <style name="Theme.SystemUI.Light"> <item name="wallpaperTextColor">@*android:color/primary_text_material_light</item> <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item> + <item name="wallpaperTextColorAccent">@*android:color/system_accent2_600</item> <item name="android:colorError">@*android:color/error_color_material_light</item> <item name="android:colorControlHighlight">#40000000</item> <item name="shadowRadius">0</item> diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml index d2bff180ef8a..b2bf6da65163 100644 --- a/packages/SystemUI/res/xml/people_space_widget_info.xml +++ b/packages/SystemUI/res/xml/people_space_widget_info.xml @@ -16,9 +16,9 @@ <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="120dp" - android:minHeight="54dp" + android:minHeight="50dp" android:minResizeWidth="60dp" - android:minResizeHeight="54dp" + android:minResizeHeight="50dp" android:maxResizeHeight="207dp" android:updatePeriodMillis="60000" android:description="@string/people_tile_description" diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 3f0e3eb84424..ab219f36bab3 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -131,7 +131,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie private void initColors() { mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(), - com.android.systemui.R.attr.wallpaperTextColor); + com.android.systemui.R.attr.wallpaperTextColorAccent); mView.setColors(mDozingColor, mLockScreenColor); mView.animateDoze(mIsDozing, false); } diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java index f6b03c1fa013..e4f6e131258e 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java @@ -24,41 +24,14 @@ import android.util.AttributeSet; import android.view.View; import android.widget.TextView; -import com.android.systemui.Dependency; import com.android.systemui.R; import java.util.Locale; public class CarrierText extends TextView { - private static final boolean DEBUG = KeyguardConstants.DEBUG; - private static final String TAG = "CarrierText"; + private final boolean mShowMissingSim; - private static CharSequence mSeparator; - - private boolean mShowMissingSim; - - private boolean mShowAirplaneMode; - private boolean mShouldMarquee; - - private CarrierTextController mCarrierTextController; - - private CarrierTextController.CarrierTextCallback mCarrierTextCallback = - new CarrierTextController.CarrierTextCallback() { - @Override - public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { - setText(info.carrierText); - } - - @Override - public void startedGoingToSleep() { - setSelected(false); - } - - @Override - public void finishedWakingUp() { - setSelected(true); - } - }; + private final boolean mShowAirplaneMode; public CarrierText(Context context) { this(context, null); @@ -78,30 +51,6 @@ public class CarrierText extends TextView { } setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps)); } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mSeparator = getResources().getString( - com.android.internal.R.string.kg_text_message_separator); - mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode, - mShowMissingSim); - mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive(); - setSelected(mShouldMarquee); // Allow marquee to work. - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mCarrierTextController.setListening(mCarrierTextCallback); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mCarrierTextController.setListening(null); - } - @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); @@ -113,7 +62,15 @@ public class CarrierText extends TextView { } } - private class CarrierTextTransformationMethod extends SingleLineTransformationMethod { + public boolean getShowAirplaneMode() { + return mShowAirplaneMode; + } + + public boolean getShowMissingSim() { + return mShowMissingSim; + } + + private static class CarrierTextTransformationMethod extends SingleLineTransformationMethod { private final Locale mLocale; private final boolean mAllCaps; diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java index d52a25139ce7..997c5275a2fa 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,680 +16,60 @@ package com.android.keyguard; -import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; -import static android.telephony.PhoneStateListener.LISTEN_NONE; - -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.net.wifi.WifiManager; -import android.os.Handler; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.android.settingslib.WirelessUtils; -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.keyguard.WakefulnessLifecycle; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; +import com.android.systemui.util.ViewController; import javax.inject.Inject; /** - * Controller that generates text including the carrier names and/or the status of all the SIM - * interfaces in the device. Through a callback, the updates can be retrieved either as a list or - * separated by a given separator {@link CharSequence}. + * Controller for {@link CarrierText}. */ -public class CarrierTextController { - private static final boolean DEBUG = KeyguardConstants.DEBUG; - private static final String TAG = "CarrierTextController"; - - private final boolean mIsEmergencyCallCapable; - private final Handler mMainHandler; - private final Handler mBgHandler; - private boolean mTelephonyCapable; - private boolean mShowMissingSim; - private boolean mShowAirplaneMode; - private final AtomicBoolean mNetworkSupported = new AtomicBoolean(); - @VisibleForTesting - protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private WifiManager mWifiManager; - private boolean[] mSimErrorState; - private final int mSimSlotsNumber; - @Nullable // Check for nullability before dispatching - private CarrierTextCallback mCarrierTextCallback; - private Context mContext; - private CharSequence mSeparator; - private WakefulnessLifecycle mWakefulnessLifecycle; - private final WakefulnessLifecycle.Observer mWakefulnessObserver = - new WakefulnessLifecycle.Observer() { +public class CarrierTextController extends ViewController<CarrierText> { + private final CarrierTextManager mCarrierTextManager; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final CarrierTextManager.CarrierTextCallback mCarrierTextCallback = + new CarrierTextManager.CarrierTextCallback() { @Override - public void onFinishedWakingUp() { - final CarrierTextCallback callback = mCarrierTextCallback; - if (callback != null) callback.finishedWakingUp(); + public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) { + mView.setText(info.carrierText); } @Override - public void onStartedGoingToSleep() { - final CarrierTextCallback callback = mCarrierTextCallback; - if (callback != null) callback.startedGoingToSleep(); + public void startedGoingToSleep() { + mView.setSelected(false); } - }; - - @VisibleForTesting - protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onRefreshCarrierInfo() { - if (DEBUG) { - Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " - + Boolean.toString(mTelephonyCapable)); - } - updateCarrierText(); - } - - @Override - public void onTelephonyCapable(boolean capable) { - if (DEBUG) { - Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " - + Boolean.toString(capable)); - } - mTelephonyCapable = capable; - updateCarrierText(); - } - - public void onSimStateChanged(int subId, int slotId, int simState) { - if (slotId < 0 || slotId >= mSimSlotsNumber) { - Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId - + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); - return; - } - - if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState)); - if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) { - mSimErrorState[slotId] = true; - updateCarrierText(); - } else if (mSimErrorState[slotId]) { - mSimErrorState[slotId] = false; - updateCarrierText(); - } - } - }; - - private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - mActiveMobileDataSubscription = subId; - if (mNetworkSupported.get() && mCarrierTextCallback != null) { - updateCarrierText(); - } - } - }; - - /** - * The status of this lock screen. Primarily used for widgets on LockScreen. - */ - private enum StatusMode { - Normal, // Normal case (sim card present, it's not locked) - NetworkLocked, // SIM card is 'network locked'. - SimMissing, // SIM card is missing. - SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access - SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times - SimLocked, // SIM card is currently locked - SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure - SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. - SimIoError, // SIM card is faulty - SimUnknown // SIM card is unknown - } - - /** - * Controller that provides updates on text with carriers names or SIM status. - * Used by {@link CarrierText}. - * - * @param separator Separator between different parts of the text - */ - public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode, - boolean showMissingSim) { - mContext = context; - mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable(); - - mShowAirplaneMode = showAirplaneMode; - mShowMissingSim = showMissingSim; - - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - mSeparator = separator; - mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class); - mSimSlotsNumber = getTelephonyManager().getSupportedModemCount(); - mSimErrorState = new boolean[mSimSlotsNumber]; - mMainHandler = Dependency.get(Dependency.MAIN_HANDLER); - mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); - mBgHandler.post(() -> { - boolean supported = - mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); - if (supported && mNetworkSupported.compareAndSet(false, supported)) { - // This will set/remove the listeners appropriately. Note that it will never double - // add the listeners. - handleSetListening(mCarrierTextCallback); - } - }); - } - - private TelephonyManager getTelephonyManager() { - return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - } - - /** - * Checks if there are faulty cards. Adds the text depending on the slot of the card - * - * @param text: current carrier text based on the sim state - * @param carrierNames names order by subscription order - * @param subOrderBySlot array containing the sub index for each slot ID - * @param noSims: whether a valid sim card is inserted - * @return text - */ - private CharSequence updateCarrierTextWithSimIoError(CharSequence text, - CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) { - final CharSequence carrier = ""; - CharSequence carrierTextForSimIOError = getCarrierTextForSimState( - TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier); - // mSimErrorState has the state of each sim indexed by slotID. - for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) { - if (!mSimErrorState[index]) { - continue; - } - // In the case when no sim cards are detected but a faulty card is inserted - // overwrite the text and only show "Invalid card" - if (noSims) { - return concatenate(carrierTextForSimIOError, - getContext().getText( - com.android.internal.R.string.emergency_calls_only), - mSeparator); - } else if (subOrderBySlot[index] != -1) { - int subIndex = subOrderBySlot[index]; - // prepend "Invalid card" when faulty card is inserted in slot 0 or 1 - carrierNames[subIndex] = concatenate(carrierTextForSimIOError, - carrierNames[subIndex], - mSeparator); - } else { - // concatenate "Invalid card" when faulty card is inserted in other slot - text = concatenate(text, carrierTextForSimIOError, mSeparator); - } - - } - return text; - } - - /** - * This may be called internally after retrieving the correct value of {@code mNetworkSupported} - * (assumed false to start). In that case, the following happens: - * <ul> - * <li> If there was a registered callback, and the network is supported, it will register - * listeners. - * <li> If there was not a registered callback, it will try to remove unregistered listeners - * which is a no-op - * </ul> - * - * This call will always be processed in a background thread. - */ - private void handleSetListening(CarrierTextCallback callback) { - TelephonyManager telephonyManager = getTelephonyManager(); - if (callback != null) { - mCarrierTextCallback = callback; - if (mNetworkSupported.get()) { - // Keyguard update monitor expects callbacks from main thread - mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback)); - mWakefulnessLifecycle.addObserver(mWakefulnessObserver); - telephonyManager.listen(mPhoneStateListener, - LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); - } else { - // Don't listen and clear out the text when the device isn't a phone. - mMainHandler.post(() -> callback.updateCarrierInfo( - new CarrierTextCallbackInfo("", null, false, null) - )); - } - } else { - mCarrierTextCallback = null; - mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback)); - mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); - telephonyManager.listen(mPhoneStateListener, LISTEN_NONE); - } - } - - /** - * Sets the listening status of this controller. If the callback is null, it is set to - * not listening. - * - * @param callback Callback to provide text updates - */ - public void setListening(CarrierTextCallback callback) { - mBgHandler.post(() -> handleSetListening(callback)); - } - - protected List<SubscriptionInfo> getSubscriptionInfo() { - return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false); - } - - protected void updateCarrierText() { - boolean allSimsMissing = true; - boolean anySimReadyAndInService = false; - CharSequence displayText = null; - List<SubscriptionInfo> subs = getSubscriptionInfo(); - - final int numSubs = subs.size(); - final int[] subsIds = new int[numSubs]; - // This array will contain in position i, the index of subscription in slot ID i. - // -1 if no subscription in that slot - final int[] subOrderBySlot = new int[mSimSlotsNumber]; - for (int i = 0; i < mSimSlotsNumber; i++) { - subOrderBySlot[i] = -1; - } - final CharSequence[] carrierNames = new CharSequence[numSubs]; - if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs); - - for (int i = 0; i < numSubs; i++) { - int subId = subs.get(i).getSubscriptionId(); - carrierNames[i] = ""; - subsIds[i] = subId; - subOrderBySlot[subs.get(i).getSimSlotIndex()] = i; - int simState = mKeyguardUpdateMonitor.getSimState(subId); - CharSequence carrierName = subs.get(i).getCarrierName(); - CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); - if (DEBUG) { - Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); - } - if (carrierTextForSimState != null) { - allSimsMissing = false; - carrierNames[i] = carrierTextForSimState; - } - if (simState == TelephonyManager.SIM_STATE_READY) { - ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); - if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) { - // hack for WFC (IWLAN) not turning off immediately once - // Wi-Fi is disassociated or disabled - if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN - || (mWifiManager.isWifiEnabled() - && mWifiManager.getConnectionInfo() != null - && mWifiManager.getConnectionInfo().getBSSID() != null)) { - if (DEBUG) { - Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); - } - anySimReadyAndInService = true; - } - } - } - } - // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY - // This condition will also be true always when numSubs == 0 - if (allSimsMissing && !anySimReadyAndInService) { - if (numSubs != 0) { - // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. - // This depends on mPlmn containing the text "Emergency calls only" when the radio - // has some connectivity. Otherwise, it should be null or empty and just show - // "No SIM card" - // Grab the first subscripton, because they all should contain the emergency text, - // described above. - displayText = makeCarrierStringOnEmergencyCapable( - getMissingSimMessage(), subs.get(0).getCarrierName()); - } else { - // We don't have a SubscriptionInfo to get the emergency calls only from. - // Grab it from the old sticky broadcast if possible instead. We can use it - // here because no subscriptions are active, so we don't have - // to worry about MSIM clashing. - CharSequence text = - getContext().getText(com.android.internal.R.string.emergency_calls_only); - Intent i = getContext().registerReceiver(null, - new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); - if (i != null) { - String spn = ""; - String plmn = ""; - if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) { - spn = i.getStringExtra(TelephonyManager.EXTRA_SPN); - } - if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) { - plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN); - } - if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); - if (Objects.equals(plmn, spn)) { - text = plmn; - } else { - text = concatenate(plmn, spn, mSeparator); - } - } - displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); - } - } - - if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames); - - displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot, - allSimsMissing); - - boolean airplaneMode = false; - // APM (airplane mode) != no carrier state. There are carrier services - // (e.g. WFC = Wi-Fi calling) which may operate in APM. - if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { - displayText = getAirplaneModeMessage(); - airplaneMode = true; - } - - final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo( - displayText, - carrierNames, - !allSimsMissing, - subsIds, - airplaneMode); - postToCallback(info); - } - - @VisibleForTesting - protected void postToCallback(CarrierTextCallbackInfo info) { - final CarrierTextCallback callback = mCarrierTextCallback; - if (callback != null) { - mMainHandler.post(() -> callback.updateCarrierInfo(info)); - } - } - - private Context getContext() { - return mContext; - } - - private String getMissingSimMessage() { - return mShowMissingSim && mTelephonyCapable - ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; - } - - private String getAirplaneModeMessage() { - return mShowAirplaneMode - ? getContext().getString(R.string.airplane_mode) : ""; - } - - /** - * Top-level function for creating carrier text. Makes text based on simState, PLMN - * and SPN as well as device capabilities, such as being emergency call capable. - * - * @return Carrier text if not in missing state, null otherwise. - */ - private CharSequence getCarrierTextForSimState(int simState, CharSequence text) { - CharSequence carrierText = null; - CarrierTextController.StatusMode status = getStatusForIccState(simState); - switch (status) { - case Normal: - carrierText = text; - break; - - case SimNotReady: - // Null is reserved for denoting missing, in this case we have nothing to display. - carrierText = ""; // nothing to display yet. - break; - - case NetworkLocked: - carrierText = makeCarrierStringOnEmergencyCapable( - mContext.getText(R.string.keyguard_network_locked_message), text); - break; - - case SimMissing: - carrierText = null; - break; - - case SimPermDisabled: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText( - R.string.keyguard_permanent_disabled_sim_message_short), - text); - break; - - case SimMissingLocked: - carrierText = null; - break; - case SimLocked: - carrierText = makeCarrierStringOnLocked( - getContext().getText(R.string.keyguard_sim_locked_message), - text); - break; - - case SimPukLocked: - carrierText = makeCarrierStringOnLocked( - getContext().getText(R.string.keyguard_sim_puk_locked_message), - text); - break; - case SimIoError: - carrierText = makeCarrierStringOnEmergencyCapable( - getContext().getText(R.string.keyguard_sim_error_message_short), - text); - break; - case SimUnknown: - carrierText = null; - break; - } - - return carrierText; - } - - /* - * Add emergencyCallMessage to carrier string only if phone supports emergency calls. - */ - private CharSequence makeCarrierStringOnEmergencyCapable( - CharSequence simMessage, CharSequence emergencyCallMessage) { - if (mIsEmergencyCallCapable) { - return concatenate(simMessage, emergencyCallMessage, mSeparator); - } - return simMessage; - } - - /* - * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in - * DSDS - */ - private CharSequence makeCarrierStringOnLocked(CharSequence simMessage, - CharSequence carrierName) { - final boolean simMessageValid = !TextUtils.isEmpty(simMessage); - final boolean carrierNameValid = !TextUtils.isEmpty(carrierName); - if (simMessageValid && carrierNameValid) { - return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template, - carrierName, simMessage); - } else if (simMessageValid) { - return simMessage; - } else if (carrierNameValid) { - return carrierName; - } else { - return ""; - } - } - - /** - * Determine the current status of the lock screen given the SIM state and other stuff. - */ - private CarrierTextController.StatusMode getStatusForIccState(int simState) { - final boolean missingAndNotProvisioned = - !mKeyguardUpdateMonitor.isDeviceProvisioned() - && (simState == TelephonyManager.SIM_STATE_ABSENT - || simState == TelephonyManager.SIM_STATE_PERM_DISABLED); - - // Assume we're NETWORK_LOCKED if not provisioned - simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState; - switch (simState) { - case TelephonyManager.SIM_STATE_ABSENT: - return CarrierTextController.StatusMode.SimMissing; - case TelephonyManager.SIM_STATE_NETWORK_LOCKED: - return CarrierTextController.StatusMode.SimMissingLocked; - case TelephonyManager.SIM_STATE_NOT_READY: - return CarrierTextController.StatusMode.SimNotReady; - case TelephonyManager.SIM_STATE_PIN_REQUIRED: - return CarrierTextController.StatusMode.SimLocked; - case TelephonyManager.SIM_STATE_PUK_REQUIRED: - return CarrierTextController.StatusMode.SimPukLocked; - case TelephonyManager.SIM_STATE_READY: - return CarrierTextController.StatusMode.Normal; - case TelephonyManager.SIM_STATE_PERM_DISABLED: - return CarrierTextController.StatusMode.SimPermDisabled; - case TelephonyManager.SIM_STATE_UNKNOWN: - return CarrierTextController.StatusMode.SimUnknown; - case TelephonyManager.SIM_STATE_CARD_IO_ERROR: - return CarrierTextController.StatusMode.SimIoError; - } - return CarrierTextController.StatusMode.SimUnknown; - } - - private static CharSequence concatenate(CharSequence plmn, CharSequence spn, - CharSequence separator) { - final boolean plmnValid = !TextUtils.isEmpty(plmn); - final boolean spnValid = !TextUtils.isEmpty(spn); - if (plmnValid && spnValid) { - return new StringBuilder().append(plmn).append(separator).append(spn).toString(); - } else if (plmnValid) { - return plmn; - } else if (spnValid) { - return spn; - } else { - return ""; - } - } - - /** - * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra - * separator added so there are no extra separators that are not needed. - */ - private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) { - int length = sequences.length; - if (length == 0) return ""; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (!TextUtils.isEmpty(sequences[i])) { - if (!TextUtils.isEmpty(sb)) { - sb.append(separator); + @Override + public void finishedWakingUp() { + mView.setSelected(true); } - sb.append(sequences[i]); - } - } - return sb.toString(); - } - - private static List<CharSequence> append(List<CharSequence> list, CharSequence string) { - if (!TextUtils.isEmpty(string)) { - list.add(string); - } - return list; - } - - private CharSequence getCarrierHelpTextForSimState(int simState, - String plmn, String spn) { - int carrierHelpTextId = 0; - CarrierTextController.StatusMode status = getStatusForIccState(simState); - switch (status) { - case NetworkLocked: - carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; - break; - - case SimMissing: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; - break; - - case SimPermDisabled: - carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; - break; - - case SimMissingLocked: - carrierHelpTextId = R.string.keyguard_missing_sim_instructions; - break; + }; - case Normal: - case SimLocked: - case SimPukLocked: - break; - } + @Inject + public CarrierTextController(CarrierText view, + CarrierTextManager.Builder carrierTextManagerBuilder, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + super(view); - return mContext.getText(carrierHelpTextId); + mCarrierTextManager = carrierTextManagerBuilder + .setShowAirplaneMode(mView.getShowAirplaneMode()) + .setShowMissingSim(mView.getShowMissingSim()) + .build(); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; } - public static class Builder { - private final Context mContext; - private final String mSeparator; - private boolean mShowAirplaneMode; - private boolean mShowMissingSim; - - @Inject - public Builder(Context context, @Main Resources resources) { - mContext = context; - mSeparator = resources.getString( - com.android.internal.R.string.kg_text_message_separator); - } - - - public Builder setShowAirplaneMode(boolean showAirplaneMode) { - mShowAirplaneMode = showAirplaneMode; - return this; - } - - public Builder setShowMissingSim(boolean showMissingSim) { - mShowMissingSim = showMissingSim; - return this; - } - - public CarrierTextController build() { - return new CarrierTextController( - mContext, mSeparator, mShowAirplaneMode, mShowMissingSim); - } + @Override + protected void onInit() { + super.onInit(); + mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive()); } - /** - * Data structure for passing information to CarrierTextController subscribers - */ - public static final class CarrierTextCallbackInfo { - public final CharSequence carrierText; - public final CharSequence[] listOfCarriers; - public final boolean anySimReady; - public final int[] subscriptionIds; - public boolean airplaneMode; - - @VisibleForTesting - public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, - boolean anySimReady, int[] subscriptionIds) { - this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false); - } - @VisibleForTesting - public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, - boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) { - this.carrierText = carrierText; - this.listOfCarriers = listOfCarriers; - this.anySimReady = anySimReady; - this.subscriptionIds = subscriptionIds; - this.airplaneMode = airplaneMode; - } + @Override + protected void onViewAttached() { + mCarrierTextManager.setListening(mCarrierTextCallback); } - /** - * Callback to communicate to Views - */ - public interface CarrierTextCallback { - /** - * Provides updated carrier information. - */ - default void updateCarrierInfo(CarrierTextCallbackInfo info) {}; - - /** - * Notifies the View that the device is going to sleep - */ - default void startedGoingToSleep() {}; - - /** - * Notifies the View that the device finished waking up - */ - default void finishedWakingUp() {}; + @Override + protected void onViewDetached() { + mCarrierTextManager.setListening(null); } } diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java new file mode 100644 index 000000000000..cfef6cb399ed --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.wifi.WifiManager; +import android.telephony.ServiceState; +import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.settingslib.WirelessUtils; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.telephony.TelephonyListenerManager; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.inject.Inject; + +/** + * Controller that generates text including the carrier names and/or the status of all the SIM + * interfaces in the device. Through a callback, the updates can be retrieved either as a list or + * separated by a given separator {@link CharSequence}. + */ +public class CarrierTextManager { + private static final boolean DEBUG = KeyguardConstants.DEBUG; + private static final String TAG = "CarrierTextController"; + + private final boolean mIsEmergencyCallCapable; + private final Executor mMainExecutor; + private final Executor mBgExecutor; + private boolean mTelephonyCapable; + private final boolean mShowMissingSim; + private final boolean mShowAirplaneMode; + private final AtomicBoolean mNetworkSupported = new AtomicBoolean(); + @VisibleForTesting + protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final WifiManager mWifiManager; + private final boolean[] mSimErrorState; + private final int mSimSlotsNumber; + @Nullable // Check for nullability before dispatching + private CarrierTextCallback mCarrierTextCallback; + private final Context mContext; + private final TelephonyManager mTelephonyManager; + private final CharSequence mSeparator; + private final TelephonyListenerManager mTelephonyListenerManager; + private final WakefulnessLifecycle mWakefulnessLifecycle; + private final WakefulnessLifecycle.Observer mWakefulnessObserver = + new WakefulnessLifecycle.Observer() { + @Override + public void onFinishedWakingUp() { + final CarrierTextCallback callback = mCarrierTextCallback; + if (callback != null) callback.finishedWakingUp(); + } + + @Override + public void onStartedGoingToSleep() { + final CarrierTextCallback callback = mCarrierTextCallback; + if (callback != null) callback.startedGoingToSleep(); + } + }; + + @VisibleForTesting + protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onRefreshCarrierInfo() { + if (DEBUG) { + Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " + + Boolean.toString(mTelephonyCapable)); + } + updateCarrierText(); + } + + @Override + public void onTelephonyCapable(boolean capable) { + if (DEBUG) { + Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " + + Boolean.toString(capable)); + } + mTelephonyCapable = capable; + updateCarrierText(); + } + + public void onSimStateChanged(int subId, int slotId, int simState) { + if (slotId < 0 || slotId >= mSimSlotsNumber) { + Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId + + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable)); + return; + } + + if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState)); + if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) { + mSimErrorState[slotId] = true; + updateCarrierText(); + } else if (mSimErrorState[slotId]) { + mSimErrorState[slotId] = false; + updateCarrierText(); + } + } + }; + + private final ActiveDataSubscriptionIdListener mPhoneStateListener = + new ActiveDataSubscriptionIdListener() { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + if (mNetworkSupported.get() && mCarrierTextCallback != null) { + updateCarrierText(); + } + } + }; + + /** + * The status of this lock screen. Primarily used for widgets on LockScreen. + */ + private enum StatusMode { + Normal, // Normal case (sim card present, it's not locked) + NetworkLocked, // SIM card is 'network locked'. + SimMissing, // SIM card is missing. + SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access + SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times + SimLocked, // SIM card is currently locked + SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure + SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM. + SimIoError, // SIM card is faulty + SimUnknown // SIM card is unknown + } + + /** + * Controller that provides updates on text with carriers names or SIM status. + * Used by {@link CarrierText}. + * + * @param separator Separator between different parts of the text + */ + private CarrierTextManager( + Context context, + CharSequence separator, + boolean showAirplaneMode, + boolean showMissingSim, + @Nullable WifiManager wifiManager, + TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, + WakefulnessLifecycle wakefulnessLifecycle, + @Main Executor mainExecutor, + @Background Executor bgExecutor, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + mContext = context; + mIsEmergencyCallCapable = telephonyManager.isVoiceCapable(); + + mShowAirplaneMode = showAirplaneMode; + mShowMissingSim = showMissingSim; + + mWifiManager = wifiManager; + mTelephonyManager = telephonyManager; + mSeparator = separator; + mTelephonyListenerManager = telephonyListenerManager; + mWakefulnessLifecycle = wakefulnessLifecycle; + mSimSlotsNumber = getTelephonyManager().getSupportedModemCount(); + mSimErrorState = new boolean[mSimSlotsNumber]; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mBgExecutor.execute(() -> { + boolean supported = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + if (supported && mNetworkSupported.compareAndSet(false, supported)) { + // This will set/remove the listeners appropriately. Note that it will never double + // add the listeners. + handleSetListening(mCarrierTextCallback); + } + }); + } + + private TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + /** + * Checks if there are faulty cards. Adds the text depending on the slot of the card + * + * @param text: current carrier text based on the sim state + * @param carrierNames names order by subscription order + * @param subOrderBySlot array containing the sub index for each slot ID + * @param noSims: whether a valid sim card is inserted + * @return text + */ + private CharSequence updateCarrierTextWithSimIoError(CharSequence text, + CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) { + final CharSequence carrier = ""; + CharSequence carrierTextForSimIOError = getCarrierTextForSimState( + TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier); + // mSimErrorState has the state of each sim indexed by slotID. + for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) { + if (!mSimErrorState[index]) { + continue; + } + // In the case when no sim cards are detected but a faulty card is inserted + // overwrite the text and only show "Invalid card" + if (noSims) { + return concatenate(carrierTextForSimIOError, + getContext().getText( + com.android.internal.R.string.emergency_calls_only), + mSeparator); + } else if (subOrderBySlot[index] != -1) { + int subIndex = subOrderBySlot[index]; + // prepend "Invalid card" when faulty card is inserted in slot 0 or 1 + carrierNames[subIndex] = concatenate(carrierTextForSimIOError, + carrierNames[subIndex], + mSeparator); + } else { + // concatenate "Invalid card" when faulty card is inserted in other slot + text = concatenate(text, carrierTextForSimIOError, mSeparator); + } + + } + return text; + } + + /** + * This may be called internally after retrieving the correct value of {@code mNetworkSupported} + * (assumed false to start). In that case, the following happens: + * <ul> + * <li> If there was a registered callback, and the network is supported, it will register + * listeners. + * <li> If there was not a registered callback, it will try to remove unregistered listeners + * which is a no-op + * </ul> + * + * This call will always be processed in a background thread. + */ + private void handleSetListening(CarrierTextCallback callback) { + if (callback != null) { + mCarrierTextCallback = callback; + if (mNetworkSupported.get()) { + // Keyguard update monitor expects callbacks from main thread + mMainExecutor.execute(() -> mKeyguardUpdateMonitor.registerCallback(mCallback)); + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); + } else { + // Don't listen and clear out the text when the device isn't a phone. + mMainExecutor.execute(() -> callback.updateCarrierInfo( + new CarrierTextCallbackInfo("", null, false, null) + )); + } + } else { + mCarrierTextCallback = null; + mMainExecutor.execute(() -> mKeyguardUpdateMonitor.removeCallback(mCallback)); + mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); + } + } + + /** + * Sets the listening status of this controller. If the callback is null, it is set to + * not listening. + * + * @param callback Callback to provide text updates + */ + public void setListening(CarrierTextCallback callback) { + mBgExecutor.execute(() -> handleSetListening(callback)); + } + + protected List<SubscriptionInfo> getSubscriptionInfo() { + return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false); + } + + protected void updateCarrierText() { + boolean allSimsMissing = true; + boolean anySimReadyAndInService = false; + CharSequence displayText = null; + List<SubscriptionInfo> subs = getSubscriptionInfo(); + + final int numSubs = subs.size(); + final int[] subsIds = new int[numSubs]; + // This array will contain in position i, the index of subscription in slot ID i. + // -1 if no subscription in that slot + final int[] subOrderBySlot = new int[mSimSlotsNumber]; + for (int i = 0; i < mSimSlotsNumber; i++) { + subOrderBySlot[i] = -1; + } + final CharSequence[] carrierNames = new CharSequence[numSubs]; + if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs); + + for (int i = 0; i < numSubs; i++) { + int subId = subs.get(i).getSubscriptionId(); + carrierNames[i] = ""; + subsIds[i] = subId; + subOrderBySlot[subs.get(i).getSimSlotIndex()] = i; + int simState = mKeyguardUpdateMonitor.getSimState(subId); + CharSequence carrierName = subs.get(i).getCarrierName(); + CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); + if (DEBUG) { + Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); + } + if (carrierTextForSimState != null) { + allSimsMissing = false; + carrierNames[i] = carrierTextForSimState; + } + if (simState == TelephonyManager.SIM_STATE_READY) { + ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); + if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) { + // hack for WFC (IWLAN) not turning off immediately once + // Wi-Fi is disassociated or disabled + if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + || (mWifiManager != null && mWifiManager.isWifiEnabled() + && mWifiManager.getConnectionInfo() != null + && mWifiManager.getConnectionInfo().getBSSID() != null)) { + if (DEBUG) { + Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); + } + anySimReadyAndInService = true; + } + } + } + } + // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY + // This condition will also be true always when numSubs == 0 + if (allSimsMissing && !anySimReadyAndInService) { + if (numSubs != 0) { + // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. + // This depends on mPlmn containing the text "Emergency calls only" when the radio + // has some connectivity. Otherwise, it should be null or empty and just show + // "No SIM card" + // Grab the first subscripton, because they all should contain the emergency text, + // described above. + displayText = makeCarrierStringOnEmergencyCapable( + getMissingSimMessage(), subs.get(0).getCarrierName()); + } else { + // We don't have a SubscriptionInfo to get the emergency calls only from. + // Grab it from the old sticky broadcast if possible instead. We can use it + // here because no subscriptions are active, so we don't have + // to worry about MSIM clashing. + CharSequence text = + getContext().getText(com.android.internal.R.string.emergency_calls_only); + Intent i = getContext().registerReceiver(null, + new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)); + if (i != null) { + String spn = ""; + String plmn = ""; + if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) { + spn = i.getStringExtra(TelephonyManager.EXTRA_SPN); + } + if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) { + plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN); + } + if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); + if (Objects.equals(plmn, spn)) { + text = plmn; + } else { + text = concatenate(plmn, spn, mSeparator); + } + } + displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text); + } + } + + if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames); + + displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot, + allSimsMissing); + + boolean airplaneMode = false; + // APM (airplane mode) != no carrier state. There are carrier services + // (e.g. WFC = Wi-Fi calling) which may operate in APM. + if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) { + displayText = getAirplaneModeMessage(); + airplaneMode = true; + } + + final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo( + displayText, + carrierNames, + !allSimsMissing, + subsIds, + airplaneMode); + postToCallback(info); + } + + @VisibleForTesting + protected void postToCallback(CarrierTextCallbackInfo info) { + final CarrierTextCallback callback = mCarrierTextCallback; + if (callback != null) { + mMainExecutor.execute(() -> callback.updateCarrierInfo(info)); + } + } + + private Context getContext() { + return mContext; + } + + private String getMissingSimMessage() { + return mShowMissingSim && mTelephonyCapable + ? getContext().getString(R.string.keyguard_missing_sim_message_short) : ""; + } + + private String getAirplaneModeMessage() { + return mShowAirplaneMode + ? getContext().getString(R.string.airplane_mode) : ""; + } + + /** + * Top-level function for creating carrier text. Makes text based on simState, PLMN + * and SPN as well as device capabilities, such as being emergency call capable. + * + * @return Carrier text if not in missing state, null otherwise. + */ + private CharSequence getCarrierTextForSimState(int simState, CharSequence text) { + CharSequence carrierText = null; + CarrierTextManager.StatusMode status = getStatusForIccState(simState); + switch (status) { + case Normal: + carrierText = text; + break; + + case SimNotReady: + // Null is reserved for denoting missing, in this case we have nothing to display. + carrierText = ""; // nothing to display yet. + break; + + case NetworkLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + mContext.getText(R.string.keyguard_network_locked_message), text); + break; + + case SimMissing: + carrierText = null; + break; + + case SimPermDisabled: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText( + R.string.keyguard_permanent_disabled_sim_message_short), + text); + break; + + case SimMissingLocked: + carrierText = null; + break; + + case SimLocked: + carrierText = makeCarrierStringOnLocked( + getContext().getText(R.string.keyguard_sim_locked_message), + text); + break; + + case SimPukLocked: + carrierText = makeCarrierStringOnLocked( + getContext().getText(R.string.keyguard_sim_puk_locked_message), + text); + break; + case SimIoError: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.keyguard_sim_error_message_short), + text); + break; + case SimUnknown: + carrierText = null; + break; + } + + return carrierText; + } + + /* + * Add emergencyCallMessage to carrier string only if phone supports emergency calls. + */ + private CharSequence makeCarrierStringOnEmergencyCapable( + CharSequence simMessage, CharSequence emergencyCallMessage) { + if (mIsEmergencyCallCapable) { + return concatenate(simMessage, emergencyCallMessage, mSeparator); + } + return simMessage; + } + + /* + * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in + * DSDS + */ + private CharSequence makeCarrierStringOnLocked(CharSequence simMessage, + CharSequence carrierName) { + final boolean simMessageValid = !TextUtils.isEmpty(simMessage); + final boolean carrierNameValid = !TextUtils.isEmpty(carrierName); + if (simMessageValid && carrierNameValid) { + return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template, + carrierName, simMessage); + } else if (simMessageValid) { + return simMessage; + } else if (carrierNameValid) { + return carrierName; + } else { + return ""; + } + } + + /** + * Determine the current status of the lock screen given the SIM state and other stuff. + */ + private CarrierTextManager.StatusMode getStatusForIccState(int simState) { + final boolean missingAndNotProvisioned = + !mKeyguardUpdateMonitor.isDeviceProvisioned() + && (simState == TelephonyManager.SIM_STATE_ABSENT + || simState == TelephonyManager.SIM_STATE_PERM_DISABLED); + + // Assume we're NETWORK_LOCKED if not provisioned + simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState; + switch (simState) { + case TelephonyManager.SIM_STATE_ABSENT: + return CarrierTextManager.StatusMode.SimMissing; + case TelephonyManager.SIM_STATE_NETWORK_LOCKED: + return CarrierTextManager.StatusMode.SimMissingLocked; + case TelephonyManager.SIM_STATE_NOT_READY: + return CarrierTextManager.StatusMode.SimNotReady; + case TelephonyManager.SIM_STATE_PIN_REQUIRED: + return CarrierTextManager.StatusMode.SimLocked; + case TelephonyManager.SIM_STATE_PUK_REQUIRED: + return CarrierTextManager.StatusMode.SimPukLocked; + case TelephonyManager.SIM_STATE_READY: + return CarrierTextManager.StatusMode.Normal; + case TelephonyManager.SIM_STATE_PERM_DISABLED: + return CarrierTextManager.StatusMode.SimPermDisabled; + case TelephonyManager.SIM_STATE_UNKNOWN: + return CarrierTextManager.StatusMode.SimUnknown; + case TelephonyManager.SIM_STATE_CARD_IO_ERROR: + return CarrierTextManager.StatusMode.SimIoError; + } + return CarrierTextManager.StatusMode.SimUnknown; + } + + private static CharSequence concatenate(CharSequence plmn, CharSequence spn, + CharSequence separator) { + final boolean plmnValid = !TextUtils.isEmpty(plmn); + final boolean spnValid = !TextUtils.isEmpty(spn); + if (plmnValid && spnValid) { + return new StringBuilder().append(plmn).append(separator).append(spn).toString(); + } else if (plmnValid) { + return plmn; + } else if (spnValid) { + return spn; + } else { + return ""; + } + } + + /** + * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra + * separator added so there are no extra separators that are not needed. + */ + private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) { + int length = sequences.length; + if (length == 0) return ""; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (!TextUtils.isEmpty(sequences[i])) { + if (!TextUtils.isEmpty(sb)) { + sb.append(separator); + } + sb.append(sequences[i]); + } + } + return sb.toString(); + } + + private static List<CharSequence> append(List<CharSequence> list, CharSequence string) { + if (!TextUtils.isEmpty(string)) { + list.add(string); + } + return list; + } + + private CharSequence getCarrierHelpTextForSimState(int simState, + String plmn, String spn) { + int carrierHelpTextId = 0; + CarrierTextManager.StatusMode status = getStatusForIccState(simState); + switch (status) { + case NetworkLocked: + carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled; + break; + + case SimMissing: + carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long; + break; + + case SimPermDisabled: + carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions; + break; + + case SimMissingLocked: + carrierHelpTextId = R.string.keyguard_missing_sim_instructions; + break; + + case Normal: + case SimLocked: + case SimPukLocked: + break; + } + + return mContext.getText(carrierHelpTextId); + } + + /** Injectable Buildeer for {@#link CarrierTextManager}. */ + public static class Builder { + private final Context mContext; + private final String mSeparator; + private final WifiManager mWifiManager; + private final TelephonyManager mTelephonyManager; + private final TelephonyListenerManager mTelephonyListenerManager; + private final WakefulnessLifecycle mWakefulnessLifecycle; + private final Executor mMainExecutor; + private final Executor mBgExecutor; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private boolean mShowAirplaneMode; + private boolean mShowMissingSim; + + @Inject + public Builder( + Context context, + @Main Resources resources, + @Nullable WifiManager wifiManager, + TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, + WakefulnessLifecycle wakefulnessLifecycle, + @Main Executor mainExecutor, + @Background Executor bgExecutor, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + mContext = context; + mSeparator = resources.getString( + com.android.internal.R.string.kg_text_message_separator); + mWifiManager = wifiManager; + mTelephonyManager = telephonyManager; + mTelephonyListenerManager = telephonyListenerManager; + mWakefulnessLifecycle = wakefulnessLifecycle; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + } + + /** */ + public Builder setShowAirplaneMode(boolean showAirplaneMode) { + mShowAirplaneMode = showAirplaneMode; + return this; + } + + /** */ + public Builder setShowMissingSim(boolean showMissingSim) { + mShowMissingSim = showMissingSim; + return this; + } + + /** Create a CarrierTextManager. */ + public CarrierTextManager build() { + return new CarrierTextManager( + mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager, + mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, + mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor); + } + } + /** + * Data structure for passing information to CarrierTextController subscribers + */ + public static final class CarrierTextCallbackInfo { + public final CharSequence carrierText; + public final CharSequence[] listOfCarriers; + public final boolean anySimReady; + public final int[] subscriptionIds; + public boolean airplaneMode; + + @VisibleForTesting + public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, + boolean anySimReady, int[] subscriptionIds) { + this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false); + } + + @VisibleForTesting + public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers, + boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) { + this.carrierText = carrierText; + this.listOfCarriers = listOfCarriers; + this.anySimReady = anySimReady; + this.subscriptionIds = subscriptionIds; + this.airplaneMode = airplaneMode; + } + } + + /** + * Callback to communicate to Views + */ + public interface CarrierTextCallback { + /** + * Provides updated carrier information. + */ + default void updateCarrierInfo(CarrierTextCallbackInfo info) {}; + + /** + * Notifies the View that the device is going to sleep + */ + default void startedGoingToSleep() {}; + + /** + * Notifies the View that the device finished waking up + */ + default void finishedWakingUp() {}; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java index 707ee298a55a..c4b02f62f291 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java @@ -16,34 +16,16 @@ package com.android.keyguard; -import static com.android.systemui.DejankUtils.whitelistIpcs; - -import android.app.ActivityOptions; -import android.app.ActivityTaskManager; import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.PowerManager; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.UserHandle; -import android.telecom.TelecomManager; -import android.telephony.TelephonyManager; import android.util.AttributeSet; -import android.util.Log; -import android.util.Slog; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.Button; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; -import com.android.systemui.util.EmergencyDialerConstants; /** * This class implements a smart emergency button that updates itself based @@ -53,34 +35,14 @@ import com.android.systemui.util.EmergencyDialerConstants; */ public class EmergencyButton extends Button { - private static final String LOG_TAG = "EmergencyButton"; private final EmergencyAffordanceManager mEmergencyAffordanceManager; private int mDownX; private int mDownY; - KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { - - @Override - public void onSimStateChanged(int subId, int slotId, int simState) { - updateEmergencyCallButton(); - } - - @Override - public void onPhoneStateChanged(int phoneState) { - updateEmergencyCallButton(); - } - }; private boolean mLongPressWasDragged; - public interface EmergencyButtonCallback { - public void onEmergencyButtonClickedWhenInCall(); - } - private LockPatternUtils mLockPatternUtils; - private PowerManager mPowerManager; - private EmergencyButtonCallback mEmergencyButtonCallback; - private final boolean mIsVoiceCapable; private final boolean mEnableEmergencyCallWhileSimLocked; public EmergencyButton(Context context) { @@ -89,34 +51,15 @@ public class EmergencyButton extends Button { public EmergencyButton(Context context, AttributeSet attrs) { super(context, attrs); - mIsVoiceCapable = getTelephonyManager().isVoiceCapable(); mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked); mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); } - private TelephonyManager getTelephonyManager() { - return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); mLockPatternUtils = new LockPatternUtils(mContext); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - setOnClickListener(v -> takeEmergencyCallAction()); if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { setOnLongClickListener(v -> { if (!mLongPressWasDragged @@ -127,7 +70,6 @@ public class EmergencyButton extends Button { return false; }); } - whitelistIpcs(this::updateEmergencyCallButton); } @Override @@ -165,65 +107,13 @@ public class EmergencyButton extends Button { return super.performLongClick(); } - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - updateEmergencyCallButton(); - } - - /** - * Shows the emergency dialer or returns the user to the existing call. - */ - public void takeEmergencyCallAction() { - MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL); - if (mPowerManager != null) { - mPowerManager.userActivity(SystemClock.uptimeMillis(), true); - } - try { - ActivityTaskManager.getService().stopSystemLockTaskMode(); - } catch (RemoteException e) { - Slog.w(LOG_TAG, "Failed to stop app pinning"); - } - if (isInCall()) { - resumeCall(); - if (mEmergencyButtonCallback != null) { - mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall(); - } - } else { - KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class); - if (updateMonitor != null) { - updateMonitor.reportEmergencyCallAction(true /* bypassHandler */); - } else { - Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway."); - } - TelecomManager telecomManager = getTelecommManager(); - if (telecomManager == null) { - Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer"); - return; - } - Intent emergencyDialIntent = - telecomManager.createLaunchEmergencyDialerIntent(null /* number*/) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_CLEAR_TOP) - .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE, - EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON); - - getContext().startActivityAsUser(emergencyDialIntent, - ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(), - new UserHandle(KeyguardUpdateMonitor.getCurrentUser())); - } - } - - private void updateEmergencyCallButton() { + void updateEmergencyCallButton(boolean isInCall, boolean isVoiceCapable, boolean simLocked) { boolean visible = false; - if (mIsVoiceCapable) { + if (isVoiceCapable) { // Emergency calling requires voice capability. - if (isInCall()) { + if (isInCall) { visible = true; // always show "return to call" if phone is off-hook } else { - final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class) - .isSimPinVoiceSecure(); if (simLocked) { // Some countries can't handle emergency calls while SIM is locked. visible = mEnableEmergencyCallWhileSimLocked; @@ -237,7 +127,7 @@ public class EmergencyButton extends Button { setVisibility(View.VISIBLE); int textId; - if (isInCall()) { + if (isInCall) { textId = com.android.internal.R.string.lockscreen_return_to_call; } else { textId = com.android.internal.R.string.lockscreen_emergency_call; @@ -247,26 +137,4 @@ public class EmergencyButton extends Button { setVisibility(View.GONE); } } - - public void setCallback(EmergencyButtonCallback callback) { - mEmergencyButtonCallback = callback; - } - - /** - * Resumes a call in progress. - */ - private void resumeCall() { - getTelecommManager().showInCallScreen(false); - } - - /** - * @return {@code true} if there is a call currently in progress. - */ - private boolean isInCall() { - return getTelecommManager().isInCall(); - } - - private TelecomManager getTelecommManager() { - return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); - } } diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java new file mode 100644 index 000000000000..4275189cfe26 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard; + +import static com.android.systemui.DejankUtils.whitelistIpcs; + +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.PowerManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.telecom.TelecomManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.keyguard.dagger.KeyguardBouncerScope; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.util.EmergencyDialerConstants; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; + +/** View Controller for {@link com.android.keyguard.EmergencyButton}. */ +@KeyguardBouncerScope +public class EmergencyButtonController extends ViewController<EmergencyButton> { + static final String LOG_TAG = "EmergencyButton"; + private final ConfigurationController mConfigurationController; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final TelephonyManager mTelephonyManager; + private final PowerManager mPowerManager; + private final ActivityTaskManager mActivityTaskManager; + private final TelecomManager mTelecomManager; + private final MetricsLogger mMetricsLogger; + + private EmergencyButtonCallback mEmergencyButtonCallback; + + private final KeyguardUpdateMonitorCallback mInfoCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + updateEmergencyCallButton(); + } + + @Override + public void onPhoneStateChanged(int phoneState) { + updateEmergencyCallButton(); + } + }; + + private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { + @Override + public void onConfigChanged(Configuration newConfig) { + updateEmergencyCallButton(); + } + }; + + private EmergencyButtonController(@Nullable EmergencyButton view, + ConfigurationController configurationController, + KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager, + PowerManager powerManager, ActivityTaskManager activityTaskManager, + @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) { + super(view); + mConfigurationController = configurationController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mTelephonyManager = telephonyManager; + mPowerManager = powerManager; + mActivityTaskManager = activityTaskManager; + mTelecomManager = telecomManager; + mMetricsLogger = metricsLogger; + } + + @Override + protected void onInit() { + whitelistIpcs(this::updateEmergencyCallButton); + } + + @Override + protected void onViewAttached() { + mKeyguardUpdateMonitor.registerCallback(mInfoCallback); + mConfigurationController.addCallback(mConfigurationListener); + mView.setOnClickListener(v -> takeEmergencyCallAction()); + } + + @Override + protected void onViewDetached() { + mKeyguardUpdateMonitor.removeCallback(mInfoCallback); + mConfigurationController.removeCallback(mConfigurationListener); + } + + private void updateEmergencyCallButton() { + if (mView != null) { + mView.updateEmergencyCallButton( + mTelecomManager != null && mTelecomManager.isInCall(), + mTelephonyManager.isVoiceCapable(), + mKeyguardUpdateMonitor.isSimPinVoiceSecure()); + } + } + + public void setEmergencyButtonCallback(EmergencyButtonCallback callback) { + mEmergencyButtonCallback = callback; + } + /** + * Shows the emergency dialer or returns the user to the existing call. + */ + public void takeEmergencyCallAction() { + mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL); + if (mPowerManager != null) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), true); + } + mActivityTaskManager.stopSystemLockTaskMode(); + if (mTelecomManager != null && mTelecomManager.isInCall()) { + mTelecomManager.showInCallScreen(false); + if (mEmergencyButtonCallback != null) { + mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall(); + } + } else { + mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */); + if (mTelecomManager == null) { + Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer"); + return; + } + Intent emergencyDialIntent = + mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE, + EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON); + + getContext().startActivityAsUser(emergencyDialIntent, + ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(), + new UserHandle(KeyguardUpdateMonitor.getCurrentUser())); + } + } + + /** */ + public interface EmergencyButtonCallback { + /** */ + void onEmergencyButtonClickedWhenInCall(); + } + + /** Injectable Factory for creating {@link EmergencyButtonController}. */ + public static class Factory { + private final ConfigurationController mConfigurationController; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final TelephonyManager mTelephonyManager; + private final PowerManager mPowerManager; + private final ActivityTaskManager mActivityTaskManager; + @Nullable + private final TelecomManager mTelecomManager; + private final MetricsLogger mMetricsLogger; + + @Inject + public Factory(ConfigurationController configurationController, + KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager, + PowerManager powerManager, ActivityTaskManager activityTaskManager, + @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) { + + mConfigurationController = configurationController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mTelephonyManager = telephonyManager; + mPowerManager = powerManager; + mActivityTaskManager = activityTaskManager; + mTelecomManager = telecomManager; + mMetricsLogger = metricsLogger; + } + + /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */ + public EmergencyButtonController create(EmergencyButton view) { + return new EmergencyButtonController(view, mConfigurationController, + mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager, + mTelecomManager, mMetricsLogger); + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 9f32c03d1de4..e41d5a3e34a7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -31,7 +31,7 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; -import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; +import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; @@ -44,6 +44,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey private final LockPatternUtils mLockPatternUtils; private final LatencyTracker mLatencyTracker; private final FalsingCollector mFalsingCollector; + private final EmergencyButtonController mEmergencyButtonController; private CountDownTimer mCountdownTimer; protected KeyguardMessageAreaController mMessageAreaController; private boolean mDismissing; @@ -73,12 +74,14 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, - LatencyTracker latencyTracker, FalsingCollector falsingCollector) { - super(view, securityMode, keyguardSecurityCallback); + LatencyTracker latencyTracker, FalsingCollector falsingCollector, + EmergencyButtonController emergencyButtonController) { + super(view, securityMode, keyguardSecurityCallback, emergencyButtonController); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; mFalsingCollector = falsingCollector; + mEmergencyButtonController = emergencyButtonController; KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); mMessageAreaController = messageAreaControllerFactory.create(kma); } @@ -87,6 +90,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey @Override public void onInit() { + super.onInit(); mMessageAreaController.init(); } @@ -95,10 +99,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey super.onViewAttached(); mView.setKeyDownListener(mKeyDownListener); mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); - EmergencyButton button = mView.findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(mEmergencyButtonCallback); - } + mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 276036c400e1..76a7473e25e8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -36,7 +36,6 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.dagger.KeyguardStatusViewComponent; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.navigationbar.NavigationBarController; @@ -46,12 +45,15 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import dagger.Lazy; + public class KeyguardDisplayManager { protected static final String TAG = "KeyguardDisplayManager"; private static boolean DEBUG = KeyguardConstants.DEBUG; private MediaRouter mMediaRouter = null; private final DisplayManager mDisplayService; + private final Lazy<NavigationBarController> mNavigationBarControllerLazy; private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; private final Context mContext; @@ -85,9 +87,11 @@ public class KeyguardDisplayManager { @Inject public KeyguardDisplayManager(Context context, + Lazy<NavigationBarController> navigationBarControllerLazy, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, @UiBackground Executor uiBgExecutor) { mContext = context; + mNavigationBarControllerLazy = navigationBarControllerLazy; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class)); mDisplayService = mContext.getSystemService(DisplayManager.class); @@ -240,7 +244,7 @@ public class KeyguardDisplayManager { // Leave this task to {@link StatusBarKeyguardViewManager} if (displayId == DEFAULT_DISPLAY) return; - NavigationBarView navBarView = Dependency.get(NavigationBarController.class) + NavigationBarView navBarView = mNavigationBarControllerLazy.get() .getNavigationBarView(displayId); // We may not have nav bar on a display. if (navBarView == null) return; diff --git a/packages/SystemUI/src/com/android/keyguard/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/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 05f33a9d0997..3d42da2e5158 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -42,6 +42,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final SecurityMode mSecurityMode; private final KeyguardSecurityCallback mKeyguardSecurityCallback; private final EmergencyButton mEmergencyButton; + private final EmergencyButtonController mEmergencyButtonController; private boolean mPaused; @@ -69,11 +70,18 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> }; protected KeyguardInputViewController(T view, SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback) { + KeyguardSecurityCallback keyguardSecurityCallback, + EmergencyButtonController emergencyButtonController) { super(view); mSecurityMode = securityMode; mKeyguardSecurityCallback = keyguardSecurityCallback; mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button); + mEmergencyButtonController = emergencyButtonController; + } + + @Override + protected void onInit() { + mEmergencyButtonController.init(); } @Override @@ -157,6 +165,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final Resources mResources; private final LiftToActivateListener mLiftToActivateListener; private final TelephonyManager mTelephonyManager; + private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final FalsingCollector mFalsingCollector; private final boolean mIsNewLayoutEnabled; @@ -168,6 +177,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, @Main Resources resources, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, + EmergencyButtonController.Factory emergencyButtonControllerFactory, FeatureFlags featureFlags) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; @@ -178,6 +188,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mResources = resources; mLiftToActivateListener = liftToActivateListener; mTelephonyManager = telephonyManager; + mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; mFalsingCollector = falsingCollector; mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled(); } @@ -185,33 +196,40 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> /** Create a new {@link KeyguardInputViewController}. */ public KeyguardInputViewController create(KeyguardInputView keyguardInputView, SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) { + EmergencyButtonController emergencyButtonController = + mEmergencyButtonControllerFactory.create( + keyguardInputView.findViewById(R.id.emergency_call_button)); + if (keyguardInputView instanceof KeyguardPatternView) { return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mLatencyTracker, mFalsingCollector, - mMessageAreaControllerFactory); + emergencyButtonController, mMessageAreaControllerFactory); } else if (keyguardInputView instanceof KeyguardPasswordView) { return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mInputMethodManager, mMainExecutor, mResources, mFalsingCollector); + mInputMethodManager, emergencyButtonController, mMainExecutor, mResources, + mFalsingCollector); + } else if (keyguardInputView instanceof KeyguardPINView) { return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, - mLiftToActivateListener, mFalsingCollector, mIsNewLayoutEnabled); + mLiftToActivateListener, emergencyButtonController, mFalsingCollector, + mIsNewLayoutEnabled); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - mIsNewLayoutEnabled); + emergencyButtonController, mIsNewLayoutEnabled); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - mIsNewLayoutEnabled); + emergencyButtonController, mIsNewLayoutEnabled); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index 561ea4075291..568bea0e2d24 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -70,7 +70,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp void onThemeChanged() { TypedArray array = mContext.obtainStyledAttributes(new int[] { - android.R.attr.textColor + android.R.attr.textColorPrimary }); ColorStateList newTextColors = ColorStateList.valueOf(array.getColor(0, Color.RED)); array.recycle(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index e45dd8baa1d8..933a919efaad 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -112,11 +112,13 @@ public class KeyguardPasswordViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, InputMethodManager inputMethodManager, + EmergencyButtonController emergencyButtonController, @Main DelayableExecutor mainExecutor, @Main Resources resources, FalsingCollector falsingCollector) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, falsingCollector); + messageAreaControllerFactory, latencyTracker, falsingCollector, + emergencyButtonController); mKeyguardSecurityCallback = keyguardSecurityCallback; mInputMethodManager = inputMethodManager; mMainExecutor = mainExecutor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index e16c01a4aa36..f0d1e02ce872 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -32,7 +32,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternView; import com.android.internal.widget.LockPatternView.Cell; import com.android.internal.widget.LockscreenCredential; -import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; +import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.Utils; import com.android.systemui.R; @@ -54,6 +54,7 @@ public class KeyguardPatternViewController private final LockPatternUtils mLockPatternUtils; private final LatencyTracker mLatencyTracker; private final FalsingCollector mFalsingCollector; + private final EmergencyButtonController mEmergencyButtonController; private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; private KeyguardMessageAreaController mMessageAreaController; @@ -189,12 +190,14 @@ public class KeyguardPatternViewController KeyguardSecurityCallback keyguardSecurityCallback, LatencyTracker latencyTracker, FalsingCollector falsingCollector, + EmergencyButtonController emergencyButtonController, KeyguardMessageAreaController.Factory messageAreaControllerFactory) { - super(view, securityMode, keyguardSecurityCallback); + super(view, securityMode, keyguardSecurityCallback, emergencyButtonController); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; mFalsingCollector = falsingCollector; + mEmergencyButtonController = emergencyButtonController; mMessageAreaControllerFactory = messageAreaControllerFactory; KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); mMessageAreaController = mMessageAreaControllerFactory.create(kma); @@ -222,11 +225,7 @@ public class KeyguardPatternViewController } return false; }); - - EmergencyButton button = mView.findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(mEmergencyButtonCallback); - } + mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback); View cancelBtn = mView.findViewById(R.id.cancel_button); if (cancelBtn != null) { @@ -242,10 +241,7 @@ public class KeyguardPatternViewController super.onViewDetached(); mLockPatternView.setOnPatternListener(null); mLockPatternView.setOnTouchListener(null); - EmergencyButton button = mView.findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(null); - } + mEmergencyButtonController.setEmergencyButtonCallback(null); View cancelBtn = mView.findViewById(R.id.cancel_button); if (cancelBtn != null) { cancelBtn.setOnClickListener(null); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index b156f8169b50..8de4e7b557f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -57,9 +57,11 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, + EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, - messageAreaControllerFactory, latencyTracker, falsingCollector); + messageAreaControllerFactory, latencyTracker, falsingCollector, + emergencyButtonController); mLiftToActivateListener = liftToActivateListener; mFalsingCollector = falsingCollector; mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 49099fa18323..a456d42f5be5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -34,10 +34,11 @@ public class KeyguardPinViewController KeyguardSecurityCallback keyguardSecurityCallback, KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, + EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - falsingCollector); + emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; view.setIsNewLayoutEnabled(isNewLayoutEnabled); } 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/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java index 631c24844417..69328cd5d344 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java @@ -23,7 +23,6 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.Dependency; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -49,24 +48,27 @@ public class KeyguardSecurityModel { private final boolean mIsPukScreenAvailable; private final LockPatternUtils mLockPatternUtils; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Inject - KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) { + KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils, + KeyguardUpdateMonitor keyguardUpdateMonitor) { mIsPukScreenAvailable = resources.getBoolean( com.android.internal.R.bool.config_enable_puk_unlock_screen); mLockPatternUtils = lockPatternUtils; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; } public SecurityMode getSecurityMode(int userId) { - KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class); - if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId( - monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED))) { + mKeyguardUpdateMonitor.getNextSubIdForState( + TelephonyManager.SIM_STATE_PUK_REQUIRED))) { return SecurityMode.SimPuk; } if (SubscriptionManager.isValidSubscriptionId( - monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED))) { + mKeyguardUpdateMonitor.getNextSubIdForState( + TelephonyManager.SIM_STATE_PIN_REQUIRED))) { return SecurityMode.SimPin; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index f1b504e9f941..33d47fe13f38 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -44,15 +44,18 @@ public class KeyguardSecurityViewFlipperController private final List<KeyguardInputViewController<KeyguardInputView>> mChildren = new ArrayList<>(); private final LayoutInflater mLayoutInflater; + private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final Factory mKeyguardSecurityViewControllerFactory; @Inject protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view, LayoutInflater layoutInflater, - KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) { + KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory, + EmergencyButtonController.Factory emergencyButtonControllerFactory) { super(view); mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory; mLayoutInflater = layoutInflater; + mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; } @Override @@ -111,7 +114,8 @@ public class KeyguardSecurityViewFlipperController if (childController == null) { childController = new NullKeyguardInputViewController( - securityMode, keyguardSecurityCallback); + securityMode, keyguardSecurityCallback, + mEmergencyButtonControllerFactory.create(null)); } return childController; @@ -140,8 +144,9 @@ public class KeyguardSecurityViewFlipperController private static class NullKeyguardInputViewController extends KeyguardInputViewController<KeyguardInputView> { protected NullKeyguardInputViewController(SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback) { - super(null, securityMode, keyguardSecurityCallback); + KeyguardSecurityCallback keyguardSecurityCallback, + EmergencyButtonController emergencyButtonController) { + super(null, securityMode, keyguardSecurityCallback, emergencyButtonController); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index b2bf2e674b33..fddbb3cdfc84 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -79,10 +79,10 @@ public class KeyguardSimPinViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, - boolean isNewLayoutEnabled) { + EmergencyButtonController emergencyButtonController, boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - falsingCollector); + emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 620db481e4a2..50bd0c71752b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -37,7 +37,6 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; @@ -86,10 +85,10 @@ public class KeyguardSimPukViewController KeyguardMessageAreaController.Factory messageAreaControllerFactory, LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, - boolean isNewLayoutEnabled) { + EmergencyButtonController emergencyButtonController, boolean isNewLayoutEnabled) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - falsingCollector); + emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); @@ -198,8 +197,7 @@ public class KeyguardSimPukViewController if (count < 2) { msg = rez.getString(R.string.kg_puk_enter_puk_hint); } else { - SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) - .getSubscriptionInfoForSubId(mSubId); + SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); if (info != null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 83c2d1e7f684..96e69ad019b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -49,10 +49,8 @@ import androidx.slice.widget.SliceContent; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.wakelock.KeepAwakeAnimationListener; import java.io.FileDescriptor; @@ -299,8 +297,23 @@ public class KeyguardSliceView extends LinearLayout { void onDensityOrFontScaleChanged() { mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size); mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size); + + for (int i = 0; i < mRow.getChildCount(); i++) { + View child = mRow.getChildAt(i); + if (child instanceof KeyguardSliceTextView) { + ((KeyguardSliceTextView) child).onDensityOrFontScaleChanged(); + } + } } + void onOverlayChanged() { + for (int i = 0; i < mRow.getChildCount(); i++) { + View child = mRow.getChildAt(i); + if (child instanceof KeyguardSliceTextView) { + ((KeyguardSliceTextView) child).onOverlayChanged(); + } + } + } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("KeyguardSliceView:"); pw.println(" mTitle: " + (mTitle == null ? "null" : mTitle.getVisibility() == VISIBLE)); @@ -466,8 +479,7 @@ public class KeyguardSliceView extends LinearLayout { * Representation of an item that appears under the clock on main keyguard message. */ @VisibleForTesting - static class KeyguardSliceTextView extends TextView implements - ConfigurationController.ConfigurationListener { + static class KeyguardSliceTextView extends TextView { private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; @StyleRes @@ -479,24 +491,10 @@ public class KeyguardSliceView extends LinearLayout { setEllipsize(TruncateAt.END); } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Dependency.get(ConfigurationController.class).addCallback(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(ConfigurationController.class).removeCallback(this); - } - - @Override public void onDensityOrFontScaleChanged() { updatePadding(); } - @Override public void onOverlayChanged() { setTextAppearance(sStyleId); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java index 1b0a7faeddab..8038ce4c7b69 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java @@ -83,6 +83,10 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie public void onDensityOrFontScaleChanged() { mView.onDensityOrFontScaleChanged(); } + @Override + public void onOverlayChanged() { + mView.onOverlayChanged(); + } }; Observer<Slice> mObserver = new Observer<Slice>() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index fea152abe36a..5db4f9e61140 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -34,7 +34,6 @@ import android.widget.TextView; import androidx.core.graphics.ColorUtils; import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; import java.io.FileDescriptor; @@ -56,7 +55,6 @@ public class KeyguardStatusView extends GridLayout { private final IActivityManager mIActivityManager; private TextView mLogoutView; - private boolean mCanShowLogout = true; // by default, try to show the logout button here private KeyguardClockSwitch mClockView; private TextView mOwnerInfo; private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here @@ -130,11 +128,6 @@ public class KeyguardStatusView extends GridLayout { } } - void setCanShowLogout(boolean canShowLogout) { - mCanShowLogout = canShowLogout; - updateLogoutView(); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -159,10 +152,7 @@ public class KeyguardStatusView extends GridLayout { mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged); onSliceContentChanged(); - boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive(); - setEnableMarquee(shouldMarquee); updateOwnerInfo(); - updateLogoutView(); updateDark(); } @@ -209,11 +199,11 @@ public class KeyguardStatusView extends GridLayout { return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0; } - void updateLogoutView() { + void updateLogoutView(boolean shouldShowLogout) { if (mLogoutView == null) { return; } - mLogoutView.setVisibility(mCanShowLogout && shouldShowLogout() ? VISIBLE : GONE); + mLogoutView.setVisibility(shouldShowLogout ? VISIBLE : GONE); // Logout button will stay in language of user 0 if we don't set that manually. mLogoutView.setText(mContext.getResources().getString( com.android.internal.R.string.global_action_logout)); @@ -313,11 +303,6 @@ public class KeyguardStatusView extends GridLayout { } } - private boolean shouldShowLogout() { - return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled() - && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM; - } - private void onLogoutClicked(View view) { int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); try { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 934e768f9ba8..31ec00378135 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import android.os.UserHandle; import android.util.Slog; import android.view.View; @@ -78,6 +79,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV @Override public void onInit() { mKeyguardClockSwitchController.init(); + mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive()); + mView.updateLogoutView(shouldShowLogout()); } @Override @@ -245,6 +248,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } } + private boolean shouldShowLogout() { + return mKeyguardUpdateMonitor.isLogoutEnabled() + && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM; + } + private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override @@ -267,10 +275,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardSliceViewController.updateLockScreenMode(mode); if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { mView.setCanShowOwnerInfo(false); - mView.setCanShowLogout(false); + mView.updateLogoutView(false); } else { mView.setCanShowOwnerInfo(true); - mView.setCanShowLogout(false); + mView.updateLogoutView(false); } updateAodIcons(); } @@ -296,7 +304,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing); refreshTime(); mView.updateOwnerInfo(); - mView.updateLogoutView(); + mView.updateLogoutView(shouldShowLogout()); } } @@ -314,12 +322,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV public void onUserSwitchComplete(int userId) { mKeyguardClockSwitchController.refreshFormat(); mView.updateOwnerInfo(); - mView.updateLogoutView(); + mView.updateLogoutView(shouldShowLogout()); } @Override public void onLogoutEnabledChanged() { - mView.updateLogoutView(); + mView.updateLogoutView(shouldShowLogout()); } }; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 69e6ed043172..9abc1e7adf86 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -22,7 +22,6 @@ import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_STOPPED; import static android.content.Intent.ACTION_USER_UNLOCKED; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; -import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; @@ -76,11 +75,11 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.telephony.CarrierConfigManager; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.Log; import android.util.SparseArray; @@ -107,6 +106,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; import com.android.systemui.util.RingerModeTracker; @@ -290,6 +290,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mDeviceInteractive; private boolean mScreenOn; private SubscriptionManager mSubscriptionManager; + private final TelephonyListenerManager mTelephonyListenerManager; private List<SubscriptionInfo> mSubscriptionInfo; private TrustManager mTrustManager; private UserManager mUserManager; @@ -358,7 +359,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab }; @VisibleForTesting - public PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + public TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener = + new TelephonyCallback.ActiveDataSubscriptionIdListener() { @Override public void onActiveDataSubscriptionIdChanged(int subId) { mActiveMobileDataSubscription = subId; @@ -1614,9 +1616,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab StatusBarStateController statusBarStateController, LockPatternUtils lockPatternUtils, AuthController authController, + TelephonyListenerManager telephonyListenerManager, FeatureFlags featureFlags) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); + mTelephonyListenerManager = telephonyListenerManager; mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged); mBackgroundExecutor = backgroundExecutor; @@ -1865,8 +1869,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (mTelephonyManager != null) { - mTelephonyManager.listen(mPhoneStateListener, - LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); // Set initial sim states values. for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) { int state = mTelephonyManager.getSimState(slot); @@ -3123,7 +3126,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); if (telephony != null) { - telephony.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); } mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener); diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java new file mode 100644 index 000000000000..c4be1ba53503 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.clock; + +import java.util.List; + +import dagger.Module; +import dagger.Provides; + +/** Dagger Module for clock package. */ +@Module +public abstract class ClockModule { + + /** */ + @Provides + public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) { + return clockManager.getClockInfos(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java index 5ef35be8df51..b6413cb61deb 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java @@ -28,11 +28,12 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Dependency; import java.io.FileNotFoundException; import java.util.List; -import java.util.function.Supplier; + +import javax.inject.Inject; +import javax.inject.Provider; /** * Exposes custom clock face options and provides realistic preview images. @@ -65,15 +66,12 @@ public final class ClockOptionsProvider extends ContentProvider { private static final String CONTENT_SCHEME = "content"; private static final String AUTHORITY = "com.android.keyguard.clock"; - private final Supplier<List<ClockInfo>> mClocksSupplier; - - public ClockOptionsProvider() { - this(() -> Dependency.get(ClockManager.class).getClockInfos()); - } + @Inject + public Provider<List<ClockInfo>> mClockInfosProvider; @VisibleForTesting - ClockOptionsProvider(Supplier<List<ClockInfo>> clocksSupplier) { - mClocksSupplier = clocksSupplier; + ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) { + mClockInfosProvider = clockInfosProvider; } @Override @@ -99,7 +97,7 @@ public final class ClockOptionsProvider extends ContentProvider { } MatrixCursor cursor = new MatrixCursor(new String[] { COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW}); - List<ClockInfo> clocks = mClocksSupplier.get(); + List<ClockInfo> clocks = mClockInfosProvider.get(); for (int i = 0; i < clocks.size(); i++) { ClockInfo clock = clocks.get(i); cursor.newRow() @@ -139,7 +137,7 @@ public final class ClockOptionsProvider extends ContentProvider { throw new FileNotFoundException("Invalid preview url, missing id"); } ClockInfo clock = null; - List<ClockInfo> clocks = mClocksSupplier.get(); + List<ClockInfo> clocks = mClockInfosProvider.get(); for (int i = 0; i < clocks.size(); i++) { if (id.equals(clocks.get(i).getId())) { clock = clocks.get(i); diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java new file mode 100644 index 000000000000..49a617eeb6c0 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.dagger; + +import com.android.keyguard.KeyguardStatusViewController; +import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; + +import dagger.BindsInstance; +import dagger.Subcomponent; + +/** + * Subcomponent for helping work with KeyguardStatusView and its children. + * + * TODO: unify this with {@link KeyguardStatusViewComponent} + */ +@Subcomponent(modules = {KeyguardStatusBarViewModule.class}) +@KeyguardStatusBarViewScope +public interface KeyguardStatusBarViewComponent { + /** Simple factory for {@link KeyguardStatusBarViewComponent}. */ + @Subcomponent.Factory + interface Factory { + KeyguardStatusBarViewComponent build(@BindsInstance KeyguardStatusBarView view); + } + + /** Builds a {@link KeyguardStatusViewController}. */ + KeyguardStatusBarViewController getKeyguardStatusBarViewController(); +} diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java new file mode 100644 index 000000000000..a6725234e4af --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.dagger; + +import com.android.keyguard.CarrierText; +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.KeyguardStatusBarView; + +import dagger.Module; +import dagger.Provides; + +/** Dagger module for {@link KeyguardStatusBarViewComponent}. */ +@Module +public abstract class KeyguardStatusBarViewModule { + @Provides + @KeyguardStatusBarViewScope + static CarrierText getCarrierText(KeyguardStatusBarView view) { + return view.findViewById(R.id.keyguard_carrier_text); + } +} diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java index 1cadf3be38ee..ba0642f57a88 100644 --- a/media/java/android/media/metrics/PlaybackComponent.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java @@ -14,22 +14,19 @@ * limitations under the License. */ -package android.media.metrics; +package com.android.keyguard.dagger; -import android.annotation.NonNull; +import static java.lang.annotation.RetentionPolicy.RUNTIME; -/** - * Interface for playback related components used by playback metrics. - */ -public interface PlaybackComponent { +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; - /** - * Sets the playback ID of the component. - */ - void setPlaybackId(@NonNull String playbackId); +import javax.inject.Scope; - /** - * Gets playback ID. - */ - @NonNull String getPlaybackId(); -} +/** + * Scope annotation for singleton items within the StatusBarComponent. + */ +@Documented +@Retention(RUNTIME) +@Scope +public @interface KeyguardStatusBarViewScope {} diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java index 1b6476ce74df..d342377da49b 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java @@ -25,6 +25,8 @@ import dagger.Subcomponent; /** * Subcomponent for helping work with KeyguardStatusView and its children. + * + * TODO: unify this with {@link KeyguardStatusBarViewComponent} */ @Subcomponent(modules = {KeyguardStatusViewModule.class}) @KeyguardStatusViewScope diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index cd53a34fbbc2..ac99f73f7cb9 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -349,6 +349,9 @@ public class BatteryMeterView extends LinearLayout implements } private void setPercentTextAtCurrentLevel() { + if (mBatteryPercentView == null) { + return; + } mBatteryPercentView.setText( NumberFormat.getPercentInstance().format(mLevel / 100f)); setContentDescription( diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 06b486ec43d0..a686fc086b40 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -120,6 +120,7 @@ import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; @@ -350,6 +351,7 @@ public class Dependency { @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory; @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy; @Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy; + @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager; @Inject public Dependency() { @@ -545,6 +547,7 @@ public class Dependency { mProviders.put(StatusBar.class, mStatusBar::get); mProviders.put(ProtoTracer.class, mProtoTracer::get); mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get); + mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java index 4afa96987499..45a0ea19c8dc 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java @@ -32,7 +32,9 @@ import android.provider.Settings; import android.util.Log; import android.view.WindowManagerGlobal; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.statusbar.phone.SystemUIDialog; /** @@ -45,6 +47,11 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver { private static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in"; private Dialog mNewSessionDialog; + private final UiEventLogger mUiEventLogger; + + public GuestResumeSessionReceiver(UiEventLogger uiEventLogger) { + mUiEventLogger = uiEventLogger; + } /** * Register this receiver with the {@link BroadcastDispatcher} @@ -83,7 +90,7 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver { int notFirstLogin = Settings.System.getIntForUser( cr, SETTING_GUEST_HAS_LOGGED_IN, 0, userId); if (notFirstLogin != 0) { - mNewSessionDialog = new ResetSessionDialog(context, userId); + mNewSessionDialog = new ResetSessionDialog(context, mUiEventLogger, userId); mNewSessionDialog.show(); } else { Settings.System.putIntForUser( @@ -153,9 +160,10 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver { private static final int BUTTON_WIPE = BUTTON_NEGATIVE; private static final int BUTTON_DONTWIPE = BUTTON_POSITIVE; + private final UiEventLogger mUiEventLogger; private final int mUserId; - public ResetSessionDialog(Context context, int userId) { + ResetSessionDialog(Context context, UiEventLogger uiEventLogger, int userId) { super(context); setTitle(context.getString(R.string.guest_wipe_session_title)); @@ -167,15 +175,18 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver { setButton(BUTTON_DONTWIPE, context.getString(R.string.guest_wipe_session_dontwipe), this); + mUiEventLogger = uiEventLogger; mUserId = userId; } @Override public void onClick(DialogInterface dialog, int which) { if (which == BUTTON_WIPE) { + mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_WIPE); wipeGuestSession(getContext(), mUserId); dismiss(); } else if (which == BUTTON_DONTWIPE) { + mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE); cancel(); } } 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/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt new file mode 100644 index 000000000000..a1149fd2a447 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.Context +import android.hardware.biometrics.BiometricSourceType +import android.view.View +import android.view.ViewGroup +import com.android.internal.annotations.VisibleForTesting +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.settingslib.Utils +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.policy.ConfigurationController +import java.io.PrintWriter +import javax.inject.Inject + +/*** + * Controls the ripple effect that shows when authentication is successful. + * The ripple uses the accent color of the current theme. + */ +@SysUISingleton +class AuthRippleController @Inject constructor( + commandRegistry: CommandRegistry, + configurationController: ConfigurationController, + private val context: Context, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor +) { + @VisibleForTesting + var rippleView: AuthRippleView = AuthRippleView(context, attrs = null) + + val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { + override fun onBiometricAuthenticated( + userId: Int, + biometricSourceType: BiometricSourceType?, + isStrongBiometric: Boolean + ) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT) { + rippleView.startRipple() + } + } + } + + init { + val configurationChangedListener = object : ConfigurationController.ConfigurationListener { + override fun onUiModeChanged() { + updateRippleColor() + } + override fun onThemeChanged() { + updateRippleColor() + } + override fun onOverlayChanged() { + updateRippleColor() + } + } + configurationController.addCallback(configurationChangedListener) + + commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } + } + + fun setSensorLocation(x: Float, y: Float) { + rippleView.setSensorLocation(x, y) + } + + fun setViewHost(viewHost: View) { + // Add the ripple view to its host layout + viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { + override fun onViewDetachedFromWindow(view: View?) {} + + override fun onViewAttachedToWindow(view: View?) { + (viewHost as ViewGroup).addView(rippleView) + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + viewHost.removeOnAttachStateChangeListener(this) + } + }) + + updateRippleColor() + } + + private fun updateRippleColor() { + rippleView.setColor( + Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor) + } + + inner class AuthRippleCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + rippleView.startRipple() + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar auth-ripple") + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt new file mode 100644 index 000000000000..2e321338999c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.biometrics + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.PointF +import android.util.AttributeSet +import android.view.View +import com.android.systemui.statusbar.charging.RippleShader + +private const val RIPPLE_ANIMATION_DURATION: Long = 950 +private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f + +/** + * Expanding ripple effect on the transition from biometric authentication success to showing + * launcher. + */ +class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { + private var rippleInProgress: Boolean = false + private val rippleShader = RippleShader() + private val defaultColor: Int = 0xffffffff.toInt() + private val ripplePaint = Paint() + + init { + rippleShader.color = defaultColor + rippleShader.progress = 0f + rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH + ripplePaint.shader = rippleShader + visibility = View.GONE + } + + fun setSensorLocation(x: Float, y: Float) { + rippleShader.origin = PointF(x, y) + rippleShader.radius = maxOf(x, y, width - x, height - y).toFloat() + } + + fun startRipple() { + if (rippleInProgress) { + return // Ignore if ripple effect is already playing + } + val animator = ValueAnimator.ofFloat(0f, 1f) + animator.duration = RIPPLE_ANIMATION_DURATION + animator.addUpdateListener { animator -> + val now = animator.currentPlayTime + val phase = now / 30000f + rippleShader.progress = animator.animatedValue as Float + rippleShader.noisePhase = phase + invalidate() + } + animator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + rippleInProgress = false + visibility = View.GONE + } + }) + animator.start() + visibility = View.VISIBLE + rippleInProgress = true + } + + fun setColor(color: Int) { + rippleShader.color = color + } + + override fun onDraw(canvas: Canvas?) { + // draw over the entire screen + canvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), ripplePaint) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 98b3fe46ff57..078ec9fdfd1c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -87,6 +87,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull private final StatusBarStateController mStatusBarStateController; @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @NonNull private final DumpManager mDumpManager; + @NonNull private final AuthRippleController mAuthRippleController; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; @@ -305,7 +306,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @Main DelayableExecutor fgExecutor, @NonNull StatusBar statusBar, @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, - @NonNull DumpManager dumpManager) { + @NonNull DumpManager dumpManager, + @NonNull AuthRippleController authRippleController) { mContext = context; mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the @@ -317,6 +319,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mStatusBarStateController = statusBarStateController; mKeyguardViewManager = statusBarKeyguardViewManager; mDumpManager = dumpManager; + mAuthRippleController = authRippleController; mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -343,6 +346,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); + + mAuthRippleController.setViewHost(mStatusBar.getNotificationShadeWindowView()); + mAuthRippleController.setSensorLocation(getSensorLocation().centerX(), + getSensorLocation().centerY()); } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 98a703f595d2..521c49549653 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -24,6 +24,7 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; +import android.util.Log; import android.util.TypedValue; import java.util.ArrayList; @@ -39,6 +40,9 @@ public class UdfpsEnrollHelper { "com.android.systemui.biometrics.UdfpsEnrollHelper.scale"; private static final float SCALE = 0.5f; + private static final String NEW_COORDS_OVERRIDE = + "com.android.systemui.biometrics.UdfpsNewCoords"; + // Enroll with two center touches before going to guided enrollment private static final int NUM_CENTER_TOUCHES = 2; @@ -68,21 +72,42 @@ public class UdfpsEnrollHelper { // Number of pixels per mm float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1, context.getResources().getDisplayMetrics()); - - mGuidedEnrollmentPoints.add(new PointF( 2.00f * px, 0.00f * px)); - mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px)); - mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px)); - mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px)); - mGuidedEnrollmentPoints.add(new PointF( 0.88f * px, 2.70f * px)); - mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px)); - mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px)); - mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px)); - mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px)); - mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px)); - mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px)); - mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px)); - mGuidedEnrollmentPoints.add(new PointF( 2.29f * px, 4.92f * px)); - mGuidedEnrollmentPoints.add(new PointF( 3.82f * px, 1.78f * px)); + boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(), + NEW_COORDS_OVERRIDE, 0, + UserHandle.USER_CURRENT) != 0; + if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) { + Log.v(TAG, "Using new coordinates"); + mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px)); + mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px)); + mGuidedEnrollmentPoints.add(new PointF( 0.29f * px, 0.00f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, -2.35f * px)); + mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, -3.96f * px)); + mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px)); + mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px)); + mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px)); + mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px)); + mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, 3.96f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, 2.35f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.58f * px, 0.00f * px)); + } else { + Log.v(TAG, "Using old coordinates"); + mGuidedEnrollmentPoints.add(new PointF( 2.00f * px, 0.00f * px)); + mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px)); + mGuidedEnrollmentPoints.add(new PointF( 0.88f * px, 2.70f * px)); + mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px)); + mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px)); + mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px)); + mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px)); + mGuidedEnrollmentPoints.add(new PointF( 2.29f * px, 4.92f * px)); + mGuidedEnrollmentPoints.add(new PointF( 3.82f * px, 1.78f * px)); + } } boolean shouldShowProgressBar() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index 378907c400d7..e2748437c0c6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -62,7 +62,8 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { mBgProtection = findViewById(R.id.udfps_keyguard_fp_bg); - mWallpaperTexColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); + mWallpaperTexColor = Utils.getColorAttrDefaultColor(mContext, + R.attr.wallpaperTextColorAccent); mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java index 2569f7c107cb..f7beaf123b7c 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java @@ -26,7 +26,6 @@ import android.os.Message; import android.util.Log; import android.util.Slog; import android.view.Gravity; -import android.view.View; import android.view.WindowManager; /** @@ -98,8 +97,8 @@ public class WirelessChargingAnimation { private final Handler mHandler; private int mGravity; - private View mView; - private View mNextView; + private WirelessChargingLayout mView; + private WirelessChargingLayout mNextView; private WindowManager mWM; private Callback mCallback; @@ -112,7 +111,7 @@ public class WirelessChargingAnimation { mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; final WindowManager.LayoutParams params = mParams; - params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.height = WindowManager.LayoutParams.MATCH_PARENT; params.width = WindowManager.LayoutParams.MATCH_PARENT; params.format = PixelFormat.TRANSLUCENT; diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index e8407f01516b..ce0b51489490 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -20,6 +20,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.PointF; import android.graphics.drawable.Animatable; import android.util.AttributeSet; import android.util.TypedValue; @@ -29,8 +30,10 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import com.android.settingslib.Utils; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.charging.ChargingRippleView; import java.text.NumberFormat; @@ -38,7 +41,9 @@ import java.text.NumberFormat; * @hide */ public class WirelessChargingLayout extends FrameLayout { - public final static int UNKNOWN_BATTERY_LEVEL = -1; + public static final int UNKNOWN_BATTERY_LEVEL = -1; + private static final long RIPPLE_ANIMATION_DURATION = 2000; + private ChargingRippleView mRippleView; public WirelessChargingLayout(Context context) { super(context); @@ -120,6 +125,8 @@ public class WirelessChargingLayout extends FrameLayout { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); + mRippleView = findViewById(R.id.wireless_charging_ripple); + if (!showTransmittingBatteryLevel) { chargingAnimation.start(); animatorSet.start(); @@ -195,4 +202,21 @@ public class WirelessChargingLayout extends FrameLayout { animatorSetTransmitting.start(); animatorSetIcon.start(); } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (mRippleView != null) { + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + mRippleView.setColor( + Utils.getColorAttr(mRippleView.getContext(), + android.R.attr.colorAccent).getDefaultColor()); + mRippleView.setOrigin(new PointF(width / 2, height / 2)); + mRippleView.setRadius(Math.max(width, height) * 0.5f); + mRippleView.setDuration(RIPPLE_ANIMATION_DURATION); + mRippleView.startRipple(); + } + + super.onLayout(changed, left, top, right, bottom); + } } 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/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index ed3d5ec33b41..8e4e3081aff2 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -16,6 +16,7 @@ package com.android.systemui.dagger; +import com.android.keyguard.clock.ClockOptionsProvider; import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.Dependency; import com.android.systemui.InitController; @@ -150,4 +151,9 @@ public interface SysUIComponent { * Member injection into the supplied argument. */ void inject(KeyguardSliceProvider keyguardSliceProvider); + + /** + * Member injection into the supplied argument. + */ + void inject(ClockOptionsProvider clockOptionsProvider); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index b0067cd15c1b..b67db03a743c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -22,6 +22,7 @@ import android.content.Context; import androidx.annotation.Nullable; import com.android.internal.statusbar.IStatusBarService; +import com.android.keyguard.clock.ClockModule; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.BootCompleteCache; import com.android.systemui.BootCompleteCacheImpl; @@ -90,6 +91,7 @@ import dagger.Provides; @Module(includes = { AppOpsModule.class, AssistModule.class, + ClockModule.class, ControlsModule.class, DemoModeModule.class, FalsingModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 52c9f164a16e..b7f6e2bb965a 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -186,7 +186,7 @@ public class DozeSensors { new TriggerSensor( findSensorWithType(config.quickPickupSensorType()), Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, - false /* setting default */, + true /* setting default */, config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser()) && udfpsEnrolled, DozeLog.REASON_SENSOR_QUICK_PICKUP, diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 553e5a7c6d7d..18627189f188 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -73,8 +73,8 @@ import android.provider.Settings; import android.service.dreams.IDreamManager; import android.sysprop.TelephonyProperties; import android.telecom.TelecomManager; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.transition.AutoTransition; import android.transition.TransitionManager; @@ -138,6 +138,7 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.leak.RotationUtils; @@ -303,7 +304,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, AudioManager audioManager, IDreamManager iDreamManager, DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, - ConnectivityManager connectivityManager, TelephonyManager telephonyManager, + ConnectivityManager connectivityManager, + TelephonyListenerManager telephonyListenerManager, ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources, ConfigurationController configurationController, ActivityStarter activityStarter, KeyguardStateController keyguardStateController, UserManager userManager, @@ -361,7 +363,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); // get notified of phone state changes - telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + telephonyListenerManager.addServiceStateListener(mPhoneStateListener); contentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, mAirplaneModeObserver); @@ -2049,7 +2051,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } }; - PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + private final TelephonyCallback.ServiceStateListener mPhoneStateListener = + new TelephonyCallback.ServiceStateListener() { @Override public void onServiceStateChanged(ServiceState serviceState) { if (!mHasTelephony) return; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index de2e7c476e18..a747edd0580a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -30,6 +30,7 @@ import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -63,8 +64,11 @@ import dagger.Provides; /** * Dagger Module providing {@link StatusBar}. */ -@Module(subcomponents = {KeyguardStatusViewComponent.class, - KeyguardQsUserSwitchComponent.class, KeyguardUserSwitcherComponent.class}, +@Module(subcomponents = { + KeyguardQsUserSwitchComponent.class, + KeyguardStatusBarViewComponent.class, + KeyguardStatusViewComponent.class, + KeyguardUserSwitcherComponent.class}, includes = {FalsingModule.class}) public class KeyguardModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/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/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 9d43e0c13320..c8dfde1d717b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -209,6 +209,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private @TransitionMode int mNavigationBarMode; private ContentResolver mContentResolver; private boolean mAssistantAvailable; + private boolean mLongPressHomeEnabled; + private boolean mAssistantTouchGestureEnabled; private int mDisabledFlags1; private int mDisabledFlags2; @@ -309,7 +311,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, // Send the assistant availability upon connection if (isConnected) { - sendAssistantAvailability(mAssistantAvailable); + updateAssistantEntrypoints(); } } @@ -404,12 +406,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, new Handler(Looper.getMainLooper())) { @Override public void onChange(boolean selfChange, Uri uri) { - boolean available = mAssistManagerLazy.get() - .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; - if (mAssistantAvailable != available) { - sendAssistantAvailability(available); - mAssistantAvailable = available; - } + updateAssistantEntrypoints(); } }; @@ -531,6 +528,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + updateAssistantEntrypoints(); if (savedState != null) { mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0); @@ -823,7 +827,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, || mNavigationBarView.getHomeButton().getCurrentView() == null) { return; } - if (mHomeButtonLongPressDurationMs.isPresent()) { + if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) { mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(false); mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false); mNavigationBarView.getHomeButton().setOnLongClickListener(null); @@ -845,6 +849,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, pw.println(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation); pw.println(" mCurrentRotation=" + mCurrentRotation); pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs); + pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled); + pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled); if (mNavigationBarView != null) { pw.println(" mNavigationBarWindowState=" @@ -1206,9 +1212,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener, return true; } } - mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> { - mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration); - }); + if (mLongPressHomeEnabled) { + mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> { + mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration); + }); + } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: @@ -1480,15 +1488,23 @@ public class NavigationBar implements View.OnAttachStateChangeListener, | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); } - private void sendAssistantAvailability(boolean available) { + private void updateAssistantEntrypoints() { + mAssistantAvailable = mAssistManagerLazy.get() + .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; + mLongPressHomeEnabled = Settings.Secure.getInt(mContentResolver, + Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, 1) != 0; + mAssistantTouchGestureEnabled = Settings.Secure.getInt(mContentResolver, + Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, 1) != 0; if (mOverviewProxyService.getProxy() != null) { try { - mOverviewProxyService.getProxy().onAssistantAvailable(available + mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable + && mAssistantTouchGestureEnabled && QuickStepContract.isGesturalMode(mNavBarMode)); } catch (RemoteException e) { Log.w(TAG, "Unable to send assistant availability data to launcher"); } } + reconfigureHomeLongClick(); } // ----- Methods that DisplayNavigationBarController talks to ----- diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 93ce5a83f684..440c5efab008 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -175,7 +175,7 @@ public class PeopleSpaceUtils { /** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */ public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key, - int appWidgetId) { + int appWidgetId, Uri contactUri) { // Write relevant persisted storage. SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); @@ -186,27 +186,24 @@ public class PeopleSpaceUtils { widgetEditor.apply(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); - editor.putString(String.valueOf(appWidgetId), key.getShortcutId()); + String contactUriString = contactUri == null ? EMPTY_STRING : contactUri.toString(); + editor.putString(String.valueOf(appWidgetId), contactUriString); // Don't overwrite existing widgets with the same key. - Set<String> storedWidgetIds = new HashSet<>( - sp.getStringSet(key.toString(), new HashSet<>())); - storedWidgetIds.add(String.valueOf(appWidgetId)); - editor.putStringSet(key.toString(), storedWidgetIds); + addAppWidgetIdForKey(sp, editor, appWidgetId, key.toString()); + addAppWidgetIdForKey(sp, editor, appWidgetId, contactUriString); editor.apply(); } /** Removes stored data when tile is deleted. */ public static void removeSharedPreferencesStorageForTile(Context context, PeopleTileKey key, - int widgetId) { + int widgetId, String contactUriString) { // Delete widgetId mapping to key. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); - Set<String> storedWidgetIds = new HashSet<>( - sp.getStringSet(key.toString(), new HashSet<>())); - storedWidgetIds.remove(String.valueOf(widgetId)); - editor.putStringSet(key.toString(), storedWidgetIds); editor.remove(String.valueOf(widgetId)); + removeAppWidgetIdForKey(sp, editor, widgetId, key.toString()); + removeAppWidgetIdForKey(sp, editor, widgetId, contactUriString); editor.apply(); // Delete all data specifically mapped to widgetId. @@ -219,6 +216,23 @@ public class PeopleSpaceUtils { widgetEditor.apply(); } + private static void addAppWidgetIdForKey(SharedPreferences sp, SharedPreferences.Editor editor, + int widgetId, String storageKey) { + Set<String> storedWidgetIdsByKey = new HashSet<>( + sp.getStringSet(storageKey, new HashSet<>())); + storedWidgetIdsByKey.add(String.valueOf(widgetId)); + editor.putStringSet(storageKey, storedWidgetIdsByKey); + } + + private static void removeAppWidgetIdForKey(SharedPreferences sp, + SharedPreferences.Editor editor, + int widgetId, String storageKey) { + Set<String> storedWidgetIds = new HashSet<>( + sp.getStringSet(storageKey, new HashSet<>())); + storedWidgetIds.remove(String.valueOf(widgetId)); + editor.putStringSet(storageKey, storedWidgetIds); + } + /** Augments a single {@link PeopleSpaceTile} with notification content, if one is present. */ public static PeopleSpaceTile augmentSingleTileFromVisibleNotifications(Context context, PeopleSpaceTile tile, NotificationEntryManager notificationEntryManager) { @@ -256,7 +270,7 @@ public class PeopleSpaceUtils { PeopleSpaceTile tile, Map<PeopleTileKey, NotificationEntry> visibleNotifications) { PeopleTileKey key = new PeopleTileKey( tile.getId(), getUserId(tile), tile.getPackageName()); - + // TODO: Match missed calls with matching Uris in addition to keys. if (!visibleNotifications.containsKey(key)) { if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key.toString()); return tile; @@ -274,13 +288,17 @@ public class PeopleSpaceUtils { return tile; } boolean isMissedCall = Objects.equals(notification.category, CATEGORY_MISSED_CALL); - Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(notification); + List<Notification.MessagingStyle.Message> messages = + getMessagingStyleMessages(notification); - if (!isMissedCall && message == null) { + if (!isMissedCall && ArrayUtils.isEmpty(messages)) { if (DEBUG) Log.d(TAG, "Notification has no content"); return tile; } + // messages are in chronological order from most recent to least. + Notification.MessagingStyle.Message message = messages != null ? messages.get(0) : null; + int messagesCount = messages != null ? messages.size() : 0; // If it's a missed call notification and it doesn't include content, use fallback value, // otherwise, use notification content. boolean hasMessageText = message != null && !TextUtils.isEmpty(message.getText()); @@ -294,12 +312,16 @@ public class PeopleSpaceUtils { .setNotificationCategory(notification.category) .setNotificationContent(content) .setNotificationDataUri(dataUri) + .setMessagesCount(messagesCount) .build(); } - /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */ + /** + * Returns {@link Notification.MessagingStyle.Message}s from the Notification in chronological + * order from most recent to least. + */ @VisibleForTesting - public static Notification.MessagingStyle.Message getLastMessagingStyleMessage( + public static List<Notification.MessagingStyle.Message> getMessagingStyleMessages( Notification notification) { if (notification == null) { return null; @@ -312,7 +334,7 @@ public class PeopleSpaceUtils { Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages); sortedMessages.sort(Collections.reverseOrder( Comparator.comparing(Notification.MessagingStyle.Message::getTimestamp))); - return sortedMessages.get(0); + return sortedMessages; } } return null; diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index ae81ab04ec10..8d1b712e0807 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -58,8 +58,10 @@ import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; +import java.text.NumberFormat; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; @@ -82,6 +84,8 @@ class PeopleTileViewHelper { private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8; private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4; + private static final int MESSAGES_COUNT_OVERFLOW = 7; + private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+"); @@ -97,6 +101,9 @@ class PeopleTileViewHelper { private int mHeight; private int mLayoutSize; + private Locale mLocale; + private NumberFormat mIntegerFormat; + PeopleTileViewHelper(Context context, PeopleSpaceTile tile, int appWidgetId, Bundle options) { mContext = context; @@ -354,12 +361,38 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.image, View.GONE); views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message); } + 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. views.setViewVisibility(R.id.subtext, View.GONE); return views; } + // Some messaging apps only include up to 7 messages in their notifications. + private String getMessagesCountText(int count) { + if (count >= MESSAGES_COUNT_OVERFLOW) { + return mContext.getResources().getString( + R.string.messages_count_overflow_indicator, MESSAGES_COUNT_OVERFLOW); + } + + // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed + // non-null, so the first time this is called we will always get the appropriate + // NumberFormat, then never regenerate it unless the locale changes on the fly. + final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0); + if (!curLocale.equals(mLocale)) { + mLocale = curLocale; + mIntegerFormat = NumberFormat.getIntegerInstance(curLocale); + } + return mIntegerFormat.format(count); + } + private RemoteViews createStatusRemoteViews(ConversationStatus status) { RemoteViews views = getViewForContentLayout(); CharSequence statusText = status.getDescription(); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index 4ad685eae107..5be2d4a05f54 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -16,17 +16,23 @@ package com.android.systemui.people.widget; +import static android.Manifest.permission.READ_CONTACTS; +import static android.app.Notification.CATEGORY_MISSED_CALL; +import static android.app.Notification.EXTRA_PEOPLE_LIST; + import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING; import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID; import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID; import static com.android.systemui.people.PeopleSpaceUtils.USER_ID; import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotification; +import static com.android.systemui.people.PeopleSpaceUtils.getMessagingStyleMessages; import static com.android.systemui.people.PeopleSpaceUtils.getStoredWidgetIds; import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView; import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews; import android.annotation.Nullable; +import android.app.Notification; import android.app.NotificationChannel; import android.app.PendingIntent; import android.app.Person; @@ -39,6 +45,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.net.Uri; import android.os.Bundle; @@ -54,16 +61,20 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; @@ -83,11 +94,19 @@ public class PeopleSpaceWidgetManager { private SharedPreferences mSharedPrefs; private PeopleManager mPeopleManager; private NotificationEntryManager mNotificationEntryManager; + private PackageManager mPackageManager; public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); @GuardedBy("mLock") public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener> mListeners = new HashMap<>(); + @GuardedBy("mLock") + // Map of notification key mapped to widget IDs previously updated by the contact Uri field. + // This is required because on notification removal, the contact Uri field is stripped and we + // only have the notification key to determine which widget IDs should be updated. + private Map<String, Set<String>> mNotificationKeyToWidgetIdsMatchedByUri = new HashMap<>(); + private boolean mIsForTesting; + @Inject public PeopleSpaceWidgetManager(Context context) { if (DEBUG) Log.d(TAG, "constructor"); @@ -99,6 +118,7 @@ public class PeopleSpaceWidgetManager { mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); mPeopleManager = mContext.getSystemService(PeopleManager.class); mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); + mPackageManager = mContext.getPackageManager(); } /** @@ -108,12 +128,15 @@ public class PeopleSpaceWidgetManager { protected void setAppWidgetManager( AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager, PeopleManager peopleManager, LauncherApps launcherApps, - NotificationEntryManager notificationEntryManager) { + NotificationEntryManager notificationEntryManager, PackageManager packageManager, + boolean isForTesting) { mAppWidgetManager = appWidgetManager; mIPeopleManager = iPeopleManager; mPeopleManager = peopleManager; mLauncherApps = launcherApps; mNotificationEntryManager = notificationEntryManager; + mPackageManager = packageManager; + mIsForTesting = isForTesting; } /** @@ -222,6 +245,16 @@ public class PeopleSpaceWidgetManager { public void updateWidgetsWithNotificationChanged(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction) { if (DEBUG) Log.d(TAG, "updateWidgetsWithNotificationChanged called"); + if (mIsForTesting) { + updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction); + return; + } + ThreadUtils.postOnBackgroundThread( + () -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction)); + } + + private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn, + PeopleSpaceUtils.NotificationAction action) { try { String sbnShortcutId = sbn.getShortcutId(); if (sbnShortcutId == null) { @@ -235,23 +268,175 @@ public class PeopleSpaceWidgetManager { Log.d(TAG, "No app widget ids returned"); return; } + PeopleTileKey key = new PeopleTileKey( + sbnShortcutId, + sbn.getUser().getIdentifier(), + sbn.getPackageName()); + if (!key.isValid()) { + Log.d(TAG, "Invalid key"); + return; + } synchronized (mLock) { - PeopleTileKey key = new PeopleTileKey( - sbnShortcutId, - UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(), - sbn.getPackageName()); - Set<String> storedWidgetIds = getStoredWidgetIds(mSharedPrefs, key); - for (String widgetIdString : storedWidgetIds) { - int widgetId = Integer.parseInt(widgetIdString); - if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); - updateStorageAndViewWithNotificationData(sbn, notificationAction, widgetId); - } + // First, update People Tiles associated with the Notification's package/shortcut. + Set<String> tilesUpdatedByKey = getStoredWidgetIds(mSharedPrefs, key); + updateWidgetIdsForNotificationAction(tilesUpdatedByKey, sbn, action); + + // Then, update People Tiles across other packages that use the same Uri. + updateTilesByUri(key, sbn, action); } } catch (Exception e) { Log.e(TAG, "Exception: " + e); } } + /** Updates {@code widgetIdsToUpdate} with {@code action}. */ + private void updateWidgetIdsForNotificationAction(Set<String> widgetIdsToUpdate, + StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction action) { + for (String widgetIdString : widgetIdsToUpdate) { + int widgetId = Integer.parseInt(widgetIdString); + PeopleSpaceTile storedTile = getTileForExistingWidget(widgetId); + if (storedTile == null) { + if (DEBUG) Log.d(TAG, "Could not find stored tile for notification"); + continue; + } + if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); + updateStorageAndViewWithNotificationData(sbn, action, widgetId, + storedTile); + } + } + + /** + * Updates tiles with matched Uris, dependent on the {@code action}. + * + * <p>If the notification was added, adds the notification based on the contact Uri within + * {@code sbn}. + * <p>If the notification was removed, removes the notification based on the in-memory map of + * widgets previously updated by Uri (since the contact Uri is stripped from the {@code sbn}). + */ + private void updateTilesByUri(PeopleTileKey key, StatusBarNotification sbn, + PeopleSpaceUtils.NotificationAction action) { + if (action.equals(PeopleSpaceUtils.NotificationAction.POSTED)) { + Set<String> widgetIdsUpdatedByUri = supplementTilesByUri(sbn, action, key); + if (widgetIdsUpdatedByUri != null && !widgetIdsUpdatedByUri.isEmpty()) { + if (DEBUG) Log.d(TAG, "Added due to uri: " + widgetIdsUpdatedByUri); + mNotificationKeyToWidgetIdsMatchedByUri.put(sbn.getKey(), widgetIdsUpdatedByUri); + } + } else { + // Remove the notification on any widgets where the notification was added + // purely based on the Uri. + Set<String> widgetsPreviouslyUpdatedByUri = + mNotificationKeyToWidgetIdsMatchedByUri.remove(sbn.getKey()); + if (widgetsPreviouslyUpdatedByUri != null && !widgetsPreviouslyUpdatedByUri.isEmpty()) { + if (DEBUG) Log.d(TAG, "Remove due to uri: " + widgetsPreviouslyUpdatedByUri); + updateWidgetIdsForNotificationAction(widgetsPreviouslyUpdatedByUri, sbn, + action); + } + } + } + + /** + * Retrieves from storage any tiles with the same contact Uri as linked via the {@code sbn}. + * Supplements the tiles with the notification content only if they still have {@link + * android.Manifest.permission.READ_CONTACTS} permission. + */ + @Nullable + private Set<String> supplementTilesByUri(StatusBarNotification sbn, + PeopleSpaceUtils.NotificationAction notificationAction, PeopleTileKey key) { + if (!shouldMatchNotificationByUri(sbn)) { + if (DEBUG) Log.d(TAG, "Should not supplement conversation"); + return null; + } + + // Try to get the Contact Uri from the Missed Call notification directly. + String contactUri = getContactUri(sbn); + if (contactUri == null) { + if (DEBUG) Log.d(TAG, "No contact uri"); + return null; + } + + // Supplement any tiles with the same Uri. + Set<String> storedWidgetIdsByUri = + new HashSet<>(mSharedPrefs.getStringSet(contactUri, new HashSet<>())); + if (storedWidgetIdsByUri.isEmpty()) { + if (DEBUG) Log.d(TAG, "No tiles for contact"); + return null; + } + + if (mPackageManager.checkPermission(READ_CONTACTS, + sbn.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + if (DEBUG) Log.d(TAG, "Notifying app missing permissions"); + return null; + } + + Set<String> widgetIdsUpdatedByUri = new HashSet<>(); + for (String widgetIdString : storedWidgetIdsByUri) { + int widgetId = Integer.parseInt(widgetIdString); + PeopleSpaceTile storedTile = getTileForExistingWidget(widgetId); + // Don't update a widget already updated. + if (key.equals(new PeopleTileKey(storedTile))) { + continue; + } + if (storedTile == null || mPackageManager.checkPermission(READ_CONTACTS, + storedTile.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + if (DEBUG) Log.d(TAG, "Cannot supplement tile: " + storedTile.getUserName()); + continue; + } + if (DEBUG) Log.d(TAG, "Adding notification by uri: " + sbn.getKey()); + updateStorageAndViewWithNotificationData(sbn, notificationAction, + widgetId, storedTile); + widgetIdsUpdatedByUri.add(String.valueOf(widgetId)); + } + return widgetIdsUpdatedByUri; + } + + /** + * Try to retrieve a valid Uri via {@code sbn}, falling back to the {@code + * contactUriFromShortcut} if valid. + */ + @Nullable + private String getContactUri(StatusBarNotification sbn) { + // First, try to get a Uri from the Person directly set on the Notification. + ArrayList<Person> people = sbn.getNotification().extras.getParcelableArrayList( + EXTRA_PEOPLE_LIST); + if (people != null && people.get(0) != null) { + String contactUri = people.get(0).getUri(); + if (contactUri != null && !contactUri.isEmpty()) { + return contactUri; + } + } + + // Then, try to get a Uri from the Person set on the Notification message. + List<Notification.MessagingStyle.Message> messages = + getMessagingStyleMessages(sbn.getNotification()); + if (messages != null && !messages.isEmpty()) { + Notification.MessagingStyle.Message message = messages.get(0); + Person sender = message.getSenderPerson(); + if (sender != null && sender.getUri() != null && !sender.getUri().isEmpty()) { + return sender.getUri(); + } + } + + return null; + } + + /** + * Returns whether a notification should be matched to other Tiles by Uri. + * + * <p>Currently only matches missed calls. + */ + private boolean shouldMatchNotificationByUri(StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + if (notification == null) { + if (DEBUG) Log.d(TAG, "Notification is null"); + return false; + } + if (!Objects.equals(notification.category, CATEGORY_MISSED_CALL)) { + if (DEBUG) Log.d(TAG, "Not missed call"); + return false; + } + return true; + } + /** * Update the tiles associated with the incoming conversation update. */ @@ -309,16 +494,11 @@ public class PeopleSpaceWidgetManager { private void updateStorageAndViewWithNotificationData( StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction, - int appWidgetId) { - PeopleSpaceTile storedTile = getTileForExistingWidget(appWidgetId); - if (storedTile == null) { - if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); - return; - } + int appWidgetId, PeopleSpaceTile storedTile) { if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); storedTile = augmentTileFromNotification(mContext, storedTile, sbn); - } else { + } else if (storedTile.getNotificationKey().equals(sbn.getKey())) { if (DEBUG) { Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); } @@ -328,6 +508,7 @@ public class PeopleSpaceWidgetManager { .setNotificationKey(null) .setNotificationContent(null) .setNotificationDataUri(null) + .setMessagesCount(0) // Reset missed calls category. .setNotificationCategory(null) .build(); @@ -439,7 +620,8 @@ public class PeopleSpaceWidgetManager { synchronized (mLock) { if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getId()); PeopleTileKey key = new PeopleTileKey(tile); - PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId); + PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId, + tile.getContactUri()); } try { if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId()); @@ -495,6 +677,7 @@ public class PeopleSpaceWidgetManager { // Retrieve storage needed for widget deletion. PeopleTileKey key; Set<String> storedWidgetIdsForKey; + String contactUriString; synchronized (mLock) { SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId), Context.MODE_PRIVATE); @@ -508,9 +691,11 @@ public class PeopleSpaceWidgetManager { } storedWidgetIdsForKey = new HashSet<>( mSharedPrefs.getStringSet(key.toString(), new HashSet<>())); + contactUriString = mSharedPrefs.getString(String.valueOf(widgetId), null); } synchronized (mLock) { - PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId); + PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId, + contactUriString); } // If another tile with the conversation is still stored, we need to keep the listener. if (DEBUG) Log.d(TAG, "Stored widget IDs: " + storedWidgetIdsForKey.toString()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 9967936ac1bd..d54d3f21f0b6 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; @@ -139,8 +140,12 @@ public class QSDetail extends LinearLayout { } private void updateDetailText() { - mDetailDoneButton.setText(R.string.quick_settings_done); - mDetailSettingsButton.setText(R.string.quick_settings_more_settings); + int resId = mDetailAdapter != null ? mDetailAdapter.getDoneText() : Resources.ID_NULL; + mDetailDoneButton.setText( + (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_done); + 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 +223,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/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt index 54e8a2be0d2a..cc5a771f78c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt @@ -118,7 +118,19 @@ enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum QS_USER_DETAIL_CLOSE(426), @UiEvent(doc = "User switcher QS detail panel more settings pressed") - QS_USER_MORE_SETTINGS(427); + QS_USER_MORE_SETTINGS(427), + + @UiEvent(doc = "The user has added a guest in the detail panel") + QS_USER_GUEST_ADD(754), + + @UiEvent(doc = "The user selected 'Start over' after switching to the existing Guest user") + QS_USER_GUEST_WIPE(755), + + @UiEvent(doc = "The user selected 'Yes, continue' after switching to the existing Guest user") + QS_USER_GUEST_CONTINUE(756), + + @UiEvent(doc = "The user has pressed 'Remove guest' in the detail panel") + QS_USER_GUEST_REMOVE(757); override fun getId() = _id }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 5e13fd345b81..2c5dbcd51f21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -283,31 +283,17 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen return mContext.getString(R.string.quick_settings_disclosure_named_vpn, vpnName); } + if (hasWorkProfile && isNetworkLoggingEnabled) { + return mContext.getString( + R.string.quick_settings_disclosure_managed_profile_network_activity); + } if (isProfileOwnerOfOrganizationOwnedDevice) { - if (isNetworkLoggingEnabled) { - if (organizationName == null) { - return mContext.getString( - R.string.quick_settings_disclosure_management_monitoring); - } - return mContext.getString( - R.string.quick_settings_disclosure_named_management_monitoring, - organizationName); - } if (workProfileOrganizationName == null) { return mContext.getString(R.string.quick_settings_disclosure_management); } return mContext.getString(R.string.quick_settings_disclosure_named_management, workProfileOrganizationName); } - if (hasWorkProfile && isNetworkLoggingEnabled) { - if (workProfileOrganizationName == null) { - return mContext.getString( - R.string.quick_settings_disclosure_managed_profile_monitoring); - } - return mContext.getString( - R.string.quick_settings_disclosure_named_managed_profile_monitoring, - workProfileOrganizationName); - } return null; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 3e3451e64b49..0a9c12fa4a13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -474,22 +474,21 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D if (tiles.contains("internet") || tiles.contains("wifi") || tiles.contains("cell")) { if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { if (!tiles.contains("internet")) { - tiles.add("internet"); - } - if (tiles.contains("wifi")) { + if (tiles.contains("wifi")) { + // Replace the WiFi with Internet, and remove the Cell + tiles.set(tiles.indexOf("wifi"), "internet"); + tiles.remove("cell"); + } else if (tiles.contains("cell")) { + // Replace the Cell with Internet + tiles.set(tiles.indexOf("cell"), "internet"); + } + } else { tiles.remove("wifi"); - } - if (tiles.contains("cell")) { tiles.remove("cell"); } } else { if (tiles.contains("internet")) { - tiles.remove("internet"); - } - if (!tiles.contains("wifi")) { - tiles.add("wifi"); - } - if (!tiles.contains("cell")) { + tiles.set(tiles.indexOf("internet"), "wifi"); tiles.add("cell"); } } @@ -513,6 +512,14 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) { tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); } + // TODO(b/174753536): Change the config file directly. + // Filter out unused tiles from the default QS config. + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + tiles.remove("cell"); + tiles.remove("wifi"); + } else { + tiles.remove("internet"); + } return tiles; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java index 994da9a174df..8aa2d09459e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java @@ -16,6 +16,7 @@ package com.android.systemui.qs; +import android.app.ActivityManager; import android.database.ContentObserver; import android.os.Handler; @@ -24,27 +25,36 @@ import com.android.systemui.util.settings.SecureSettings; /** Helper for managing a secure setting. **/ public abstract class SecureSetting extends ContentObserver implements Listenable { - private static final int DEFAULT = 0; - - private SecureSettings mSecureSettings; + private final SecureSettings mSecureSettings; private final String mSettingName; + private final int mDefaultValue; private boolean mListening; private int mUserId; - private int mObservedValue = DEFAULT; + private int mObservedValue; protected abstract void handleValueChanged(int value, boolean observedChange); public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName, int userId) { + this(secureSettings, handler, settingName, userId, 0); + } + + public SecureSetting(SecureSettings secureSetting, Handler handler, String settingName) { + this(secureSetting, handler, settingName, ActivityManager.getCurrentUser()); + } + + public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName, + int userId, int defaultValue) { super(handler); mSecureSettings = secureSettings; mSettingName = settingName; + mObservedValue = mDefaultValue = defaultValue; mUserId = userId; } public int getValue() { - return mSecureSettings.getIntForUser(mSettingName, DEFAULT, mUserId); + return mSecureSettings.getIntForUser(mSettingName, mDefaultValue, mUserId); } public void setValue(int value) { @@ -61,7 +71,7 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl mSecureSettings.getUriFor(mSettingName), false, this, mUserId); } else { mSecureSettings.unregisterContentObserver(this); - mObservedValue = DEFAULT; + mObservedValue = mDefaultValue; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java index 0dc0b30748aa..5afe1c87121d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java @@ -34,7 +34,7 @@ import android.widget.TextView; import androidx.annotation.VisibleForTesting; -import com.android.keyguard.CarrierTextController; +import com.android.keyguard.CarrierTextManager; import com.android.settingslib.AccessibilityContentDescriptions; import com.android.settingslib.mobile.TelephonyIcons; import com.android.systemui.R; @@ -59,7 +59,7 @@ public class QSCarrierGroupController { private final ActivityStarter mActivityStarter; private final Handler mBgHandler; private final NetworkController mNetworkController; - private final CarrierTextController mCarrierTextController; + private final CarrierTextManager mCarrierTextManager; private final TextView mNoSimTextView; private final H mMainHandler; private final Callback mCallback; @@ -149,7 +149,7 @@ public class QSCarrierGroupController { } }; - private static class Callback implements CarrierTextController.CarrierTextCallback { + private static class Callback implements CarrierTextManager.CarrierTextCallback { private H mHandler; Callback(H handler) { @@ -157,7 +157,7 @@ public class QSCarrierGroupController { } @Override - public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) { mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); } } @@ -165,7 +165,7 @@ public class QSCarrierGroupController { private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter, @Background Handler bgHandler, @Main Looper mainLooper, NetworkController networkController, - CarrierTextController.Builder carrierTextControllerBuilder, Context context) { + CarrierTextManager.Builder carrierTextManagerBuilder, Context context) { if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { mProviderModel = true; } else { @@ -174,7 +174,7 @@ public class QSCarrierGroupController { mActivityStarter = activityStarter; mBgHandler = bgHandler; mNetworkController = networkController; - mCarrierTextController = carrierTextControllerBuilder + mCarrierTextManager = carrierTextManagerBuilder .setShowAirplaneMode(false) .setShowMissingSim(false) .build(); @@ -192,7 +192,6 @@ public class QSCarrierGroupController { mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState); mCallback = new Callback(mMainHandler); - mCarrierGroups[0] = view.getCarrier1View(); mCarrierGroups[1] = view.getCarrier2View(); mCarrierGroups[2] = view.getCarrier3View(); @@ -243,10 +242,10 @@ public class QSCarrierGroupController { if (mNetworkController.hasVoiceCallingFeature()) { mNetworkController.addCallback(mSignalCallback); } - mCarrierTextController.setListening(mCallback); + mCarrierTextManager.setListening(mCallback); } else { mNetworkController.removeCallback(mSignalCallback); - mCarrierTextController.setListening(null); + mCarrierTextManager.setListening(null); } } @@ -273,7 +272,7 @@ public class QSCarrierGroupController { } @MainThread - private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) { + private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) { if (!mMainHandler.getLooper().isCurrentThread()) { mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget(); return; @@ -327,13 +326,13 @@ public class QSCarrierGroupController { } private static class H extends Handler { - private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo; + private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo; private Runnable mUpdateState; static final int MSG_UPDATE_CARRIER_INFO = 0; static final int MSG_UPDATE_STATE = 1; H(Looper looper, - Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo, + Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo, Runnable updateState) { super(looper); mUpdateCarrierInfo = updateCarrierInfo; @@ -345,7 +344,7 @@ public class QSCarrierGroupController { switch (msg.what) { case MSG_UPDATE_CARRIER_INFO: mUpdateCarrierInfo.accept( - (CarrierTextController.CarrierTextCallbackInfo) msg.obj); + (CarrierTextManager.CarrierTextCallbackInfo) msg.obj); break; case MSG_UPDATE_STATE: mUpdateState.run(); @@ -362,13 +361,13 @@ public class QSCarrierGroupController { private final Handler mHandler; private final Looper mLooper; private final NetworkController mNetworkController; - private final CarrierTextController.Builder mCarrierTextControllerBuilder; + private final CarrierTextManager.Builder mCarrierTextControllerBuilder; private final Context mContext; @Inject public Builder(ActivityStarter activityStarter, @Background Handler handler, @Main Looper looper, NetworkController networkController, - CarrierTextController.Builder carrierTextControllerBuilder, Context context) { + CarrierTextManager.Builder carrierTextControllerBuilder, Context context) { mActivityStarter = activityStarter; mHandler = handler; mLooper = looper; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 32b41ec0ab66..d72f8e9ca1c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -115,34 +115,13 @@ public class TileQueryHelper { final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); // TODO(b/174753536): Move it into the config file. - // Only do the below hacking when at least one of the below tiles exist - // --InternetTile - // --WiFiTile - // --CellularTIle - if (possibleTiles.contains("internet") || possibleTiles.contains("wifi") - || possibleTiles.contains("cell")) { - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { - if (!possibleTiles.contains("internet")) { - possibleTiles.add("internet"); - } - if (possibleTiles.contains("wifi")) { - possibleTiles.remove("wifi"); - } - if (possibleTiles.contains("cell")) { - possibleTiles.remove("cell"); - } - } else { - if (possibleTiles.contains("internet")) { - possibleTiles.remove("internet"); - } - if (!possibleTiles.contains("wifi")) { - possibleTiles.add("wifi"); - } - if (!possibleTiles.contains("cell")) { - possibleTiles.add("cell"); - } - } + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + possibleTiles.remove("cell"); + possibleTiles.remove("wifi"); + } else { + possibleTiles.remove("internet"); } + for (String spec : possibleTiles) { // Only add current and stock tiles that can be created from QSFactoryImpl. // Do not include CustomTile. Those will be created by `addPackageTiles`. diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 01afacfad385..04d199645b24 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -267,8 +267,14 @@ public class LongScreenshotActivity extends Activity { @Override protected void onPause() { - Log.d(TAG, "onPause finishing=" + isFinishing()); + Log.d(TAG, "onPause"); super.onPause(); + } + + @Override + protected void onStop() { + Log.d(TAG, "onStop finishing=" + isFinishing()); + super.onStop(); if (isFinishing()) { if (mScrollCaptureResponse != null) { mScrollCaptureResponse.close(); @@ -297,12 +303,6 @@ public class LongScreenshotActivity extends Activity { } @Override - protected void onStop() { - Log.d(TAG, "onStop"); - super.onStop(); - } - - @Override protected void onDestroy() { Log.d(TAG, "onDestroy"); super.onDestroy(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index c1ae29230e61..badffce3259a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -25,7 +25,6 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; -import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; import static com.android.systemui.screenshot.LogConfig.logTag; @@ -39,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; @@ -56,7 +54,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.provider.DeviceConfig; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; @@ -76,9 +73,9 @@ 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.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; @@ -86,7 +83,6 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; -import com.android.systemui.util.DeviceConfigProxy; import com.google.common.util.concurrent.ListenableFuture; @@ -193,7 +189,6 @@ public class ScreenshotController { private final AccessibilityManager mAccessibilityManager; private final MediaActionSound mCameraSound; private final ScrollCaptureClient mScrollCaptureClient; - private final DeviceConfigProxy mConfigProxy; private final PhoneWindow mWindow; private final DisplayManager mDisplayManager; @@ -237,7 +232,6 @@ public class ScreenshotController { ScreenshotNotificationsController screenshotNotificationsController, ScrollCaptureClient scrollCaptureClient, UiEventLogger uiEventLogger, - DeviceConfigProxy configProxy, ImageExporter imageExporter, @Main Executor mainExecutor) { mScreenshotSmartActions = screenshotSmartActions; @@ -254,7 +248,6 @@ public class ScreenshotController { mWindowManager = mContext.getSystemService(WindowManager.class); mAccessibilityManager = AccessibilityManager.getInstance(mContext); - mConfigProxy = configProxy; // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( @@ -525,22 +518,17 @@ public class ScreenshotController { // The window is focusable by default setWindowFocusable(true); - if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, true)) { - View decorView = mWindow.getDecorView(); - - // Wait until this window is attached to request because it is - // the reference used to locate the target window (below). - withWindowAttached(() -> { - mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken()); - if (mLastScrollCaptureRequest != null) { - mLastScrollCaptureRequest.cancel(true); - } - mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY); - mLastScrollCaptureRequest.addListener(() -> - onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor); - }); - } + // Wait until this window is attached to request because it is + // the reference used to locate the target window (below). + withWindowAttached(() -> { + mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken()); + if (mLastScrollCaptureRequest != null) { + mLastScrollCaptureRequest.cancel(true); + } + mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY); + mLastScrollCaptureRequest.addListener(() -> + onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor); + }); attachWindow(); mScreenshotView.getViewTreeObserver().addOnPreDrawListener( diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index fea521f15b84..bdb392608455 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -241,15 +241,7 @@ public class BrightnessController implements ToggleSlider.Listener { public void run() { final float valFloat; final boolean inVrMode = mIsVrModeEnabled; - if (inVrMode) { - valFloat = Settings.System.getFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mDefaultBacklightForVr, - UserHandle.USER_CURRENT); - } else { - valFloat = Settings.System.getFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBacklight, - UserHandle.USER_CURRENT); - } + valFloat = mDisplayManager.getBrightness(mDisplayId); // Value is passed as intbits, since this is what the message takes. final int valueAsIntBits = Float.floatToIntBits(valFloat); mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits, @@ -364,14 +356,12 @@ public class BrightnessController implements ToggleSlider.Listener { metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR; minBacklight = mMinimumBacklightForVr; maxBacklight = mMaximumBacklightForVr; - settingToChange = Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT; } else { metric = mAutomatic ? MetricsEvent.ACTION_BRIGHTNESS_AUTO : MetricsEvent.ACTION_BRIGHTNESS; minBacklight = PowerManager.BRIGHTNESS_MIN; maxBacklight = PowerManager.BRIGHTNESS_MAX; - settingToChange = Settings.System.SCREEN_BRIGHTNESS_FLOAT; } final float valFloat = MathUtils.min(convertGammaToLinearFloat(value, minBacklight, maxBacklight), @@ -386,8 +376,7 @@ public class BrightnessController implements ToggleSlider.Listener { if (!tracking) { AsyncTask.execute(new Runnable() { public void run() { - Settings.System.putFloatForUser(mContext.getContentResolver(), - settingToChange, valFloat, UserHandle.USER_CURRENT); + mDisplayManager.setBrightness(mDisplayId, valFloat); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index ead6d32f3a7f..e27c1a21b8cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -189,7 +189,7 @@ class NotificationShadeDepthController @Inject constructor( blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur) val zoomOut = blurUtils.ratioOfBlurRadius(blur) try { - if (root.isAttachedToWindow) { + if (root.isAttachedToWindow && root.windowToken != null) { wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut) } else { Log.i(TAG, "Won't set zoom. Window not attached $root") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt index 6f80317e8f56..05af08e0287a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt @@ -25,10 +25,8 @@ import android.graphics.Paint import android.graphics.PointF import android.util.AttributeSet import android.view.View -import kotlin.math.max -private const val RIPPLE_ANIMATION_DURATION: Long = 1500 -private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f +private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f /** * Expanding ripple effect that shows when charging begins. @@ -39,17 +37,18 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context private val defaultColor: Int = 0xffffffff.toInt() private val ripplePaint = Paint() + var radius: Float = 0.0f + set(value) { rippleShader.radius = value } + var origin: PointF = PointF() + set(value) { rippleShader.origin = value } + var duration: Long = 1500 + init { rippleShader.color = defaultColor rippleShader.progress = 0f rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH ripplePaint.shader = rippleShader - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - rippleShader.origin = PointF(measuredWidth / 2f, measuredHeight.toFloat()) - rippleShader.radius = max(measuredWidth, measuredHeight).toFloat() - super.onLayout(changed, left, top, right, bottom) + visibility = View.GONE } fun startRipple() { @@ -57,7 +56,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context return // Ignore if ripple effect is already playing } val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = RIPPLE_ANIMATION_DURATION + animator.duration = duration animator.addUpdateListener { animator -> val now = animator.currentPlayTime val phase = now / 30000f diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt index 5547c1e78cc4..d400205af50b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt @@ -53,7 +53,7 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { float s = 0.0; for (float i = 0; i < 4; i += 1) { float l = i * 0.25; - float h = l + 0.025; + float h = l + 0.005; float o = abs(sin(0.1 * PI * (t + i))); s += threshold(n + o, l, h); } @@ -97,7 +97,7 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { float fadeRipple = min(fadeIn, 1.-fadeOutRipple); float rippleAlpha = softRing(p, in_origin, radius, 0.5) * fadeRipple * in_color.a; - vec4 ripple = in_color * max(circle, rippleAlpha) * 0.4; + vec4 ripple = in_color * max(circle, rippleAlpha) * 0.3; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); }""" private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt index b567ad4ccfc2..2900462c6924 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.charging import android.content.Context import android.content.res.Configuration +import android.graphics.PointF import android.util.DisplayMetrics import android.view.View import android.view.ViewGroupOverlay @@ -31,6 +32,7 @@ import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import java.io.PrintWriter +import java.lang.Integer.max import javax.inject.Inject /*** @@ -46,7 +48,7 @@ class WiredChargingRippleController @Inject constructor( private val context: Context, private val keyguardStateController: KeyguardStateController ) { - private var pluggedIn: Boolean? = null + private var charging: Boolean? = null private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled @VisibleForTesting var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null) @@ -55,16 +57,18 @@ class WiredChargingRippleController @Inject constructor( val batteryStateChangeCallback = object : BatteryController.BatteryStateChangeCallback { override fun onBatteryLevelChanged( level: Int, - nowPluggedIn: Boolean, - charging: Boolean + pluggedIn: Boolean, + nowCharging: Boolean ) { - if (!rippleEnabled) { + // Suppresses the ripple when it's disabled, or when the state change comes + // from wireless charging. + if (!rippleEnabled || batteryController.isWirelessCharging) { return } - val wasPluggedIn = pluggedIn - pluggedIn = nowPluggedIn + val wasCharging = charging + charging = nowCharging // Only triggers when the keyguard is active and the device is just plugged in. - if (wasPluggedIn == false && nowPluggedIn && keyguardStateController.isShowing) { + if (wasCharging == false && nowCharging && keyguardStateController.isShowing) { rippleView.startRipple() } } @@ -113,10 +117,13 @@ class WiredChargingRippleController @Inject constructor( val width = displayMetrics.widthPixels val height = displayMetrics.heightPixels if (width != rippleView.width || height != rippleView.height) { - rippleView.measure( - View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)) - rippleView.layout(0, 0, width, height) + rippleView.apply { + measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)) + layout(0, 0, width, height) + origin = PointF(width / 2f, height.toFloat()) + radius = max(width, height).toFloat() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 815cfb39ea2f..a3a40144806e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -656,11 +656,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P; boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S; - if (Notification.DevFlags.shouldBackportSNotifRules(mContext.getContentResolver())) { - // When back-porting S rules, if an app targets P/Q/R then enforce the new S rule on - // that notification. If it's before P though, we still want to enforce legacy rules. - beforeS = beforeP; - } int smallHeight; View expandedView = layout.getExpandedChild(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 3728388f63b2..609ca97c1b02 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -25,6 +25,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; import android.view.ViewOutlineProvider; import com.android.systemui.R; @@ -101,6 +102,28 @@ public abstract class ExpandableOutlineView extends ExpandableView { } }; + /** + * Get the relative start padding of a view relative to this view. This recursively walks up the + * hierarchy and does the corresponding measuring. + * + * @param view the view to get the padding for. The requested view has to be a child of this + * notification. + * @return the start padding + */ + public int getRelativeStartPadding(View view) { + boolean isRtl = isLayoutRtl(); + int startPadding = 0; + while (view.getParent() instanceof ViewGroup) { + View parent = (View) view.getParent(); + startPadding += isRtl ? parent.getWidth() - view.getRight() : view.getLeft(); + view = parent; + if (view == this) { + return startPadding; + } + } + return startPadding; + } + protected Path getClipPath(boolean ignoreTranslation) { int left; int top; @@ -109,15 +132,17 @@ public abstract class ExpandableOutlineView extends ExpandableView { int height; float topRoundness = mAlwaysRoundBothCorners ? mOutlineRadius : getCurrentBackgroundRadiusTop(); + if (!mCustomOutline) { - int translation = mShouldTranslateContents && !ignoreTranslation - ? (int) getTranslation() : 0; - int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f); - left = Math.max(translation, 0) - halfExtraWidth; - top = mClipTopAmount + mBackgroundTop; - right = getWidth() + halfExtraWidth + Math.min(translation, 0); + // Extend left/right clip bounds beyond the notification by the + // 1) space between the notification and edge of screen + // 2) corner radius (so we do not see any rounding as the notification goes off screen) + left = (int) (-getRelativeStartPadding(this) - mOutlineRadius); + right = (int) (((View) getParent()).getWidth() + mOutlineRadius); + // If the top is rounded we want the bottom to be at most at the top roundness, in order // to avoid the shadow changing when scrolling up. + top = mClipTopAmount + mBackgroundTop; bottom = Math.max(mMinimumHeightForClipping, Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRoundness))); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 73e080423d40..1086d6761249 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -69,6 +69,8 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { private float mContentTranslation; protected boolean mLastInSection; protected boolean mFirstInSection; + private float mOutlineRadius; + private float mParentWidth; public ExpandableView(Context context, AttributeSet attrs) { super(context, attrs); @@ -79,6 +81,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { private void initDimens() { mContentShift = getResources().getDimensionPixelSize( R.dimen.shelf_transform_content_shift); + mOutlineRadius = getResources().getDimensionPixelSize(R.dimen.notification_corner_radius); } @Override @@ -150,6 +153,9 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); + if (getParent() != null) { + mParentWidth = ((View) getParent()).getWidth(); + } updateClipping(); } @@ -436,11 +442,15 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { protected void updateClipping() { if (mClipToActualHeight && shouldClipToActualHeight()) { - int top = getClipTopAmount(); - int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding() + final int top = getClipTopAmount(); + final int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding() - mClipBottomAmount, top), mMinimumHeightForClipping); - int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f); - mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom); + // Extend left/right clip bounds beyond the notification by the + // 1) space between the notification and edge of screen + // 2) corner radius (so we do not see any rounding as the notification goes off screen) + final int left = (int) (-getRelativeStartPadding(this) - mOutlineRadius); + final int right = (int) (mParentWidth + mOutlineRadius); + mClipRect.set(left, top, right, bottom); setClipBounds(mClipRect); } else { setClipBounds(null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java new file mode 100644 index 000000000000..377fb92ac6ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import com.android.keyguard.CarrierTextController; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; + +/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */ +public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> { + private final CarrierTextController mCarrierTextController; + + @Inject + public KeyguardStatusBarViewController( + KeyguardStatusBarView view, CarrierTextController carrierTextController) { + super(view); + mCarrierTextController = carrierTextController; + } + + @Override + protected void onInit() { + super.onInit(); + mCarrierTextController.init(); + } + + @Override + protected void onViewAttached() { + } + + @Override + protected void onViewDetached() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 415cfff3713b..d3da0bce0a15 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -83,6 +83,7 @@ import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; @@ -298,6 +299,7 @@ public class NotificationPanelViewController extends PanelViewController { private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory; private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory; + private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; private final QSDetailDisplayer mQSDetailDisplayer; private final FeatureFlags mFeatureFlags; private final ScrimController mScrimController; @@ -313,6 +315,7 @@ public class NotificationPanelViewController extends PanelViewController { private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; + private KeyguardStatusBarViewController mKeyguarStatusBarViewController; private ViewGroup mBigClockContainer; private QS mQs; private FrameLayout mQsFrame; @@ -565,6 +568,7 @@ public class NotificationPanelViewController extends PanelViewController { KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory, KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, + KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory, QSDetailDisplayer qsDetailDisplayer, NotificationGroupManagerLegacy groupManager, NotificationIconAreaController notificationIconAreaController, @@ -589,6 +593,7 @@ public class NotificationPanelViewController extends PanelViewController { mGroupManager = groupManager; mNotificationIconAreaController = notificationIconAreaController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; + mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory; mFeatureFlags = featureFlags; mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory; mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory; @@ -693,7 +698,9 @@ public class NotificationPanelViewController extends PanelViewController { } updateViewControllers(mView.findViewById(R.id.keyguard_status_view), - userAvatarView, keyguardUserSwitcherView); + userAvatarView, + mKeyguardStatusBar, + keyguardUserSwitcherView); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); NotificationStackScrollLayout stackScrollLayout = mView.findViewById( R.id.notification_stack_scroller); @@ -773,13 +780,21 @@ public class NotificationPanelViewController extends PanelViewController { } private void updateViewControllers(KeyguardStatusView keyguardStatusView, - UserAvatarView userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { + UserAvatarView userAvatarView, + KeyguardStatusBarView keyguardStatusBarView, + KeyguardUserSwitcherView keyguardUserSwitcherView) { // Re-associate the KeyguardStatusViewController KeyguardStatusViewComponent statusViewComponent = mKeyguardStatusViewComponentFactory.build(keyguardStatusView); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + KeyguardStatusBarViewComponent statusBarViewComponent = + mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView); + mKeyguarStatusBarViewController = + statusBarViewComponent.getKeyguardStatusBarViewController(); + mKeyguarStatusBarViewController.init(); + // Re-associate the clock container with the keyguard clock switch. KeyguardClockSwitchController keyguardClockSwitchController = statusViewComponent.getKeyguardClockSwitchController(); @@ -919,7 +934,8 @@ public class NotificationPanelViewController extends PanelViewController { showKeyguardUserSwitcher /* enabled */); mBigClockContainer.removeAllViews(); - updateViewControllers(keyguardStatusView, userAvatarView, keyguardUserSwitcherView); + updateViewControllers( + keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView); // Update keyguard bottom area index = mView.indexOfChild(mKeyguardBottomArea); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index c83b60d07aa6..d581c4b6f9b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -184,7 +184,6 @@ import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CircleReveal; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; @@ -2469,39 +2468,19 @@ public class StatusBar extends SystemUI implements DemoMode, protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel, long animationDelay) { - if (mDozing || mKeyguardManager.isKeyguardLocked()) { - // on ambient or lockscreen, hide notification panel - WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, - transmittingBatteryLevel, batteryLevel, - new WirelessChargingAnimation.Callback() { - @Override - public void onAnimationStarting() { - mNotificationShadeWindowController.setRequestTopUi(true, TAG); - CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1); - } - - @Override - public void onAnimationEnded() { - CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView()); - mNotificationShadeWindowController.setRequestTopUi(false, TAG); - } - }, mDozing).show(animationDelay); - } else { - // workspace - WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, - transmittingBatteryLevel, batteryLevel, - new WirelessChargingAnimation.Callback() { - @Override - public void onAnimationStarting() { - mNotificationShadeWindowController.setRequestTopUi(true, TAG); - } + WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, + transmittingBatteryLevel, batteryLevel, + new WirelessChargingAnimation.Callback() { + @Override + public void onAnimationStarting() { + mNotificationShadeWindowController.setRequestTopUi(true, TAG); + } - @Override - public void onAnimationEnded() { - mNotificationShadeWindowController.setRequestTopUi(false, TAG); - } - }, false).show(animationDelay); - } + @Override + public void onAnimationEnded() { + mNotificationShadeWindowController.setRequestTopUi(false, TAG); + } + }, false).show(animationDelay); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 525f2205f784..94edd1e0415a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -24,6 +24,7 @@ import android.app.KeyguardManager; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; @@ -187,7 +188,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, boolean removedByUser, int reason) { StatusBarNotificationPresenter.this.onNotificationRemoved( - entry.getKey(), entry.getSbn()); + entry.getKey(), entry.getSbn(), reason); if (removedByUser) { maybeEndAmbientPulse(); } @@ -301,13 +302,14 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mNotificationPanel.updateNotificationViews(reason); } - public void onNotificationRemoved(String key, StatusBarNotification old) { + private void onNotificationRemoved(String key, StatusBarNotification old, int reason) { if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { - if (mStatusBarStateController.getState() == StatusBarState.SHADE) { + if (mStatusBarStateController.getState() == StatusBarState.SHADE + && reason != NotificationListenerService.REASON_CLICK) { mCommandQueue.animateCollapsePanels(); } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED && !isCollapsing()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 461587760f56..fd37d89a24ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -321,6 +321,11 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie } @Override + public int getDoneText() { + return R.string.quick_settings_close_user_panel; + } + + @Override public boolean onDoneButtonClicked() { if (mNotificationPanelViewController != null) { mNotificationPanelViewController.animateCloseQs(true /* animateAway */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 8a8602142363..cfaeb0edec53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -22,7 +22,6 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; -import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -44,11 +43,11 @@ import android.os.Looper; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.telephony.CellSignalStrength; -import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.FeatureFlagUtils; @@ -74,6 +73,7 @@ import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.telephony.TelephonyListenerManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -108,6 +108,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final Context mContext; private final TelephonyManager mPhone; + private final TelephonyListenerManager mTelephonyListenerManager; private final WifiManager mWifiManager; private final ConnectivityManager mConnectivityManager; private final SubscriptionManager mSubscriptionManager; @@ -121,7 +122,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final boolean mProviderModel; private Config mConfig; - private PhoneStateListener mPhoneStateListener; + private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; // Subcontrollers. @@ -201,12 +202,14 @@ public class NetworkControllerImpl extends BroadcastReceiver BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager, TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, @Nullable WifiManager wifiManager, NetworkScoreManager networkScoreManager, AccessPointControllerImpl accessPointController, DemoModeController demoModeController) { this(context, connectivityManager, telephonyManager, + telephonyListenerManager, wifiManager, networkScoreManager, SubscriptionManager.from(context), Config.readConfig(context), bgLooper, @@ -222,7 +225,9 @@ public class NetworkControllerImpl extends BroadcastReceiver @VisibleForTesting NetworkControllerImpl(Context context, ConnectivityManager connectivityManager, - TelephonyManager telephonyManager, WifiManager wifiManager, + TelephonyManager telephonyManager, + TelephonyListenerManager telephonyListenerManager, + WifiManager wifiManager, NetworkScoreManager networkScoreManager, SubscriptionManager subManager, Config config, Looper bgLooper, CallbackHandler callbackHandler, @@ -233,6 +238,7 @@ public class NetworkControllerImpl extends BroadcastReceiver BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController) { mContext = context; + mTelephonyListenerManager = telephonyListenerManager; mConfig = config; mReceiverHandler = new Handler(bgLooper); mCallbackHandler = callbackHandler; @@ -372,23 +378,20 @@ public class NetworkControllerImpl extends BroadcastReceiver // exclusively for status bar icons. mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler); // Register the listener on our bg looper - mPhoneStateListener = new PhoneStateListener(mReceiverHandler::post) { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - // For data switching from A to B, we assume B is validated for up to 2 seconds iff: - // 1) A and B are in the same subscription group e.g. CBRS data switch. And - // 2) A was validated before the switch. - // This is to provide smooth transition for UI without showing cross during data - // switch. - if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) { - if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true."); - mForceCellularValidated = true; - mReceiverHandler.removeCallbacks(mClearForceValidated); - mReceiverHandler.postDelayed(mClearForceValidated, 2000); - } - mActiveMobileDataSubscription = subId; - doUpdateMobileControllers(); + mPhoneStateListener = subId -> { + // For data switching from A to B, we assume B is validated for up to 2 seconds iff: + // 1) A and B are in the same subscription group e.g. CBRS data switch. And + // 2) A was validated before the switch. + // This is to provide smooth transition for UI without showing cross during data + // switch. + if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) { + if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true."); + mForceCellularValidated = true; + mReceiverHandler.removeCallbacks(mClearForceValidated); + mReceiverHandler.postDelayed(mClearForceValidated, 2000); } + mActiveMobileDataSubscription = subId; + doUpdateMobileControllers(); }; mDemoModeController.addCallback(this); @@ -428,7 +431,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mSubscriptionListener = new SubListener(); } mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener); - mPhone.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener); // broadcasts IntentFilter filter = new IntentFilter(); 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 d4029e64036e..83558cbf089f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -42,8 +42,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; +import android.telephony.TelephonyCallback; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -69,6 +68,7 @@ import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.tiles.UserDetailView; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.user.CreateUserActivity; import java.io.FileDescriptor; @@ -99,12 +99,12 @@ public class UserSwitcherController implements Dumpable { protected final Context mContext; protected final UserManager mUserManager; private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>(); - private final GuestResumeSessionReceiver mGuestResumeSessionReceiver - = new GuestResumeSessionReceiver(); + private final GuestResumeSessionReceiver mGuestResumeSessionReceiver; private final KeyguardStateController mKeyguardStateController; protected final Handler mHandler; private final ActivityStarter mActivityStarter; private final BroadcastDispatcher mBroadcastDispatcher; + private final TelephonyListenerManager mTelephonyListenerManager; private final IActivityTaskManager mActivityTaskManager; private ArrayList<UserRecord> mUsers = new ArrayList<>(); @@ -127,11 +127,14 @@ public class UserSwitcherController implements Dumpable { public UserSwitcherController(Context context, KeyguardStateController keyguardStateController, @Main Handler handler, ActivityStarter activityStarter, BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger, + TelephonyListenerManager telephonyListenerManager, IActivityTaskManager activityTaskManager) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; + mTelephonyListenerManager = telephonyListenerManager; mActivityTaskManager = activityTaskManager; mUiEventLogger = uiEventLogger; + mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(mUiEventLogger); mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger); if (!UserManager.isGuestUserEphemeral()) { mGuestResumeSessionReceiver.register(mBroadcastDispatcher); @@ -388,6 +391,7 @@ public class UserSwitcherController implements Dumpable { // haven't reloaded the user list yet. return; } + mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD); id = guest.id; } else if (record.isAddUser) { showAddUserDialog(); @@ -458,18 +462,15 @@ public class UserSwitcherController implements Dumpable { } private void listenForCallState() { - final TelephonyManager tele = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - if (tele != null) { - tele.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); - } + mTelephonyListenerManager.addCallStateListener(mPhoneStateListener); } - private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + private final TelephonyCallback.CallStateListener mPhoneStateListener = + new TelephonyCallback.CallStateListener() { private int mCallState; @Override - public void onCallStateChanged(int state, String incomingNumber) { + public void onCallStateChanged(int state) { if (mCallState == state) return; if (DEBUG) Log.v(TAG, "Call state changed: " + state); mCallState = state; @@ -821,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; } @@ -891,6 +897,7 @@ public class UserSwitcherController implements Dumpable { if (which == BUTTON_NEGATIVE) { cancel(); } else { + mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE); dismiss(); exitGuest(mGuestId, mTargetId); } diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java new file mode 100644 index 000000000000..95216c559420 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.telephony; + +import android.telephony.ServiceState; +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +/** + * Class for use by {@link TelephonyListenerManager} to centralize TelephonyManager Callbacks. + * + * There are more callback interfaces defined in {@link android.telephony.TelephonyCallback} that + * are not currently covered. Add them here if they ever become necessary. + */ +class TelephonyCallback extends android.telephony.TelephonyCallback + implements ActiveDataSubscriptionIdListener, CallStateListener, ServiceStateListener { + + private final List<ActiveDataSubscriptionIdListener> mActiveDataSubscriptionIdListeners = + new ArrayList<>(); + private final List<CallStateListener> mCallStateListeners = new ArrayList<>(); + private final List<ServiceStateListener> mServiceStateListeners = new ArrayList<>(); + + @Inject + TelephonyCallback() { + } + + boolean hasAnyListeners() { + return !mActiveDataSubscriptionIdListeners.isEmpty() + || !mCallStateListeners.isEmpty() + || !mServiceStateListeners.isEmpty(); + } + + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveDataSubscriptionIdListeners.forEach(listener -> { + listener.onActiveDataSubscriptionIdChanged(subId); + }); + } + + void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mActiveDataSubscriptionIdListeners.add(listener); + } + + void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mActiveDataSubscriptionIdListeners.remove(listener); + } + + @Override + public void onCallStateChanged(int state) { + mCallStateListeners.forEach(listener -> { + listener.onCallStateChanged(state); + }); + } + + void addCallStateListener(CallStateListener listener) { + mCallStateListeners.add(listener); + } + + void removeCallStateListener(CallStateListener listener) { + mCallStateListeners.remove(listener); + } + + @Override + public void onServiceStateChanged(@NonNull ServiceState serviceState) { + mServiceStateListeners.forEach(listener -> { + listener.onServiceStateChanged(serviceState); + }); + } + + void addServiceStateListener(ServiceStateListener listener) { + mServiceStateListeners.add(listener); + } + + void removeServiceStateListener(ServiceStateListener listener) { + mServiceStateListeners.remove(listener); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java new file mode 100644 index 000000000000..3111930b2e60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.telephony; + +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; +import android.telephony.TelephonyManager; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Wrapper around {@link TelephonyManager#listen(PhoneStateListener, int)}. + * + * The TelephonyManager complains if too many places in code register a listener. This class + * encapsulates SystemUI's usage of this function, reducing it down to a single listener. + * + * See also + * {@link TelephonyManager#registerTelephonyCallback(Executor, android.telephony.TelephonyCallback)} + */ +@SysUISingleton +public class TelephonyListenerManager { + private final TelephonyManager mTelephonyManager; + private final Executor mExecutor; + private final TelephonyCallback mTelephonyCallback; + + private boolean mListening = false; + + @Inject + public TelephonyListenerManager( + TelephonyManager telephonyManager, + @Main Executor executor, + TelephonyCallback telephonyCallback) { + mTelephonyManager = telephonyManager; + mExecutor = executor; + mTelephonyCallback = telephonyCallback; + } + + /** */ + public void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mTelephonyCallback.addActiveDataSubscriptionIdListener(listener); + updateListening(); + } + + /** */ + public void removeActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) { + mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener); + updateListening(); + } + + /** */ + public void addCallStateListener(CallStateListener listener) { + mTelephonyCallback.addCallStateListener(listener); + updateListening(); + } + + /** */ + public void removeCallStateListener(CallStateListener listener) { + mTelephonyCallback.removeCallStateListener(listener); + updateListening(); + } + + /** */ + public void addServiceStateListener(ServiceStateListener listener) { + mTelephonyCallback.addServiceStateListener(listener); + updateListening(); + } + + /** */ + public void removeServiceStateListener(ServiceStateListener listener) { + mTelephonyCallback.removeServiceStateListener(listener); + updateListening(); + } + + + private void updateListening() { + if (!mListening && mTelephonyCallback.hasAnyListeners()) { + mListening = true; + mTelephonyManager.registerTelephonyCallback(mExecutor, mTelephonyCallback); + } else if (mListening && !mTelephonyCallback.hasAnyListeners()) { + mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); + mListening = false; + } + } +} 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/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index 6f2c0af5384d..c2c7dde562a2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -21,12 +21,13 @@ import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -40,10 +41,6 @@ import static org.mockito.Mockito.when; import android.content.pm.PackageManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Process; import android.provider.Settings; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -51,13 +48,14 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; import android.text.TextUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -73,8 +71,7 @@ import java.util.List; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class CarrierTextControllerTest extends SysuiTestCase { +public class CarrierTextManagerTest extends SysuiTestCase { private static final CharSequence SEPARATOR = " \u2014 "; private static final CharSequence INVALID_CARD_TEXT = "Invalid card"; @@ -95,7 +92,9 @@ public class CarrierTextControllerTest extends SysuiTestCase { @Mock private WifiManager mWifiManager; @Mock - private CarrierTextController.CarrierTextCallback mCarrierTextCallback; + private WakefulnessLifecycle mWakefulnessLifecycle; + @Mock + private CarrierTextManager.CarrierTextCallback mCarrierTextCallback; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock @@ -103,24 +102,25 @@ public class CarrierTextControllerTest extends SysuiTestCase { @Mock private TelephonyManager mTelephonyManager; @Mock + private TelephonyListenerManager mTelephonyListenerManager; + private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); + private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); + private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock); + @Mock private SubscriptionManager mSubscriptionManager; - private CarrierTextController.CarrierTextCallbackInfo mCarrierTextCallbackInfo; + private CarrierTextManager.CarrierTextCallbackInfo mCarrierTextCallbackInfo; - private CarrierTextController mCarrierTextController; - private TestableLooper mTestableLooper; + private CarrierTextManager mCarrierTextManager; private Void checkMainThread(InvocationOnMock inv) { - Looper mainLooper = Dependency.get(Dependency.MAIN_HANDLER).getLooper(); - if (!mainLooper.isCurrentThread()) { - fail("This call should be done from the main thread"); - } + assertThat(mMainExecutor.isExecuting()).isTrue(); + assertThat(mBgExecutor.isExecuting()).isFalse(); return null; } @Before public void setUp() { MockitoAnnotations.initMocks(this); - mTestableLooper = TestableLooper.get(this); mContext.addMockSystemService(WifiManager.class, mWifiManager); mContext.addMockSystemService(PackageManager.class, mPackageManager); @@ -132,9 +132,6 @@ public class CarrierTextControllerTest extends SysuiTestCase { mContext.getOrCreateTestableResources().addOverride( R.string.airplane_mode, AIRPLANE_MODE_TEXT); mDependency.injectMockDependency(WakefulnessLifecycle.class); - mDependency.injectTestDependency(Dependency.MAIN_HANDLER, - new Handler(mTestableLooper.getLooper())); - mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor); doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor) @@ -142,35 +139,30 @@ public class CarrierTextControllerTest extends SysuiTestCase { doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor) .removeCallback(any(KeyguardUpdateMonitorCallback.class)); - mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("", + mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("", new CharSequence[]{}, false, new int[]{}); when(mTelephonyManager.getSupportedModemCount()).thenReturn(3); when(mTelephonyManager.getActiveModemCount()).thenReturn(3); - mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true); + mCarrierTextManager = new CarrierTextManager.Builder( + mContext, mContext.getResources(), mWifiManager, + mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor, + mBgExecutor, mKeyguardUpdateMonitor) + .setShowAirplaneMode(true) + .setShowMissingSim(true) + .build(); + // This should not start listening on any of the real dependencies but will test that // callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread - mCarrierTextController.setListening(mCarrierTextCallback); - mTestableLooper.processAllMessages(); + mCarrierTextManager.setListening(mCarrierTextCallback); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); } @Test public void testKeyguardUpdateMonitorCalledInMainThread() throws Exception { - // This test will run on the main looper (which is not the same as the looper set as MAIN - // for CarrierTextCallback. This will fail if calls to mKeyguardUpdateMonitor are not done - // through the looper set in the set up - HandlerThread thread = new HandlerThread("testThread", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - TestableLooper testableLooper = new TestableLooper(thread.getLooper()); - Handler h = new Handler(testableLooper.getLooper()); - h.post(() -> { - mCarrierTextController.setListening(null); - mCarrierTextController.setListening(mCarrierTextCallback); - }); - testableLooper.processAllMessages(); - mTestableLooper.processAllMessages(); - thread.quitSafely(); + mCarrierTextManager.setListening(null); + mCarrierTextManager.setListening(mCarrierTextCallback); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); } @Test @@ -183,13 +175,13 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - mCarrierTextController.updateCarrierText(); + mCarrierTextManager.updateCarrierText(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText); } @@ -205,14 +197,14 @@ public class CarrierTextControllerTest extends SysuiTestCase { TelephonyManager.SIM_STATE_CARD_IO_ERROR); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - mCarrierTextController.mCallback.onSimStateChanged(3, 1, + mCarrierTextManager.mCallback.onSimStateChanged(3, 1, TelephonyManager.SIM_STATE_CARD_IO_ERROR); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals("TEST_CARRIER" + SEPARATOR + INVALID_CARD_TEXT, captor.getValue().carrierText); // There's only one subscription in the list @@ -223,8 +215,8 @@ public class CarrierTextControllerTest extends SysuiTestCase { reset(mCarrierTextCallback); when(mTelephonyManager.getActiveModemCount()).thenReturn(1); // Update carrier text. It should ignore error state of subId 3 in inactive slotId. - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals("TEST_CARRIER", captor.getValue().carrierText); } @@ -237,9 +229,9 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( TelephonyManager.SIM_STATE_CARD_IO_ERROR); // This should not produce an out of bounds error, even though there are no subscriptions - mCarrierTextController.mCallback.onSimStateChanged(0, -3, + mCarrierTextManager.mCallback.onSimStateChanged(0, -3, TelephonyManager.SIM_STATE_CARD_IO_ERROR); - mCarrierTextController.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY); + mCarrierTextManager.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY); verify(mCarrierTextCallback, never()).updateCarrierInfo(any()); } @@ -257,23 +249,23 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( TelephonyManager.SIM_STATE_CARD_IO_ERROR); // This should not produce an out of bounds error, even though there are no subscriptions - mCarrierTextController.mCallback.onSimStateChanged(0, 1, + mCarrierTextManager.mCallback.onSimStateChanged(0, 1, TelephonyManager.SIM_STATE_CARD_IO_ERROR); - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo( - any(CarrierTextController.CarrierTextCallbackInfo.class)); + any(CarrierTextManager.CarrierTextCallbackInfo.class)); } @Test public void testCallback() { reset(mCarrierTextCallback); - mCarrierTextController.postToCallback(mCarrierTextCallbackInfo); - mTestableLooper.processAllMessages(); + mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(mCarrierTextCallbackInfo, captor.getValue()); } @@ -282,11 +274,11 @@ public class CarrierTextControllerTest extends SysuiTestCase { public void testNullingCallback() { reset(mCarrierTextCallback); - mCarrierTextController.postToCallback(mCarrierTextCallbackInfo); - mCarrierTextController.setListening(null); + mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo); + mCarrierTextManager.setListening(null); // This shouldn't produce NPE - mTestableLooper.processAllMessages(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(any()); } @@ -301,15 +293,15 @@ public class CarrierTextControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); - CarrierTextController.CarrierTextCallbackInfo info = captor.getValue(); + CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue(); assertEquals(1, info.listOfCarriers.length); assertEquals(TEST_CARRIER, info.listOfCarriers[0]); assertEquals(1, info.subscriptionIds.length); @@ -326,15 +318,15 @@ public class CarrierTextControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); - CarrierTextController.CarrierTextCallbackInfo info = captor.getValue(); + CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue(); assertEquals(1, info.listOfCarriers.length); assertTrue(info.listOfCarriers[0].toString().contains(TEST_CARRIER)); assertEquals(1, info.subscriptionIds.length); @@ -346,17 +338,17 @@ public class CarrierTextControllerTest extends SysuiTestCase { List<SubscriptionInfo> list = new ArrayList<>(); list.add(TEST_SUBSCRIPTION_NULL); when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( - TelephonyManager.SIM_STATE_READY); + TelephonyManager.SIM_STATE_READY); when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText, @@ -380,12 +372,12 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(ss.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE); mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertFalse("No SIM should be available", captor.getValue().anySimReady); @@ -407,15 +399,15 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn( new ArrayList<>()); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); - CarrierTextController.CarrierTextCallbackInfo info = captor.getValue(); + CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue(); assertEquals(0, info.listOfCarriers.length); assertEquals(0, info.subscriptionIds.length); @@ -428,17 +420,17 @@ public class CarrierTextControllerTest extends SysuiTestCase { list.add(TEST_SUBSCRIPTION); list.add(TEST_SUBSCRIPTION); when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( - TelephonyManager.SIM_STATE_READY); + TelephonyManager.SIM_STATE_READY); when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER, @@ -458,12 +450,12 @@ public class CarrierTextControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER, @@ -483,12 +475,12 @@ public class CarrierTextControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER, @@ -509,12 +501,12 @@ public class CarrierTextControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); - ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor = + ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor = ArgumentCaptor.forClass( - CarrierTextController.CarrierTextCallbackInfo.class); + CarrierTextManager.CarrierTextCallbackInfo.class); - mCarrierTextController.updateCarrierText(); - mTestableLooper.processAllMessages(); + mCarrierTextManager.updateCarrierText(); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mCarrierTextCallback).updateCarrierInfo(captor.capture()); assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 0cf343c2d3c4..90f7fda69663 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -72,6 +72,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { @Mock private LatencyTracker mLatencyTracker; private final FalsingCollector mFalsingCollector = new FalsingCollectorFake(); + @Mock + private EmergencyButtonController mEmergencyButtonController; private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; @@ -87,7 +89,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { .thenReturn(mKeyguardMessageArea); mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector) { + mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector, + mEmergencyButtonController) { @Override void resetState() { } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java index 826be2ba0d83..4beec574cd2a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java @@ -54,11 +54,11 @@ import java.util.concurrent.Executor; public class KeyguardDisplayManagerTest extends SysuiTestCase { @Mock + private NavigationBarController mNavigationBarController; + @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; - @Mock private DisplayManager mDisplayManager; - @Mock private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation; @@ -76,9 +76,8 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(DisplayManager.class, mDisplayManager); - mDependency.injectMockDependency(NavigationBarController.class); - mManager = spy(new KeyguardDisplayManager(mContext, mKeyguardStatusViewComponentFactory, - mBackgroundExecutor)); + mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController, + mKeyguardStatusViewComponentFactory, mBackgroundExecutor)); doReturn(mKeyguardPresentation).when(mManager).createPresentation(any()); mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index fc93dedc4e8e..bb71bed84f43 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -52,6 +52,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { private lateinit var mLatencyTracker: LatencyTracker private var mFalsingCollector: FalsingCollector = FalsingCollectorFake() @Mock + private lateinit var mEmergencyButtonController: EmergencyButtonController + @Mock private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory @Mock @@ -75,7 +77,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { .thenReturn(mKeyguardMessageAreaController) mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, - mLatencyTracker, mFalsingCollector, mKeyguardMessageAreaControllerFactory) + mLatencyTracker, mFalsingCollector, mEmergencyButtonController, + mKeyguardMessageAreaControllerFactory) } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index 33a0dcd048ae..9597cabb2eb8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -68,6 +68,8 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { private LatencyTracker mLatencyTracker; @Mock private LiftToActivateListener mLiftToactivateListener; + @Mock + private EmergencyButtonController mEmergencyButtonController; private FalsingCollector mFalsingCollector = new FalsingCollectorFake(); @Mock private SingleTapClassifier mSingleTapClassifier; @@ -97,7 +99,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener, - mFalsingCollector) { + mEmergencyButtonController, mFalsingCollector) { @Override public void onResume(int reason) { super.onResume(reason); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 096ce0f9de27..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; @@ -94,6 +96,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private KeyguardMessageArea mKeyguardMessageArea; @Mock private ConfigurationController mConfigurationController; + @Mock + private EmergencyButtonController mEmergencyButtonController; + @Mock + private Resources mResources; + private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; private KeyguardPasswordViewController mKeyguardPasswordViewController; @@ -101,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); @@ -112,11 +124,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardPasswordViewController = new KeyguardPasswordViewController( (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor, SecurityMode.Password, mLockPatternUtils, null, - mKeyguardMessageAreaControllerFactory, null, null, null, mock(Resources.class), - null); + mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController, + null, mock(Resources.class), null); mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory( - mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, + mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, mKeyguardSecurityViewFlipperController, mConfigurationController) @@ -152,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/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index 3b7f4b839853..9296d3d5ec82 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -59,6 +59,10 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { @Mock private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory; @Mock + private EmergencyButtonController.Factory mEmergencyButtonControllerFactory; + @Mock + private EmergencyButtonController mEmergencyButtonController; + @Mock private KeyguardInputViewController mKeyguardInputViewController; @Mock private KeyguardInputView mInputView; @@ -76,9 +80,12 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { any(KeyguardSecurityCallback.class))) .thenReturn(mKeyguardInputViewController); when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController); + when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class))) + .thenReturn(mEmergencyButtonController); mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView, - mLayoutInflater, mKeyguardSecurityViewControllerFactory); + mLayoutInflater, mKeyguardSecurityViewControllerFactory, + mEmergencyButtonControllerFactory); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 52e2016d6f0e..160dae579964 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -88,6 +88,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeTracker; import org.junit.After; @@ -163,6 +164,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private AuthController mAuthController; @Mock + private TelephonyListenerManager mTelephonyListenerManager; + @Mock private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; @@ -883,7 +886,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBroadcastDispatcher, mDumpManager, mRingerModeTracker, mBackgroundExecutor, mStatusBarStateController, mLockPatternUtils, - mAuthController, mFeatureFlags); + mAuthController, mTelephonyListenerManager, mFeatureFlags); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt new file mode 100644 index 000000000000..02ba304f8c37 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.hardware.biometrics.BiometricSourceType +import android.testing.AndroidTestingRunner +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.policy.ConfigurationController +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class AuthRippleControllerTest : SysuiTestCase() { + private lateinit var controller: AuthRippleController + @Mock private lateinit var commandRegistry: CommandRegistry + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var rippleView: AuthRippleView + @Mock private lateinit var viewHost: ViewGroup + + @Before + + fun setUp() { + MockitoAnnotations.initMocks(this) + controller = AuthRippleController( + commandRegistry, configurationController, context, keyguardUpdateMonitor) + controller.rippleView = rippleView // Replace the real ripple view with a mock instance + controller.setViewHost(viewHost) + } + + @Test + fun testAddRippleView() { + val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java) + verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture()) + + // Fake attach to window + listenerCaptor.value.onViewAttachedToWindow(viewHost) + verify(viewHost).addView(rippleView) + } + + @Test + fun testTriggerRipple() { + // Fake attach to window + val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java) + verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture()) + listenerCaptor.value.onViewAttachedToWindow(viewHost) + + val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + captor.value.onBiometricAuthenticated( + 0 /* userId */, + BiometricSourceType.FACE /* type */, + false /* isStrongBiometric */) + verify(rippleView, never()).startRipple() + + captor.value.onBiometricAuthenticated( + 0 /* userId */, + BiometricSourceType.FINGERPRINT /* type */, + false /* isStrongBiometric */) + verify(rippleView).startRipple() + } + + @Test + fun testUpdateRippleColor() { + val captor = ArgumentCaptor + .forClass(ConfigurationController.ConfigurationListener::class.java) + verify(configurationController).addCallback(captor.capture()) + + reset(rippleView) + captor.value.onThemeChanged() + verify(rippleView).setColor(ArgumentMatchers.anyInt()) + + reset(rippleView) + captor.value.onUiModeChanged() + verify(rippleView).setColor(ArgumentMatchers.anyInt()) + } + + @Test + fun testForwardsSensorLocation() { + controller.setSensorLocation(5f, 5f) + verify(rippleView).setSensorLocation(5f, 5f) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 3f1a927dd3ed..d3694ddedae4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -98,6 +98,8 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock + private AuthRippleController mAuthRippleController; + @Mock private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback; private FakeExecutor mFgExecutor; @@ -148,7 +150,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor, mStatusBar, mStatusBarKeyguardViewManager, - mDumpManager); + mDumpManager, + mAuthRippleController); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); 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/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index eedf09936b4d..8add93032caa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -43,7 +43,6 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; import android.service.dreams.IDreamManager; -import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.IWindowManager; @@ -75,6 +74,7 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.settings.SecureSettings; @@ -106,7 +106,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock private LockPatternUtils mLockPatternUtils; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private ConnectivityManager mConnectivityManager; - @Mock private TelephonyManager mTelephonyManager; + @Mock private TelephonyListenerManager mTelephonyListenerManager; @Mock private ContentResolver mContentResolver; @Mock private Resources mResources; @Mock private ConfigurationController mConfigurationController; @@ -167,7 +167,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mLockPatternUtils, mBroadcastDispatcher, mConnectivityManager, - mTelephonyManager, + mTelephonyListenerManager, mContentResolver, null, mResources, 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/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index 1c7a84a36404..1f4dffa09d86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -65,6 +65,7 @@ import android.util.DisplayMetrics; import androidx.test.filters.SmallTest; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.util.ArrayUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.people.widget.PeopleTileKey; @@ -119,6 +120,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { .setNotificationKey(NOTIFICATION_KEY) .setNotificationContent(NOTIFICATION_CONTENT) .setNotificationDataUri(URI) + .setMessagesCount(1) .build(); private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext, @@ -318,7 +320,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { } @Test - public void testGetLastMessagingStyleMessageNoMessage() { + public void testGetMessagingStyleMessagesNoMessage() { Notification notification = new Notification.Builder(mContext, "test") .setContentTitle("TEST_TITLE") .setContentText("TEST_TEXT") @@ -328,22 +330,23 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { .setNotification(notification) .build(); - Notification.MessagingStyle.Message lastMessage = - PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification()); + List<Notification.MessagingStyle.Message> messages = + PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification()); - assertThat(lastMessage).isNull(); + assertThat(ArrayUtils.isEmpty(messages)).isTrue(); } @Test - public void testGetLastMessagingStyleMessage() { + public void testGetMessagingStyleMessages() { StatusBarNotification sbn = new SbnBuilder() .setNotification(mNotification1) .build(); - Notification.MessagingStyle.Message lastMessage = - PeopleSpaceUtils.getLastMessagingStyleMessage(sbn.getNotification()); + List<Notification.MessagingStyle.Message> messages = + PeopleSpaceUtils.getMessagingStyleMessages(sbn.getNotification()); - assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2); + assertThat(messages.size()).isEqualTo(3); + assertThat(messages.get(0).getText().toString()).isEqualTo(NOTIFICATION_TEXT_2); } @Test 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 39bf06050741..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, @@ -467,6 +470,9 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility()); + // 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_large)); mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, @@ -489,9 +495,86 @@ 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 + public void testCreateRemoteViewsWithNotificationTemplateTwoMessages() { + PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder() + .setNotificationDataUri(null) + .setStatuses(Arrays.asList(GAME_STATUS, + NEW_STORY_WITH_AVAILABILITY)) + .setMessagesCount(2).build(); + RemoteViews views = new PeopleTileViewHelper(mContext, + tileWithStatusAndNotification, 0, mOptions).getViews(); + View result = views.apply(mContext, null); + + TextView name = (TextView) result.findViewById(R.id.name); + assertEquals(name.getText(), NAME); + assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility()); + assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility()); + // Has availability. + assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility()); + // Has person icon. + 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 two messages, show count. + assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility()); + } + + + @Test public void testGetBackgroundTextFromMessageNoPunctuation() { String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 7090e781a316..d91625eb7fab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -17,11 +17,14 @@ package com.android.systemui.people.widget; import static android.app.Notification.CATEGORY_MISSED_CALL; +import static android.app.Notification.EXTRA_PEOPLE_LIST; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY; import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY; import static android.app.people.ConversationStatus.ACTIVITY_GAME; +import static android.content.PermissionChecker.PERMISSION_GRANTED; +import static android.content.PermissionChecker.PERMISSION_HARD_DENIED; import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING; import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID; @@ -55,6 +58,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Icon; import android.net.Uri; @@ -86,6 +90,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -106,6 +111,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private static final int SECOND_WIDGET_ID_WITH_SHORTCUT = 3; private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2; private static final int WIDGET_ID_WITH_KEY_IN_OPTIONS = 4; + private static final int WIDGET_ID_WITH_SAME_URI = 5; + private static final int WIDGET_ID_WITH_DIFFERENT_URI = 6; private static final String SHORTCUT_ID = "101"; private static final String OTHER_SHORTCUT_ID = "102"; private static final String NOTIFICATION_KEY = "0|com.android.systemui.tests|0|null|0"; @@ -123,10 +130,21 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID, "username", ICON, new Intent()) .setPackageName(TEST_PACKAGE_A) - .setUserHandle(new UserHandle(1)) + .setUserHandle(new UserHandle(0)) .setNotificationKey(NOTIFICATION_KEY + "1") .setNotificationContent(NOTIFICATION_CONTENT) .setNotificationDataUri(URI) + .setContactUri(URI) + .build(); + private static final PeopleSpaceTile PERSON_TILE_WITH_SAME_URI = + new PeopleSpaceTile + // Different shortcut ID + .Builder(OTHER_SHORTCUT_ID, "username", ICON, new Intent()) + // Different package name + .setPackageName(TEST_PACKAGE_B) + .setUserHandle(new UserHandle(0)) + // Same contact uri. + .setContactUri(URI) .build(); private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext, SHORTCUT_ID).setLongLabel("name").build(); @@ -149,6 +167,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private LauncherApps mLauncherApps; @Mock private NotificationEntryManager mNotificationEntryManager; + @Mock + private PackageManager mPackageManager; @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor; @@ -167,7 +187,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager); mManager = new PeopleSpaceWidgetManager(mContext); mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager, - mLauncherApps, mNotificationEntryManager); + mLauncherApps, mNotificationEntryManager, mPackageManager, true); mManager.attach(mListenerService); mProvider = new PeopleSpaceWidgetProvider(); mProvider.setPeopleSpaceWidgetManager(mManager); @@ -177,16 +197,10 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { mNoMan.addListener(serviceListener); clearStorage(); - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); - - Bundle options = new Bundle(); - options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE); - when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT))) - .thenReturn(options); + addTileForWidget(PERSON_TILE, WIDGET_ID_WITH_SHORTCUT); + addTileForWidget(PERSON_TILE_WITH_SAME_URI, WIDGET_ID_WITH_SAME_URI); when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT))) .thenReturn(new Bundle()); - when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn( - getConversationWithShortcutId(SHORTCUT_ID)); } @Test @@ -477,7 +491,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { addSecondWidgetForPersonTile(); PeopleSpaceUtils.removeSharedPreferencesStorageForTile( - mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT); + mContext, KEY, SECOND_WIDGET_ID_WITH_SHORTCUT, EMPTY_STRING); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(createNotification( SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false)) @@ -501,7 +515,6 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(createNotification( @@ -527,7 +540,6 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(createNotification( @@ -548,10 +560,267 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + public void testUpdateMissedCallNotificationWithContentPostedIfMatchingUriTile() + throws Exception { + int[] widgetIdsArray = + {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createNotification( + SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true)) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo( + NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), + mBundleArgumentCaptor.capture()); + Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI), + any()); + } + + @Test + public void testRemoveMissedCallNotificationWithContentPostedIfMatchingUriTile() + throws Exception { + int[] widgetIdsArray = + {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createNotification( + SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true)) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn.cloneLight(), 0); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = mBundleArgumentCaptor.getValue(); + PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(null); + assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(null); + verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(2)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), + mBundleArgumentCaptor.capture()); + Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(null); + assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(null); + verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI), + any()); + } + + @Test + public void testDoNotRemoveMissedCallIfMatchingUriTileMissingReadContactsPermissionWhenPosted() + throws Exception { + when(mPackageManager.checkPermission(any(), + eq(PERSON_TILE_WITH_SAME_URI.getPackageName()))).thenReturn( + PERMISSION_HARD_DENIED); + int[] widgetIdsArray = + {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createNotification( + SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true)) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + // We should only try to remove the notification if the Missed Call was added when posted. + NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn.cloneLight(), 0); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = mBundleArgumentCaptor.getValue(); + PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(null); + assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo(null); + verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(0)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), any()); + verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI), + any()); + } + + @Test + public void testUpdateMissedCallNotificationWithContentPostedIfMatchingUriTileFromSender() + throws Exception { + int[] widgetIdsArray = + {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + Notification notificationWithPersonOnlyInSender = + createMessagingStyleNotificationWithoutExtras( + SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ + true).build(); + StatusBarNotification sbn = new SbnBuilder() + .setNotification(notificationWithPersonOnlyInSender) + .setPkg(TEST_PACKAGE_A) + .setUid(0) + .setUser(new UserHandle(0)) + .build(); + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(sbn) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo( + NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), + mBundleArgumentCaptor.capture()); + Bundle bundleForSameUriTile = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithSameUri = bundleForSameUriTile.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithSameUri.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tileWithSameUri.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI), + any()); + } + + @Test + public void testDoNotUpdateMissedCallNotificationWithContentPostedIfNoPersonsAttached() + throws Exception { + int[] widgetIdsArray = + {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + // Notification posted without any Person attached. + Notification notificationWithoutPersonObject = + createMessagingStyleNotificationWithoutExtras( + SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ + true).setStyle(new Notification.MessagingStyle("sender") + .addMessage( + new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10, + "sender")) + ).build(); + StatusBarNotification sbn = new SbnBuilder() + .setNotification(notificationWithoutPersonObject) + .setPkg(TEST_PACKAGE_A) + .setUid(0) + .setUser(new UserHandle(0)) + .build(); + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(sbn) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo( + NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + // Do not update since notification doesn't include a Person reference. + verify(mAppWidgetManager, times(0)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), + any()); + verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI), + any()); + } + + @Test + public void testDoNotUpdateMissedCallNotificationWithContentPostedIfNotMatchingUriTile() + throws Exception { + clearStorage(); + addTileForWidget(PERSON_TILE, WIDGET_ID_WITH_SHORTCUT); + addTileForWidget(PERSON_TILE_WITH_SAME_URI.toBuilder().setContactUri( + Uri.parse("different_uri")).build(), WIDGET_ID_WITH_DIFFERENT_URI); + int[] widgetIdsArray = + {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_DIFFERENT_URI}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createNotification( + SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true)) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo( + NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + // Do not update since missing permission to read contacts. + verify(mAppWidgetManager, times(0)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_DIFFERENT_URI), + any()); + verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_DIFFERENT_URI), + any()); + } + + @Test + public void testDoNotUpdateMissedCallIfMatchingUriTileMissingReadContactsPermission() + throws Exception { + when(mPackageManager.checkPermission(any(), + eq(PERSON_TILE_WITH_SAME_URI.getPackageName()))).thenReturn( + PERMISSION_HARD_DENIED); + int[] widgetIdsArray = + {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, WIDGET_ID_WITH_SAME_URI}; + when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + + NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() + .setSbn(createNotification( + SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ true)) + .setId(1)); + mClock.advanceTime(MIN_LINGER_DURATION); + + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle bundle = requireNonNull(mBundleArgumentCaptor.getValue()); + PeopleSpaceTile tileWithMissedCallOrigin = bundle.getParcelable(OPTIONS_PEOPLE_TILE); + assertThat(tileWithMissedCallOrigin.getNotificationKey()).isEqualTo(NOTIFICATION_KEY); + assertThat(tileWithMissedCallOrigin.getNotificationContent()).isEqualTo( + NOTIFICATION_CONTENT); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + any()); + // Do not update since missing permission to read contacts. + verify(mAppWidgetManager, times(0)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SAME_URI), + any()); + verify(mAppWidgetManager, times(0)).updateAppWidget(eq(WIDGET_ID_WITH_SAME_URI), + any()); + } + + @Test public void testUpdateNotificationRemovedIfExistingTile() throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); StatusBarNotification sbn = createNotification( SHORTCUT_ID, /* isMessagingStyle = */ true, /* isMissedCall = */ false); @@ -574,7 +843,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test - public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners() { + public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners() + throws Exception { addSecondWidgetForPersonTile(); mProvider.onUpdate(mContext, mAppWidgetManager, new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT}); @@ -746,19 +1016,26 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { * Adds another widget for {@code PERSON_TILE} with widget ID: {@code * SECOND_WIDGET_ID_WITH_SHORTCUT}. */ - private void addSecondWidgetForPersonTile() { - Bundle options = new Bundle(); - options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE); - when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT))) - .thenReturn(options); + private void addSecondWidgetForPersonTile() throws Exception { // Set the same Person associated on another People Tile widget ID. - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); - setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT); + addTileForWidget(PERSON_TILE, SECOND_WIDGET_ID_WITH_SHORTCUT); int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT}; when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray); } + private void addTileForWidget(PeopleSpaceTile tile, int widgetId) throws Exception { + setStorageForTile(tile.getId(), tile.getPackageName(), widgetId, tile.getContactUri()); + Bundle options = new Bundle(); + options.putParcelable(OPTIONS_PEOPLE_TILE, tile); + when(mAppWidgetManager.getAppWidgetOptions(eq(widgetId))) + .thenReturn(options); + when(mIPeopleManager.getConversation(tile.getPackageName(), 0, tile.getId())).thenReturn( + getConversationWithShortcutId(tile.getId())); + when(mPackageManager.checkPermission(any(), eq(tile.getPackageName()))).thenReturn( + PERMISSION_GRANTED); + } + /** * Returns a single conversation associated with {@code shortcutId}. */ @@ -772,7 +1049,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private ConversationChannel getConversationWithShortcutId(String shortcutId, List<ConversationStatus> statuses) throws Exception { ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel( - "name").build(); + "name").setPerson(PERSON).build(); ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null, 0L, false, false, statuses); return convo; @@ -780,9 +1057,14 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private Notification createMessagingStyleNotification(String shortcutId, boolean isMessagingStyle, boolean isMissedCall) { + Bundle extras = new Bundle(); + ArrayList<Person> person = new ArrayList<Person>(); + person.add(PERSON); + extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, person); Notification.Builder builder = new Notification.Builder(mContext) .setContentTitle("TEST_TITLE") .setContentText("TEST_TEXT") + .setExtras(extras) .setShortcutId(shortcutId); if (isMessagingStyle) { builder.setStyle(new Notification.MessagingStyle(PERSON) @@ -797,6 +1079,26 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { return builder.build(); } + private Notification.Builder createMessagingStyleNotificationWithoutExtras(String shortcutId, + boolean isMessagingStyle, boolean isMissedCall) { + Notification.Builder builder = new Notification.Builder(mContext) + .setContentTitle("TEST_TITLE") + .setContentText("TEST_TEXT") + .setShortcutId(shortcutId); + if (isMessagingStyle) { + builder.setStyle(new Notification.MessagingStyle(PERSON) + .addMessage( + new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10, + PERSON)) + ); + } + if (isMissedCall) { + builder.setCategory(CATEGORY_MISSED_CALL); + } + return builder; + } + + private StatusBarNotification createNotification(String shortcutId, boolean isMessagingStyle, boolean isMissedCall) { Notification notification = createMessagingStyleNotification( @@ -804,6 +1106,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { return new SbnBuilder() .setNotification(notification) .setPkg(TEST_PACKAGE_A) + .setUid(0) + .setUser(new UserHandle(0)) .build(); } @@ -824,11 +1128,16 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS), Context.MODE_PRIVATE); widgetSp4.edit().clear().commit(); + SharedPreferences widgetSp5 = mContext.getSharedPreferences( + String.valueOf(WIDGET_ID_WITH_SAME_URI), + Context.MODE_PRIVATE); + widgetSp5.edit().clear().commit(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); sp.edit().clear().commit(); } - private void setStorageForTile(String shortcutId, String packageName, int widgetId) { + private void setStorageForTile(String shortcutId, String packageName, int widgetId, + Uri contactUri) { SharedPreferences widgetSp = mContext.getSharedPreferences( String.valueOf(widgetId), Context.MODE_PRIVATE); @@ -840,11 +1149,17 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); SharedPreferences.Editor editor = sp.edit(); - editor.putString(String.valueOf(widgetId), shortcutId); + editor.putString(String.valueOf(widgetId), contactUri.toString()); + String key = new PeopleTileKey(shortcutId, 0, packageName).toString(); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); storedWidgetIds.add(String.valueOf(widgetId)); editor.putStringSet(key, storedWidgetIds); + + Set<String> storedWidgetIdsByUri = new HashSet<>( + sp.getStringSet(contactUri.toString(), new HashSet<>())); + storedWidgetIdsByUri.add(String.valueOf(widgetId)); + editor.putStringSet(contactUri.toString(), storedWidgetIdsByUri); editor.apply(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt index 418fa61f3b06..6af8402d8aff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt @@ -41,6 +41,7 @@ class SecureSettingTest : SysuiTestCase() { private const val TEST_SETTING = "setting" private const val USER = 0 private const val OTHER_USER = 1 + private const val DEFAULT_VALUE = 1 private val FAIL_CALLBACK: Callback = { _, _ -> fail("Callback should not be called") } } @@ -59,7 +60,8 @@ class SecureSettingTest : SysuiTestCase() { secureSettings, Handler(testableLooper.looper), TEST_SETTING, - USER + USER, + DEFAULT_VALUE ) { override fun handleValueChanged(value: Int, observedChange: Boolean) { callback(value, observedChange) @@ -150,4 +152,14 @@ class SecureSettingTest : SysuiTestCase() { assertThat(changed).isTrue() } + + @Test + fun testDefaultValue() { + // Check default value before listening + assertThat(setting.value).isEqualTo(DEFAULT_VALUE) + + // Check default value if setting is not set + setting.isListening = true + assertThat(setting.value).isEqualTo(DEFAULT_VALUE) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java index 5a1bd5f72a02..59a5f03da530 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java @@ -33,7 +33,7 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; -import com.android.keyguard.CarrierTextController; +import com.android.keyguard.CarrierTextManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; @@ -55,7 +55,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { private QSCarrierGroupController mQSCarrierGroupController; private NetworkController.SignalCallback mSignalCallback; - private CarrierTextController.CarrierTextCallback mCallback; + private CarrierTextManager.CarrierTextCallback mCallback; @Mock private QSCarrierGroup mQSCarrierGroup; @Mock @@ -63,9 +63,9 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { @Mock private NetworkController mNetworkController; @Mock - private CarrierTextController.Builder mCarrierTextControllerBuilder; + private CarrierTextManager.Builder mCarrierTextControllerBuilder; @Mock - private CarrierTextController mCarrierTextController; + private CarrierTextManager mCarrierTextManager; private TestableLooper mTestableLooper; @Before @@ -84,11 +84,11 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { .thenReturn(mCarrierTextControllerBuilder); when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean())) .thenReturn(mCarrierTextControllerBuilder); - when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController); + when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager); doAnswer(invocation -> mCallback = invocation.getArgument(0)) - .when(mCarrierTextController) - .setListening(any(CarrierTextController.CarrierTextCallback.class)); + .when(mCarrierTextManager) + .setListening(any(CarrierTextManager.CarrierTextCallback.class)); when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext)); when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class)); @@ -114,8 +114,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0)); // listOfCarriers length 1, subscriptionIds length 1, anySims false - CarrierTextController.CarrierTextCallbackInfo - c1 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c1 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{""}, false, @@ -123,8 +123,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(c1); // listOfCarriers length 1, subscriptionIds length 1, anySims true - CarrierTextController.CarrierTextCallbackInfo - c2 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c2 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{""}, true, @@ -132,8 +132,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(c2); // listOfCarriers length 2, subscriptionIds length 2, anySims false - CarrierTextController.CarrierTextCallbackInfo - c3 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c3 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{"", ""}, false, @@ -141,8 +141,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(c3); // listOfCarriers length 2, subscriptionIds length 2, anySims true - CarrierTextController.CarrierTextCallbackInfo - c4 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c4 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{"", ""}, true, @@ -160,8 +160,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0)); // listOfCarriers length 2, subscriptionIds length 1, anySims false - CarrierTextController.CarrierTextCallbackInfo - c1 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c1 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{"", ""}, false, @@ -169,8 +169,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(c1); // listOfCarriers length 2, subscriptionIds length 1, anySims true - CarrierTextController.CarrierTextCallbackInfo - c2 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c2 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{"", ""}, true, @@ -178,8 +178,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(c2); // listOfCarriers length 1, subscriptionIds length 2, anySims false - CarrierTextController.CarrierTextCallbackInfo - c3 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c3 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{""}, false, @@ -187,8 +187,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(c3); // listOfCarriers length 1, subscriptionIds length 2, anySims true - CarrierTextController.CarrierTextCallbackInfo - c4 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c4 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{""}, true, @@ -204,8 +204,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenReturn( SubscriptionManager.INVALID_SIM_SLOT_INDEX); - CarrierTextController.CarrierTextCallbackInfo - c4 = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + c4 = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{"", ""}, true, @@ -225,8 +225,8 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { @Test public void testNoEmptyVisibleView_airplaneMode() { - CarrierTextController.CarrierTextCallbackInfo - info = new CarrierTextController.CarrierTextCallbackInfo( + CarrierTextManager.CarrierTextCallbackInfo + info = new CarrierTextManager.CarrierTextCallbackInfo( "", new CharSequence[]{""}, true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java index 410d9de9e0ac..7fe178c81ea8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.screenshot; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -57,13 +58,17 @@ public class ScrollCaptureControllerTest extends SysuiTestCase { public int availableBottom = Integer.MAX_VALUE; // If true, return an empty rect any time a partial result would have been returned. public boolean emptyInsteadOfPartial = false; + private int mPreviousTopRequested = 0; @Override public ListenableFuture<ScrollCaptureClient.CaptureResult> requestTile(int top) { + // Ensure we don't request a tile more than a tile away. + assertTrue(Math.abs(top - mPreviousTopRequested) <= getTileHeight()); + mPreviousTopRequested = top; Rect requested = new Rect(0, top, getPageWidth(), top + getTileHeight()); Rect fullContent = new Rect(0, availableTop, getPageWidth(), availableBottom); Rect captured = new Rect(requested); - captured.intersect(fullContent); + assertTrue(captured.intersect(fullContent)); if (emptyInsteadOfPartial && captured.height() != getTileHeight()) { captured = new Rect(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 0bf1ac31c9dd..e65db5ea4192 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar import android.app.WallpaperManager +import android.os.IBinder import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Choreographer @@ -64,6 +65,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var root: View @Mock private lateinit var viewRootImpl: ViewRootImpl + @Mock private lateinit var windowToken: IBinder @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation @Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation @Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation @@ -80,6 +82,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Before fun setup() { `when`(root.viewRootImpl).thenReturn(viewRootImpl) + `when`(root.windowToken).thenReturn(windowToken) `when`(root.isAttachedToWindow).thenReturn(true) `when`(statusBarStateController.state).then { statusBarState } `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer -> diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt index 3701b9127116..9ce72414d751 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt @@ -93,8 +93,8 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { captor.value.onBatteryLevelChanged( unusedBatteryLevel, - true /* plugged in */, - false /* charging */) + false /* plugged in */, + true /* charging */) verify(rippleView).startRipple() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 91f3611d548d..6a5e6e8f9872 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -66,6 +66,7 @@ import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.R; @@ -206,10 +207,16 @@ public class NotificationPanelViewTest extends SysuiTestCase { @Mock private KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock + private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; + @Mock + private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; + @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock private KeyguardStatusViewController mKeyguardStatusViewController; @Mock + private KeyguardStatusBarViewController mKeyguardStatusBarViewController; + @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private AuthController mAuthController; @@ -296,6 +303,10 @@ public class NotificationPanelViewTest extends SysuiTestCase { .thenReturn(mKeyguardClockSwitchController); when(mKeyguardStatusViewComponent.getKeyguardStatusViewController()) .thenReturn(mKeyguardStatusViewController); + when(mKeyguardStatusBarViewComponentFactory.build(any())) + .thenReturn(mKeyguardStatusBarViewComponent); + when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController()) + .thenReturn(mKeyguardStatusBarViewController); mNotificationPanelViewController = new NotificationPanelViewController(mView, mResources, @@ -314,6 +325,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { mKeyguardStatusViewComponentFactory, mKeyguardQsUserSwitchComponentFactory, mKeyguardUserSwitcherComponentFactory, + mKeyguardStatusBarViewComponentFactory, mQSDetailDisplayer, mGroupManager, mNotificationAreaController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 8f36415d60af..ef3317288e4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -76,6 +76,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import com.android.systemui.telephony.TelephonyListenerManager; import org.junit.After; import org.junit.Before; @@ -113,6 +114,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkScoreManager mMockNsm; protected SubscriptionManager mMockSm; protected TelephonyManager mMockTm; + protected TelephonyListenerManager mTelephonyListenerManager; protected BroadcastDispatcher mMockBd; protected Config mConfig; protected CallbackHandler mCallbackHandler; @@ -164,6 +166,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mDemoModeController = mock(DemoModeController.class); mMockWm = mock(WifiManager.class); mMockTm = mock(TelephonyManager.class); + mTelephonyListenerManager = mock(TelephonyListenerManager.class); mMockSm = mock(SubscriptionManager.class); mMockCm = mock(ConnectivityManager.class); mMockBd = mock(BroadcastDispatcher.class); @@ -213,6 +216,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, @@ -285,7 +289,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected NetworkControllerImpl setUpNoMobileData() { when(mMockTm.isDataCapable()).thenReturn(false); NetworkControllerImpl networkControllerNoMobile = - new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm, + new NetworkControllerImpl(mContext, mMockCm, mMockTm, mTelephonyListenerManager, + mMockWm, mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index b108dd817bde..f4ad819acf57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -106,7 +106,8 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { public void test4gDataIcon() { // Switch to showing 4g icon and re-initialize the NetworkController. mConfig.show4gForLte = true; - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java index 91e9f0622cbf..3c5cbb69eef6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java @@ -61,8 +61,9 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController); @@ -80,8 +81,9 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { when(mMockTm.getServiceState()).thenReturn(mServiceState); when(mMockSm.getCompleteActiveSubscriptionInfoList()).thenReturn(Collections.emptyList()); - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController); @@ -147,8 +149,9 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest { // Turn off mobile network support. when(mMockTm.isDataCapable()).thenReturn(false); // Create a new NetworkController as this is currently handled in constructor. - mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, - mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler, + mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, + mTelephonyListenerManager, mMockWm, mMockNsm, mMockSm, mConfig, + Looper.getMainLooper(), mCallbackHandler, mock(AccessPointControllerImpl.class), mock(DataUsageController.class), mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd, mDemoModeController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java new file mode 100644 index 000000000000..ac15903e9806 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.telephony; + +import static com.google.common.truth.Truth.assertThat; + +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TelephonyCallbackTest extends SysuiTestCase { + + private TelephonyCallback mTelephonyCallback = new TelephonyCallback(); + + @Test + public void testAddListener_ActiveDataSubscriptionIdListener() { + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.addActiveDataSubscriptionIdListener(subId -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + } + + @Test + public void testAddListener_CallStateListener() { + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + mTelephonyCallback.addCallStateListener(state -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.addCallStateListener(state -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + } + + @Test + public void testAddListener_ServiceStateListener() { + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + mTelephonyCallback.addServiceStateListener(serviceState -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.addServiceStateListener(serviceState -> {}); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + } + + @Test + public void testRemoveListener_ActiveDataSubscriptionIdListener() { + ActiveDataSubscriptionIdListener listener = subId -> {}; + mTelephonyCallback.addActiveDataSubscriptionIdListener(listener); + mTelephonyCallback.addActiveDataSubscriptionIdListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeActiveDataSubscriptionIdListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + } + + @Test + public void testRemoveListener_CallStateListener() { + CallStateListener listener = state -> {}; + mTelephonyCallback.addCallStateListener(listener); + mTelephonyCallback.addCallStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeCallStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeCallStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + } + + @Test + public void testRemoveListener_ServiceStateListener() { + ServiceStateListener listener = serviceState -> {}; + mTelephonyCallback.addServiceStateListener(listener); + mTelephonyCallback.addServiceStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeServiceStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isTrue(); + mTelephonyCallback.removeServiceStateListener(listener); + assertThat(mTelephonyCallback.hasAnyListeners()).isFalse(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java new file mode 100644 index 000000000000..0d1ac7b96f0e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyListenerManagerTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.telephony; + +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.when; + +import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import android.telephony.TelephonyCallback.CallStateListener; +import android.telephony.TelephonyCallback.ServiceStateListener; +import android.telephony.TelephonyManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TelephonyListenerManagerTest extends SysuiTestCase { + + @Mock + private TelephonyManager mTelephonyManager; + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + @Mock + private TelephonyCallback mTelephonyCallback; + + TelephonyListenerManager mTelephonyListenerManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTelephonyListenerManager = new TelephonyListenerManager( + mTelephonyManager, mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_ActiveDataSubscriptionIdListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_CallStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_ServiceStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testAddListenerRegisters_mixed() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + mTelephonyListenerManager.addCallStateListener(state -> {}); + mTelephonyListenerManager.addServiceStateListener(serviceState -> {}); + + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_ActiveDataSubscriptionIdListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + ActiveDataSubscriptionIdListener mListener = subId -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListener); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListener); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_CallStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + CallStateListener mListener = state -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addCallStateListener(mListener); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeCallStateListener(mListener); + mTelephonyListenerManager.removeCallStateListener(mListener); + mTelephonyListenerManager.removeCallStateListener(mListener); + mTelephonyListenerManager.removeCallStateListener(mListener); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_ServiceStateListener() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + ServiceStateListener mListener = serviceState -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addServiceStateListener(mListener); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeServiceStateListener(mListener); + mTelephonyListenerManager.removeServiceStateListener(mListener); + mTelephonyListenerManager.removeServiceStateListener(mListener); + mTelephonyListenerManager.removeServiceStateListener(mListener); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testRemoveListenerUnregisters_mixed() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + ActiveDataSubscriptionIdListener mListenerA = subId -> { }; + ServiceStateListener mListenerB = serviceState -> { }; + CallStateListener mListenerC = state -> { }; + + // Need to add one to actually register + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mListenerA); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + reset(mTelephonyManager); + + when(mTelephonyCallback.hasAnyListeners()).thenReturn(false); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA); + mTelephonyListenerManager.removeServiceStateListener(mListenerB); + mTelephonyListenerManager.removeCallStateListener(mListenerC); + mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mListenerA); + mTelephonyListenerManager.removeServiceStateListener(mListenerB); + mTelephonyListenerManager.removeCallStateListener(mListenerC); + verify(mTelephonyManager, times(1)) + .unregisterTelephonyCallback(mTelephonyCallback); + } + + @Test + public void testAddListener_noDoubleRegister() { + when(mTelephonyCallback.hasAnyListeners()).thenReturn(true); + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + verify(mTelephonyManager, times(1)) + .registerTelephonyCallback(mExecutor, mTelephonyCallback); + + reset(mTelephonyManager); + + // A second call to add doesn't register another listener. + mTelephonyListenerManager.addActiveDataSubscriptionIdListener(subId -> {}); + verify(mTelephonyManager, never()).registerTelephonyCallback(mExecutor, mTelephonyCallback); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java index 7c7ad5322d48..d3d30f242dcf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java @@ -27,6 +27,7 @@ public class FakeExecutor implements DelayableExecutor { private final FakeSystemClock mClock; private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>(); private boolean mIgnoreClockUpdates; + private boolean mExecuting; /** * Initializes a fake executor. @@ -56,7 +57,9 @@ public class FakeExecutor implements DelayableExecutor { */ public boolean runNextReady() { if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) { + mExecuting = true; mQueuedRunnables.poll().mRunnable.run(); + mExecuting = false; return true; } @@ -162,6 +165,10 @@ public class FakeExecutor implements DelayableExecutor { executeDelayed(command, 0); } + public boolean isExecuting() { + return mExecuting; + } + /** * Run all Executors in a loop until they all report they have no ready work to do. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java index abc283f40b1c..87206c5b1c80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java @@ -16,6 +16,8 @@ package com.android.systemui.util.concurrency; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -319,6 +321,18 @@ public class FakeExecutorTest extends SysuiTestCase { assertEquals(1, runnable.mRunCount); } + @Test + public void testIsExecuting() { + FakeSystemClock clock = new FakeSystemClock(); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + + Runnable runnable = () -> assertThat(fakeExecutor.isExecuting()).isTrue(); + + assertThat(fakeExecutor.isExecuting()).isFalse(); + fakeExecutor.execute(runnable); + assertThat(fakeExecutor.isExecuting()).isFalse(); + } + private static class RunnableImpl implements Runnable { int mRunCount; diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index 443a9bc33b90..f971a0916837 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -28,6 +28,17 @@ ]]> appears at the top of your screen when VPN is active. </string> + <!-- TV specific dialog message to warn about the risk of using a VPN application. [CHAR LIMIT=NONE] --> + <string name="warning" product="tv"> + <xliff:g id="app">%s</xliff:g> wants to set up a VPN connection + that allows it to monitor network traffic. Only accept if you trust the source. + <![CDATA[ + <br /> + <br /> + <img src="vpn_icon" /> + ]]> appears on your screen when VPN is active. + </string> + <!-- Dialog title for built-in VPN. [CHAR LIMIT=40] --> <string name="legacy_title">VPN is connected</string> <!-- Label for the name of the current VPN session. [CHAR LIMIT=20] --> diff --git a/services/Android.bp b/services/Android.bp index f6bb157312e0..311e87356d78 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -153,7 +153,7 @@ stubs_defaults { " --hide DeprecationMismatch" + " --hide HiddenTypedefConstant", visibility: ["//visibility:private"], - filter_packages: ["com.android."] + filter_packages: ["com.android."], } droidstubs { @@ -168,7 +168,7 @@ droidstubs { last_released: { api_file: ":android.api.system-server.latest", removed_api_file: ":removed.api.system-server.latest", - baseline_file: ":android-incompatibilities.api.system-server.latest" + baseline_file: ":android-incompatibilities.api.system-server.latest", }, api_lint: { enabled: true, @@ -178,18 +178,24 @@ droidstubs { }, dists: [ { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "android.txt", - tag: ".api.txt" + tag: ".api.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "removed.txt", tag: ".removed-api.txt", }, - ] + ], } java_library { @@ -223,16 +229,22 @@ droidstubs { }, dists: [ { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "android-non-updatable.txt", - tag: ".api.txt" + tag: ".api.txt", }, { - targets: ["sdk", "win_sdk"], + targets: [ + "sdk", + "win_sdk", + ], dir: "apistubs/android/system-server/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", }, - ] -}
\ No newline at end of file + ], +} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java index d2c1bc10abb0..1fdd9083b7cc 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java @@ -108,7 +108,7 @@ public class WindowMagnificationPromptController { SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION); final String message = mContext.getString(R.string.window_magnification_prompt_content); - notificationBuilder.setSmallIcon(R.drawable.ic_settings_24dp) + notificationBuilder.setSmallIcon(R.drawable.ic_accessibility_24dp) .setContentTitle(mContext.getString(R.string.window_magnification_prompt_title)) .setContentText(message) .setLargeIcon(Icon.createWithResource(mContext, @@ -118,7 +118,7 @@ public class WindowMagnificationPromptController { .setStyle(new Notification.BigTextStyle().bigText(message)) .setDeleteIntent(createPendingIntent(ACTION_DISMISS)) .setContentIntent(createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)) - .setActions(buildTurnOnAction(), buildDismissAction()); + .setActions(buildTurnOnAction()); mNotificationManager.notify(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE, notificationBuilder.build()); registerReceiverIfNeeded(); @@ -145,11 +145,6 @@ public class WindowMagnificationPromptController { createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)).build(); } - private Notification.Action buildDismissAction() { - return new Notification.Action.Builder(null, mContext.getString(R.string.dismiss_action), - createPendingIntent(ACTION_DISMISS)).build(); - } - private PendingIntent createPendingIntent(String action) { final Intent intent = new Intent(action); intent.setPackage(mContext.getPackageName()); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b160d78a0b64..483f67a91c19 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -639,6 +639,16 @@ public class CompanionDeviceManagerService extends SystemService implements Bind })); } + @Override + public boolean createAssociation(String packageName, String macAddress, int userId) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation"); + + addAssociation(new Association( + userId, macAddress, packageName, null, false, System.currentTimeMillis())); + return true; + } + private void checkCanCallNotificationApi(String callingPackage) throws RemoteException { checkCallerIsSystemOr(callingPackage); int userId = getCallingUserId(); diff --git a/services/core/Android.bp b/services/core/Android.bp index bed76f3112fb..641b38db0c07 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -222,6 +222,7 @@ filegroup { srcs: [ "java/com/android/server/ConnectivityService.java", "java/com/android/server/ConnectivityServiceInitializer.java", + "java/com/android/server/NetIdManager.java", "java/com/android/server/TestNetworkService.java", "java/com/android/server/connectivity/AutodestructReference.java", "java/com/android/server/connectivity/ConnectivityConstants.java", @@ -234,8 +235,11 @@ 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", + "java/com/android/server/connectivity/ProfileNetworkPreferences.java", "java/com/android/server/connectivity/ProxyTracker.java", "java/com/android/server/connectivity/QosCallbackAgentConnection.java", "java/com/android/server/connectivity/QosCallbackTracker.java", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index c2957780c9d7..a9eb2c110867 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1141,4 +1141,9 @@ public abstract class PackageManagerInternal { */ public abstract boolean isPackageFrozen( @NonNull String packageName, int callingUid, int userId); + + /** + * Deletes the OAT artifacts of a package. + */ + public abstract void deleteOatArtifactsOfPackage(String packageName); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 19858488a917..8bb9ce940cc3 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; @@ -145,7 +145,6 @@ import android.net.NetworkRequest; import android.net.NetworkScore; import android.net.NetworkSpecifier; import android.net.NetworkStack; -import android.net.NetworkStackClient; import android.net.NetworkState; import android.net.NetworkStateSnapshot; import android.net.NetworkTestResultParcelable; @@ -172,13 +171,14 @@ import android.net.VpnTransportInfo; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; +import android.net.networkstack.ModuleNetworkStackClient; +import android.net.networkstack.NetworkStackClientBase; import android.net.resolv.aidl.DnsHealthEventParcel; import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener; import android.net.resolv.aidl.Nat64PrefixEventParcel; import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; -import android.net.util.NetdService; import android.os.BatteryStatsManager; import android.os.Binder; import android.os.Build; @@ -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. */ @@ -1121,10 +1135,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Get a reference to the NetworkStackClient. + * Get a reference to the ModuleNetworkStackClient. */ - public NetworkStackClient getNetworkStack() { - return NetworkStackClient.getInstance(); + public NetworkStackClientBase getNetworkStack() { + return ModuleNetworkStackClient.getInstance(null); } /** @@ -1145,8 +1159,8 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * @see NetworkUtils#queryUserAccess(int, int) */ - public boolean queryUserAccess(int uid, int netId) { - return NetworkUtils.queryUserAccess(uid, netId); + public boolean queryUserAccess(int uid, Network network, ConnectivityService cs) { + return cs.queryUserAccess(uid, network); } /** @@ -1183,7 +1197,8 @@ public class ConnectivityService extends IConnectivityManager.Stub public ConnectivityService(Context context) { this(context, getDnsResolver(context), new IpConnectivityLog(), - NetdService.getInstance(), new Dependencies()); + INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), + new Dependencies()); } @VisibleForTesting @@ -1202,7 +1217,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkRanker = new NetworkRanker(); final NetworkRequest defaultInternetRequest = createDefaultRequest(); mDefaultRequest = new NetworkRequestInfo( - defaultInternetRequest, null, + Process.myUid(), defaultInternetRequest, null, new Binder(), NetworkCallback.FLAG_INCLUDE_LOCATION_INFO, null /* attributionTags */); mNetworkRequests.put(defaultInternetRequest, mDefaultRequest); @@ -1408,8 +1423,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (enable) { handleRegisterNetworkRequest(new NetworkRequestInfo( - networkRequest, null, - new Binder(), + Process.myUid(), networkRequest, null, new Binder(), NetworkCallback.FLAG_INCLUDE_LOCATION_INFO, null /* attributionTags */)); } else { @@ -1562,7 +1576,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final int requestId = nri.getActiveRequest() != null ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId; mNetworkInfoBlockingLogs.log(String.format( - "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId())); + "%s %d(%d) on netId %d", action, nri.mAsUid, requestId, net.getNetId())); } /** @@ -2077,6 +2091,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private void restrictRequestUidsForCallerAndSetRequestorInfo(NetworkCapabilities nc, int callerUid, String callerPackageName) { if (!checkSettingsPermission()) { + // There is no need to track the effective UID of the request here. If the caller lacks + // the settings permission, the effective UID is the same as the calling ID. nc.setSingleUid(callerUid); } nc.setRequestorUidAndPackageName(callerUid, callerPackageName); @@ -2904,10 +2920,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } pw.println(); - pw.println("NetworkStackClient logs:"); - pw.increaseIndent(); - NetworkStackClient.getInstance().dump(pw); - pw.decreaseIndent(); pw.println(); pw.println("Permission Monitor:"); @@ -3704,6 +3716,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager.removeNetwork(nai.network); } mNetIdManager.releaseNetId(nai.network.getNetId()); + nai.onNetworkDisconnected(); } private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) { @@ -4567,6 +4580,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; @@ -4842,6 +4867,42 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkMonitor().forceReevaluation(uid); } + // TODO: call into netd. + private boolean queryUserAccess(int uid, Network network) { + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai == null) return false; + + // Any UID can use its default network. + if (nai == getDefaultNetworkForUid(uid)) return true; + + // Privileged apps can use any network. + if (mPermissionMonitor.hasRestrictedNetworksPermission(uid)) { + return true; + } + + // An unprivileged UID can use a VPN iff the VPN applies to it. + if (nai.isVPN()) { + return nai.networkCapabilities.appliesToUid(uid); + } + + // An unprivileged UID can bypass the VPN that applies to it only if it can protect its + // sockets, i.e., if it is the owner. + final NetworkAgentInfo vpn = getVpnForUid(uid); + if (vpn != null && !vpn.networkAgentConfig.allowBypass + && uid != vpn.networkCapabilities.getOwnerUid()) { + return false; + } + + // The UID's permission must be at least sufficient for the network. Since the restricted + // permission was already checked above, that just leaves background networks. + if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) { + return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid); + } + + // Unrestricted network. Anyone gets to use it. + return true; + } + /** * Returns information about the proxy a certain network is using. If given a null network, it * it will return the proxy for the bound network for the caller app or the default proxy if @@ -4862,7 +4923,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } return getLinkPropertiesProxyInfo(activeNetwork); - } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network.getNetId())) { + } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network, this)) { // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which // caller may not have. return getLinkPropertiesProxyInfo(network); @@ -5367,6 +5428,8 @@ public class ConnectivityService extends IConnectivityManager.Stub boolean mPendingIntentSent; @Nullable final Messenger mMessenger; + + // Information about the caller that caused this object to be created. @Nullable private final IBinder mBinder; final int mPid; @@ -5374,6 +5437,13 @@ public class ConnectivityService extends IConnectivityManager.Stub final @NetworkCallback.Flag int mCallbackFlags; @Nullable final String mCallingAttributionTag; + + // Effective UID of this request. This is different from mUid when a privileged process + // files a request on behalf of another UID. This UID is used to determine blocked status, + // UID matching, and so on. mUid above is used for permission checks and to enforce the + // maximum limit of registered callbacks per UID. + final int mAsUid; + // In order to preserve the mapping of NetworkRequest-to-callback when apps register // callbacks using a returned NetworkRequest, the original NetworkRequest needs to be // maintained for keying off of. This is only a concern when the original nri @@ -5401,12 +5471,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return (null == uids) ? new ArraySet<>() : uids; } - NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi, - @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), r, pi, callingAttributionTag); + NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, + @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { + this(asUid, Collections.singletonList(r), r, pi, callingAttributionTag); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r, @NonNull final NetworkRequest requestForCallback, @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { ensureAllNetworkRequestsHaveType(r); @@ -5417,6 +5487,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = null; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); + mAsUid = asUid; mNetworkRequestCounter.incrementCountOrThrow(mUid); /** * Location sensitive data not included in pending intent. Only included in @@ -5426,14 +5497,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mCallingAttributionTag = callingAttributionTag; } - NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, + NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r, @Nullable final Messenger m, @Nullable final IBinder binder, @NetworkCallback.Flag int callbackFlags, @Nullable String callingAttributionTag) { - this(Collections.singletonList(r), r, m, binder, callbackFlags, callingAttributionTag); + this(asUid, Collections.singletonList(r), r, m, binder, callbackFlags, + callingAttributionTag); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r, @NonNull final NetworkRequest requestForCallback, @Nullable final Messenger m, @Nullable final IBinder binder, @NetworkCallback.Flag int callbackFlags, @@ -5446,6 +5518,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = binder; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); + mAsUid = asUid; mPendingIntent = null; mNetworkRequestCounter.incrementCountOrThrow(mUid); mCallbackFlags = callbackFlags; @@ -5488,18 +5561,19 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = nri.mBinder; mPid = nri.mPid; mUid = nri.mUid; + mAsUid = nri.mAsUid; mPendingIntent = nri.mPendingIntent; mNetworkRequestCounter.incrementCountOrThrow(mUid); mCallbackFlags = nri.mCallbackFlags; mCallingAttributionTag = nri.mCallingAttributionTag; } - NetworkRequestInfo(@NonNull final NetworkRequest r) { - this(Collections.singletonList(r)); + NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) { + this(asUid, Collections.singletonList(r)); } - NetworkRequestInfo(@NonNull final List<NetworkRequest> r) { - this(r, r.get(0), null /* pi */, null /* callingAttributionTag */); + NetworkRequestInfo(int asUid, @NonNull final List<NetworkRequest> r) { + this(asUid, r, r.get(0), null /* pi */, null /* callingAttributionTag */); } // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer @@ -5535,9 +5609,10 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String toString() { - return "uid/pid:" + mUid + "/" + mPid + " active request Id: " + final String asUidString = (mAsUid == mUid) ? "" : " asUid: " + mAsUid; + return "uid/pid:" + mUid + "/" + mPid + asUidString + " activeRequest: " + (mActiveRequest == null ? null : mActiveRequest.requestId) - + " callback request Id: " + + " callbackRequest: " + mNetworkRequestForCallback.requestId + " " + mRequests + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent) @@ -5638,7 +5713,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities, + public NetworkRequest requestNetwork(int asUid, NetworkCapabilities networkCapabilities, int reqTypeInt, Messenger messenger, int timeoutMs, IBinder binder, int legacyType, int callbackFlags, @NonNull String callingPackageName, @Nullable String callingAttributionTag) { @@ -5650,6 +5725,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities; final int callingUid = mDeps.getCallingUid(); + // Privileged callers can track the default network of another UID by passing in a UID. + if (asUid != Process.INVALID_UID) { + enforceSettingsPermission(); + } else { + asUid = callingUid; + } final NetworkRequest.Type reqType; try { reqType = NetworkRequest.Type.values()[reqTypeInt]; @@ -5659,10 +5740,10 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (reqType) { case TRACK_DEFAULT: // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities} - // is unused and will be replaced by ones appropriate for the caller. - // This allows callers to keep track of the default network for their app. + // is unused and will be replaced by ones appropriate for the UID (usually, the + // calling app). This allows callers to keep track of the default network. networkCapabilities = copyDefaultNetworkCapabilitiesForUid( - defaultNc, callingUid, callingPackageName); + defaultNc, asUid, callingUid, callingPackageName); enforceAccessPermission(); break; case TRACK_SYSTEM_DEFAULT: @@ -5714,7 +5795,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); final NetworkRequestInfo nri = getNriToRegister( - networkRequest, messenger, binder, callbackFlags, callingAttributionTag); + asUid, networkRequest, messenger, binder, callbackFlags, + callingAttributionTag); if (DBG) log("requestNetwork for " + nri); // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were @@ -5741,25 +5823,27 @@ public class ConnectivityService extends IConnectivityManager.Stub * requests registered to track the default request. If there is currently a per-app default * tracking the app requestor, then we need to create a version of this nri that mirrors that of * the tracking per-app default so that callbacks are sent to the app requestor appropriately. + * @param asUid the uid on behalf of which to file the request. Different from requestorUid + * when a privileged caller is tracking the default network for another uid. * @param nr the network request for the nri. * @param msgr the messenger for the nri. * @param binder the binder for the nri. * @param callingAttributionTag the calling attribution tag for the nri. * @return the nri to register. */ - private NetworkRequestInfo getNriToRegister(@NonNull final NetworkRequest nr, + private NetworkRequestInfo getNriToRegister(final int asUid, @NonNull final NetworkRequest nr, @Nullable final Messenger msgr, @Nullable final IBinder binder, @NetworkCallback.Flag int callbackFlags, @Nullable String callingAttributionTag) { final List<NetworkRequest> requests; if (NetworkRequest.Type.TRACK_DEFAULT == nr.type) { requests = copyDefaultNetworkRequestsForUid( - nr.getRequestorUid(), nr.getRequestorPackageName()); + asUid, nr.getRequestorUid(), nr.getRequestorPackageName()); } else { requests = Collections.singletonList(nr); } return new NetworkRequestInfo( - requests, nr, msgr, binder, callbackFlags, callingAttributionTag); + asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag); } private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities, @@ -5840,8 +5924,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.REQUEST); - NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, operation, callingAttributionTag); + NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation, + callingAttributionTag); if (DBG) log("pendingRequest for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT, nri)); @@ -5908,7 +5992,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, messenger, binder, callbackFlags, + new NetworkRequestInfo(callingUid, networkRequest, messenger, binder, callbackFlags, callingAttributionTag); if (VDBG) log("listenForNetwork for " + nri); @@ -5933,8 +6017,8 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); - NetworkRequestInfo nri = - new NetworkRequestInfo(networkRequest, operation, callingAttributionTag); + NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation, + callingAttributionTag); if (VDBG) log("pendingListenForNetwork for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); @@ -5989,12 +6073,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); } @@ -6033,6 +6142,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<>(); @@ -6084,33 +6197,37 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Get a copy of the network requests of the default request that is currently tracking the * given uid. + * @param asUid the uid on behalf of which to file the request. Different from requestorUid + * when a privileged caller is tracking the default network for another uid. * @param requestorUid the uid to check the default for. * @param requestorPackageName the requestor's package name. * @return a copy of the default's NetworkRequest that is tracking the given uid. */ @NonNull private List<NetworkRequest> copyDefaultNetworkRequestsForUid( - @NonNull final int requestorUid, @NonNull final String requestorPackageName) { + final int asUid, final int requestorUid, @NonNull final String requestorPackageName) { return copyNetworkRequestsForUid( - getDefaultRequestTrackingUid(requestorUid).mRequests, - requestorUid, requestorPackageName); + getDefaultRequestTrackingUid(asUid).mRequests, + asUid, requestorUid, requestorPackageName); } /** * Copy the given nri's NetworkRequest collection. * @param requestsToCopy the NetworkRequest collection to be copied. + * @param asUid the uid on behalf of which to file the request. Different from requestorUid + * when a privileged caller is tracking the default network for another uid. * @param requestorUid the uid to set on the copied collection. * @param requestorPackageName the package name to set on the copied collection. * @return the copied NetworkRequest collection. */ @NonNull private List<NetworkRequest> copyNetworkRequestsForUid( - @NonNull final List<NetworkRequest> requestsToCopy, @NonNull final int requestorUid, - @NonNull final String requestorPackageName) { + @NonNull final List<NetworkRequest> requestsToCopy, final int asUid, + final int requestorUid, @NonNull final String requestorPackageName) { final List<NetworkRequest> requests = new ArrayList<>(); for (final NetworkRequest nr : requestsToCopy) { requests.add(new NetworkRequest(copyDefaultNetworkCapabilitiesForUid( - nr.networkCapabilities, requestorUid, requestorPackageName), + nr.networkCapabilities, asUid, requestorUid, requestorPackageName), nr.legacyType, nextNetworkRequestId(), nr.type)); } return requests; @@ -6118,17 +6235,17 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull private NetworkCapabilities copyDefaultNetworkCapabilitiesForUid( - @NonNull final NetworkCapabilities netCapToCopy, @NonNull final int requestorUid, - @NonNull final String requestorPackageName) { + @NonNull final NetworkCapabilities netCapToCopy, final int asUid, + final int requestorUid, @NonNull final String requestorPackageName) { // These capabilities are for a TRACK_DEFAULT callback, so: // 1. Remove NET_CAPABILITY_VPN, because it's (currently!) the only difference between // mDefaultRequest and a per-UID default request. // TODO: stop depending on the fact that these two unrelated things happen to be the same - // 2. Always set the UIDs to mAsUid. restrictRequestUidsForCallerAndSetRequestorInfo will + // 2. Always set the UIDs to asUid. restrictRequestUidsForCallerAndSetRequestorInfo will // not do this in the case of a privileged application. final NetworkCapabilities netCap = new NetworkCapabilities(netCapToCopy); netCap.removeCapability(NET_CAPABILITY_NOT_VPN); - netCap.setSingleUid(requestorUid); + netCap.setSingleUid(asUid); restrictRequestUidsForCallerAndSetRequestorInfo( netCap, requestorUid, requestorPackageName); return netCap; @@ -6338,6 +6455,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 @@ -7945,6 +8127,7 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilitiesForNetwork(networkAgent); } networkAgent.created = true; + networkAgent.onNetworkCreated(); } if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) { @@ -8034,9 +8217,9 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean metered = nai.networkCapabilities.isMetered(); boolean blocked; - blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges); + blocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges); blocked |= NetworkPolicyManager.isUidBlocked( - mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE), metered); + mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE), metered); callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); } @@ -8064,12 +8247,12 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequestInfo nri = mNetworkRequests.get(nr); final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked; - oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges); + oldVpnBlocked = isUidBlockedByVpn(nri.mAsUid, oldBlockedUidRanges); newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges) - ? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges) + ? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges) : oldVpnBlocked; - final int blockedReasons = mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE); + final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE); oldBlocked = oldVpnBlocked || NetworkPolicyManager.isUidBlocked( blockedReasons, oldMetered); newBlocked = newVpnBlocked || NetworkPolicyManager.isUidBlocked( @@ -8104,7 +8287,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest nr = nai.requestAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); - if (nri != null && nri.mUid == uid) { + if (nri != null && nri.mAsUid == uid) { callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg); } } @@ -8869,7 +9052,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // nri is not bound to the death of callback. Instead, callback.bindToDeath() is set in // handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the // callback's binder death. - final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId); + final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId); final ConnectivityDiagnosticsCallbackInfo cbInfo = new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName); @@ -9353,7 +9536,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities)); nrs.add(createDefaultRequest()); setNetworkRequestUids(nrs, UidRange.fromIntRanges(pref.capabilities.getUids())); - final NetworkRequestInfo nri = new NetworkRequestInfo(nrs); + final NetworkRequestInfo nri = new NetworkRequestInfo(Process.myUid(), nrs); result.add(nri); } return result; @@ -9524,7 +9707,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } // Include this nri if it will be tracked by the new per-app default requests. final boolean isNriGoingToBeTracked = - getDefaultRequestTrackingUid(nri.mUid) != mDefaultRequest; + getDefaultRequestTrackingUid(nri.mAsUid) != mDefaultRequest; if (isNriGoingToBeTracked) { defaultCallbackRequests.add(nri); } @@ -9546,7 +9729,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final ArraySet<NetworkRequestInfo> callbackRequestsToRegister = new ArraySet<>(); for (final NetworkRequestInfo callbackRequest : perAppCallbackRequestsForUpdate) { final NetworkRequestInfo trackingNri = - getDefaultRequestTrackingUid(callbackRequest.mUid); + getDefaultRequestTrackingUid(callbackRequest.mAsUid); // If this nri is not being tracked, the change it back to an untracked nri. if (trackingNri == mDefaultRequest) { @@ -9556,12 +9739,12 @@ public class ConnectivityService extends IConnectivityManager.Stub continue; } - final String requestorPackageName = - callbackRequest.mRequests.get(0).getRequestorPackageName(); + final NetworkRequest request = callbackRequest.mRequests.get(0); callbackRequestsToRegister.add(new NetworkRequestInfo( callbackRequest, copyNetworkRequestsForUid( - trackingNri.mRequests, callbackRequest.mUid, requestorPackageName))); + trackingNri.mRequests, callbackRequest.mAsUid, + callbackRequest.mUid, request.getRequestorPackageName()))); } return callbackRequestsToRegister; } @@ -9665,7 +9848,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ranges.add(new UidRange(uid, uid)); } setNetworkRequestUids(requests, ranges); - return new NetworkRequestInfo(requests); + return new NetworkRequestInfo(Process.myUid(), requests); } private NetworkRequest createUnmeteredNetworkRequest() { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 7f96aff28016..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; @@ -3377,19 +3376,28 @@ class StorageManagerService extends IStorageManager.Stub } @Override - public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, int reason) { + public void notifyAppIoBlocked(String volumeUuid, int uid, int tid, + @StorageManager.AppIoBlockedReason int reason) { enforceExternalStorageService(); mStorageSessionController.notifyAppIoBlocked(volumeUuid, uid, tid, reason); } @Override - public void notifyAppIoResumed(String volumeUuid, int uid, int tid, int reason) { + public void notifyAppIoResumed(String volumeUuid, int uid, int tid, + @StorageManager.AppIoBlockedReason int reason) { enforceExternalStorageService(); mStorageSessionController.notifyAppIoResumed(volumeUuid, uid, tid, reason); } + @Override + public boolean isAppIoBlocked(String volumeUuid, int uid, int tid, + @StorageManager.AppIoBlockedReason int reason) { + return isAppIoBlocked(uid); + } + + private boolean isAppIoBlocked(int uid) { return mStorageSessionController.isAppIoBlocked(uid); } @@ -4259,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) @@ -4291,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) == @@ -4301,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; } } @@ -4326,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 { @@ -4702,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..3ea44588e2da 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)); @@ -365,11 +366,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { || events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED); } - private boolean isPhoneStatePermissionRequired(Set<Integer> events) { + private boolean isPhoneStatePermissionRequired(Set<Integer> events, int targetSdk) { return events.contains(TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED) || events.contains(TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED) || events.contains(TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED) - || events.contains(TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED); + || events.contains(TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED) + || (targetSdk <= android.os.Build.VERSION_CODES.R ? events.contains( + TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED) : false); } private boolean isPrecisePhoneStatePermissionRequired(Set<Integer> events) { @@ -881,12 +884,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(callback.asBinder()); return; } - + int callerTargetSdk = TelephonyPermissions.getTargetSdk(mContext, callingPackage); // Checks permission and throws SecurityException for disallowed operations. For pre-M // apps whose runtime permission has been revoked, we return immediately to skip sending // events to the app without crashing it. if (!checkListenerPermission(events, subId, callingPackage, callingFeatureId, - "listen")) { + "listen", callerTargetSdk)) { return; } @@ -919,6 +922,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } r.phoneId = phoneId; r.eventList = events; + r.targetSdk = callerTargetSdk; + if (DBG) { log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId); } @@ -1177,7 +1182,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)) { try { r.callback.onPhysicalChannelConfigChanged( - mPhysicalChannelConfigs); + shouldSanitizeLocationForPhysicalChannelConfig(r) + ? getLocationSanitizedConfigs(mPhysicalChannelConfigs) + : mPhysicalChannelConfigs); } catch (RemoteException ex) { remove(r.binder); } @@ -1748,6 +1755,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 +1771,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); } @@ -2402,8 +2427,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return; } + List<PhysicalChannelConfig> sanitizedConfigs = getLocationSanitizedConfigs(configs); if (VDBG) { - log("notifyPhysicalChannelConfig: subId=" + subId + " configs=" + configs); + log("notifyPhysicalChannelConfig: subId=" + subId + " configs=" + configs + + " sanitizedConfigs=" + sanitizedConfigs); } synchronized (mRecords) { @@ -2416,11 +2443,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { && idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { - log("notifyPhysicalChannelConfig: " - + "mPhysicalChannelConfigs=" - + configs + " r=" + r); + log("notifyPhysicalChannelConfig: mPhysicalChannelConfigs=" + + (shouldSanitizeLocationForPhysicalChannelConfig(r) + ? sanitizedConfigs : configs) + + " r=" + r); } - r.callback.onPhysicalChannelConfigChanged(configs); + r.callback.onPhysicalChannelConfigChanged( + shouldSanitizeLocationForPhysicalChannelConfig(r) + ? sanitizedConfigs : configs); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -2431,6 +2461,25 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + private static boolean shouldSanitizeLocationForPhysicalChannelConfig(Record record) { + // Always redact location info from PhysicalChannelConfig if the registrant is from neither + // PHONE nor SYSTEM process. There is no user case that the registrant needs the location + // info (e.g. physicalCellId). This also remove the need for the location permissions check. + return record.callerUid != Process.PHONE_UID && record.callerUid != Process.SYSTEM_UID; + } + + /** + * Return a copy of the PhysicalChannelConfig list but with location info removed. + */ + private static List<PhysicalChannelConfig> getLocationSanitizedConfigs( + List<PhysicalChannelConfig> configs) { + List<PhysicalChannelConfig> sanitizedConfigs = new ArrayList<>(configs.size()); + for (PhysicalChannelConfig config : configs) { + sanitizedConfigs.add(config.createLocationInfoSanitizedCopy()); + } + return sanitizedConfigs; + } + /** * Notify that the data enabled has changed. * @@ -2855,7 +2904,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage, - @Nullable String callingFeatureId, String message) { + @Nullable String callingFeatureId, String message, int targetSdk) { LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder = new LocationAccessPolicy.LocationPermissionQuery.Builder() .setCallingPackage(callingPackage) @@ -2891,7 +2940,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if (isPhoneStatePermissionRequired(events)) { + if (isPhoneStatePermissionRequired(events, targetSdk)) { if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( mContext, subId, callingPackage, callingFeatureId, message)) { isPermissionCheckSuccessful = false; diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index f5662772f59f..09873f4db045 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -35,7 +35,6 @@ import android.net.NetworkProvider; import android.net.RouteInfo; import android.net.TestNetworkInterface; import android.net.TestNetworkSpecifier; -import android.net.util.NetdService; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -86,7 +85,9 @@ class TestNetworkService extends ITestNetworkManager.Stub { mHandler = new Handler(mHandlerThread.getLooper()); mContext = Objects.requireNonNull(context, "missing Context"); - mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance"); + mNetd = Objects.requireNonNull( + INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)), + "could not get netd instance"); mCm = mContext.getSystemService(ConnectivityManager.class); mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(), TEST_NETWORK_PROVIDER_NAME); diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 051cd9907bee..cd2d6d4e4585 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -29,7 +29,10 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.NetworkCapabilities; @@ -158,6 +161,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb; @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker; @NonNull private final VcnContext mVcnContext; + @NonNull private final BroadcastReceiver mPkgChangeReceiver; /** Can only be assigned when {@link #systemReady()} is called, since it uses AppOpsManager. */ @Nullable private LocationPermissionChecker mLocationPermissionChecker; @@ -203,6 +207,29 @@ public class VcnManagementService extends IVcnManagementService.Stub { mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE); mVcnContext = mDeps.newVcnContext(mContext, mLooper, mNetworkProvider); + mPkgChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + if (Intent.ACTION_PACKAGE_ADDED.equals(action) + || Intent.ACTION_PACKAGE_REPLACED.equals(action) + || Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + mTelephonySubscriptionTracker.handleSubscriptionsChanged(); + } else { + Log.wtf(TAG, "received unexpected intent: " + action); + } + } + }; + + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.registerReceiver( + mPkgChangeReceiver, intentFilter, null /* broadcastPermission */, mHandler); + // Run on handler to ensure I/O does not block system server startup mHandler.post(() -> { PersistableBundle configBundle = null; @@ -348,7 +375,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 +390,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 +566,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 +584,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) { @@ -820,8 +854,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { final IBinder cbBinder = callback.asBinder(); final VcnStatusCallbackInfo cbInfo = - new VcnStatusCallbackInfo( - subGroup, callback, opPkgName, mDeps.getBinderCallingUid()); + new VcnStatusCallbackInfo(subGroup, callback, opPkgName, callingUid); try { cbBinder.linkToDeath(cbInfo, 0 /* flags */); diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 963664149b38..211999f43183 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -35,11 +35,11 @@ import android.os.ServiceDebugInfo; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.sysprop.WatchdogProperties; import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.SparseArray; -import android.sysprop.WatchdogProperties; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ZygoteConnectionConstants; @@ -56,9 +56,9 @@ import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.concurrent.TimeUnit; import java.util.HashSet; import java.util.List; +import java.util.concurrent.TimeUnit; /** This class calls its monitor every minute. Killing this process if they don't return **/ public class Watchdog { @@ -688,7 +688,7 @@ public class Watchdog { if (mActivity != null) { mActivity.addErrorToDropBox( "watchdog", null, "system_server", null, null, null, - subject, report.toString(), stack, null); + subject, report.toString(), stack, null, null, null); } FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d2fd8ff3890e..c6405e01dbff 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -18,7 +18,6 @@ package com.android.server.am; import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND; -import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; @@ -49,6 +48,7 @@ import static android.os.PowerWhitelistManager.REASON_UID_VISIBLE; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.PowerWhitelistManager.getReasonCodeFromProcState; import static android.os.PowerWhitelistManager.reasonCodeToString; +import static android.os.Process.INVALID_UID; import static android.os.Process.NFC_UID; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; @@ -660,7 +660,7 @@ public final class ActiveServices { } ServiceRecord r = res.record; - setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, + setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId, allowBackgroundActivityStarts); if (!mAm.mUserController.exists(r.userId)) { @@ -693,19 +693,7 @@ public final class ActiveServices { + r.shortInstanceName; Slog.w(TAG, msg); showFgsBgRestrictedNotificationLocked(r); - ApplicationInfo aInfo = null; - try { - aInfo = AppGlobals.getPackageManager().getApplicationInfo( - callingPackage, ActivityManagerService.STOCK_PM_FLAGS, - userId); - } catch (android.os.RemoteException e) { - // pm is in same process, this will never happen. - } - if (aInfo == null) { - throw new SecurityException("startServiceLocked failed, " - + "could not resolve client package " + callingPackage); - } - if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, aInfo.uid)) { + if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) { throw new ForegroundServiceStartNotAllowedException(msg); } return null; @@ -839,7 +827,7 @@ public final class ActiveServices { boolean addToStarting = false; if (!callerFg && !fgRequired && r.app == null && mAm.mUserController.hasStartedUserState(r.userId)) { - ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false); + ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid); if (proc == null || proc.mState.getCurProcState() > PROCESS_STATE_RECEIVER) { // If this is not coming from a foreground caller, then we may want // to delay the start if there are already other background services @@ -1790,6 +1778,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, r.userId,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, r.userId,false); + } + } logFgsBackgroundStart(r); if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { final String msg = "Service.startForeground() not allowed due to " @@ -1843,6 +1869,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; + r.mStartForegroundCount++; if (!stopProcStatsOp) { ServiceState stracker = r.getTracker(); if (stracker != null) { @@ -1901,6 +1928,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(), @@ -2539,7 +2567,8 @@ public final class ActiveServices { return 0; } } - setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, false); + setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId, + false); if (s.app != null) { ProcessServiceRecord servicePsr = s.app.mServices; @@ -3411,7 +3440,7 @@ public final class ActiveServices { ProcessRecord app; if (!isolated) { - app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); + app = mAm.getProcessRecordLocked(procName, r.appInfo.uid); if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app); if (app != null) { @@ -3459,7 +3488,7 @@ public final class ActiveServices { // TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service // was initiated from a notification tap or not. if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, - hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) { + hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated)) == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " @@ -3892,6 +3921,7 @@ public final class ActiveServices { r.foregroundId = 0; r.foregroundNoti = null; r.mAllowWhileInUsePermissionInFgs = false; + r.mAllowStartForeground = REASON_DENIED; // Clear start entries. r.clearDeliveredStartsLocked(); @@ -5428,8 +5458,9 @@ public final class ActiveServices { * @return true if allow, false otherwise. */ private void setFgsRestrictionLocked(String callingPackage, - int callingPid, int callingUid, Intent intent, ServiceRecord r, + int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId, boolean allowBackgroundActivityStarts) { + r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime(); // Check DeviceConfig flag. if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { r.mAllowWhileInUsePermissionInFgs = true; @@ -5445,11 +5476,20 @@ public final class ActiveServices { if (r.mAllowStartForeground == REASON_DENIED) { r.mAllowStartForeground = shouldAllowFgsStartForegroundLocked(allowWhileInUse, callingPackage, callingPid, callingUid, intent, r, - allowBackgroundActivityStarts); + userId); } } } + 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; @@ -5579,13 +5619,20 @@ public final class ActiveServices { */ private @ReasonCode int shouldAllowFgsStartForegroundLocked( @ReasonCode int allowWhileInUse, String callingPackage, int callingPid, - int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) { + int callingUid, Intent intent, ServiceRecord r, int userId) { FgsStartTempAllowList.TempFgsAllowListEntry tempAllowListReason = r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid); int ret = shouldAllowFgsStartForegroundLocked(allowWhileInUse, callingPid, callingUid, callingPackage, r); final int uidState = mAm.getUidStateLocked(callingUid); + int callerTargetSdkVersion = INVALID_UID; + try { + ApplicationInfo ai = mAm.mContext.getPackageManager().getApplicationInfoAsUser( + callingPackage, PackageManager.MATCH_KNOWN_PACKAGES, userId); + callerTargetSdkVersion = ai.targetSdkVersion; + } catch (PackageManager.NameNotFoundException e) { + } final String debugInfo = "[callingPackage: " + callingPackage + "; callingUid: " + callingUid @@ -5601,6 +5648,8 @@ public final class ActiveServices { + ",callingUid:" + tempAllowListReason.mCallingUid)) + ">" + "; targetSdkVersion:" + r.appInfo.targetSdkVersion + + "; callerTargetSdkVersion:" + callerTargetSdkVersion + + "; startForegroundCount:" + r.mStartForegroundCount + "]"; if (!debugInfo.equals(r.mInfoAllowStartForeground)) { r.mLoggedInfoAllowStartForeground = false; @@ -5771,7 +5820,12 @@ public final class ActiveServices { private boolean isBgFgsRestrictionEnabled(ServiceRecord r) { return mAm.mConstants.mFlagFgsStartRestrictionEnabled - && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid); + // Checking service's targetSdkVersion. + && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid) + && (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk + // Checking callingUid's targetSdkVersion. + || CompatChanges.isChangeEnabled( + FGS_BG_START_RESTRICTION_CHANGE_ID, r.mRecentCallingUid)); } private void logFgsBackgroundStart(ServiceRecord r) { diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 5859cea2d284..e16f4c9905e8 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. /** @@ -171,6 +172,13 @@ final class ActivityManagerConstants extends ContentObserver { "default_fgs_starts_restriction_enabled"; /** + * Default value for mFgsStartRestrictionCheckCallerTargetSdk if not explicitly set in + * Settings.Global. + */ + private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK = + "default_fgs_starts_restriction_check_caller_target_sdk"; + + /** * Whether FGS notification display is deferred following the transition into * the foreground state. Default behavior is {@code true} unless overridden. */ @@ -370,6 +378,13 @@ final class ActivityManagerConstants extends ContentObserver { // at all. volatile boolean mFlagFgsStartRestrictionEnabled = true; + /** + * Indicates whether the foreground service background start restriction is enabled for + * caller app that is targeting S+. + * This is in addition to check of {@link #mFlagFgsStartRestrictionEnabled} flag. + */ + volatile boolean mFgsStartRestrictionCheckCallerTargetSdk = true; + // Whether we defer FGS notifications a few seconds following their transition to // the foreground state. Applies only to S+ apps; enabled by default. volatile boolean mFlagFgsNotificationDeferralEnabled = true; @@ -396,6 +411,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(','); @@ -547,6 +568,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED: updateFgsStartsRestriction(); break; + case KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK: + updateFgsStartsRestrictionCheckCallerTargetSdk(); + break; case KEY_DEFERRED_FGS_NOTIFICATIONS_ENABLED: updateFgsNotificationDeferralEnable(); break; @@ -586,6 +610,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; } @@ -819,6 +846,13 @@ final class ActivityManagerConstants extends ContentObserver { /*defaultValue*/ true); } + private void updateFgsStartsRestrictionCheckCallerTargetSdk() { + mFgsStartRestrictionCheckCallerTargetSdk = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK, + /*defaultValue*/ true); + } + private void updateFgsNotificationDeferralEnable() { mFlagFgsNotificationDeferralEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -869,6 +903,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 +1112,16 @@ 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.print(" "); pw.print(KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED); pw.print("="); + pw.println(mFlagBackgroundActivityStartsEnabled); + pw.print(" "); pw.print(KEY_DEFAULT_BACKGROUND_FGS_STARTS_RESTRICTION_ENABLED); + pw.print("="); pw.println(mFlagBackgroundFgsStartRestrictionEnabled); + pw.print(" "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED); pw.print("="); + pw.println(mFlagFgsStartRestrictionEnabled); + pw.print(" "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK); + pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 492759f7c73d..2bceb20fa86f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2604,8 +2604,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") - final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) { - return mProcessList.getProcessRecordLocked(processName, uid, keepIfLarge); + final ProcessRecord getProcessRecordLocked(String processName, int uid) { + return mProcessList.getProcessRecordLocked(processName, uid); } @GuardedBy(anyOf = {"this", "mProcLock"}) @@ -2639,8 +2639,7 @@ public class ActivityManagerService extends IActivityManager.Stub false /* knownToBeDead */, 0 /* intentFlags */, sNullHostingRecord /* hostingRecord */, ZYGOTE_POLICY_FLAG_EMPTY, true /* allowWhileBooting */, true /* isolated */, - uid, true /* keepIfLarge */, abiOverride, entryPoint, entryPointArgs, - crashHandler); + uid, abiOverride, entryPoint, entryPointArgs, crashHandler); return proc != null; } } @@ -2649,10 +2648,10 @@ public class ActivityManagerService extends IActivityManager.Stub final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, - boolean isolated, boolean keepIfLarge) { + boolean isolated) { return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags, hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */, - keepIfLarge, null /* ABI override */, null /* entryPoint */, + null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */, null /* crashHandler */); } @@ -3905,7 +3904,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Only the system server can kill an application if (callerUid == SYSTEM_UID) { synchronized (this) { - ProcessRecord app = getProcessRecordLocked(processName, uid, true); + ProcessRecord app = getProcessRecordLocked(processName, uid); IApplicationThread thread; if (app != null && (thread = app.getThread()) != null) { try { @@ -6101,7 +6100,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, - info.uid, true); + info.uid); } else { app = null; } @@ -7706,9 +7705,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName, ApplicationErrorReport.CrashInfo crashInfo) { - boolean isIncremental = false; float loadingProgress = 1; - long millisSinceOldestPendingRead = 0; + IncrementalMetrics incrementalMetrics = null; // Notify package manager service to possibly update package state if (r != null && r.info != null && r.info.packageName != null) { final String codePath = r.info.getCodePath(); @@ -7719,8 +7717,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (incrementalStatesInfo != null) { loadingProgress = incrementalStatesInfo.getProgress(); } - isIncremental = IncrementalManager.isIncrementalPath(codePath); - if (isIncremental) { + if (IncrementalManager.isIncrementalPath(codePath)) { // Report in the main log about the incremental package Slog.e(TAG, "App crashed on incremental package " + r.info.packageName + " which is " + ((int) (loadingProgress * 100)) + "% loaded."); @@ -7729,8 +7726,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (incrementalService != null) { final IncrementalManager incrementalManager = new IncrementalManager( IIncrementalService.Stub.asInterface(incrementalService)); - IncrementalMetrics metrics = incrementalManager.getMetrics(codePath); - millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead(); + incrementalMetrics = incrementalManager.getMetrics(codePath); } } } @@ -7760,7 +7756,9 @@ public class ActivityManagerService extends IActivityManager.Stub processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER : (r != null) ? r.getProcessClassEnum() : ServerProtoEnums.ERROR_SOURCE_UNKNOWN, - isIncremental, loadingProgress, millisSinceOldestPendingRead + incrementalMetrics != null /* isIncremental */, loadingProgress, + incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead() + : -1 ); final int relaunchReason = r == null ? RELAUNCH_REASON_NONE @@ -7773,7 +7771,8 @@ public class ActivityManagerService extends IActivityManager.Stub } addErrorToDropBox( - eventType, r, processName, null, null, null, null, null, null, crashInfo); + eventType, r, processName, null, null, null, null, null, null, crashInfo, + new Float(loadingProgress), incrementalMetrics); mAppErrors.crashApplication(r, crashInfo); } @@ -7955,7 +7954,8 @@ public class ActivityManagerService extends IActivityManager.Stub FrameworkStatsLog.write(FrameworkStatsLog.WTF_OCCURRED, callingUid, tag, processName, callingPid, (r != null) ? r.getProcessClassEnum() : 0); - addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo); + addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo, + null, null); return r; } @@ -7980,7 +7980,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (Pair<String, ApplicationErrorReport.CrashInfo> p = list.poll(); p != null; p = list.poll()) { addErrorToDropBox("wtf", proc, "system_server", null, null, null, p.first, null, null, - p.second); + p.second, null, null); } } @@ -8069,12 +8069,15 @@ public class ActivityManagerService extends IActivityManager.Stub * @param report in long form describing the error, null if absent * @param dataFile text file to include in the report, null if none * @param crashInfo giving an application stack trace, null if absent + * @param loadingProgress the loading progress of an installed package, range in [0, 1]. + * @param incrementalMetrics metrics for apps installed on Incremental. */ public void addErrorToDropBox(String eventType, ProcessRecord process, String processName, String activityShortComponentName, String parentShortComponentName, ProcessRecord parentProcess, String subject, final String report, final File dataFile, - final ApplicationErrorReport.CrashInfo crashInfo) { + final ApplicationErrorReport.CrashInfo crashInfo, + @Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics) { // NOTE -- this must never acquire the ActivityManagerService lock, // otherwise the watchdog may be prevented from resetting the system. @@ -8135,6 +8138,18 @@ public class ActivityManagerService extends IActivityManager.Stub if (crashInfo != null && crashInfo.crashTag != null && !crashInfo.crashTag.isEmpty()) { sb.append("Crash-Tag: ").append(crashInfo.crashTag).append("\n"); } + if (loadingProgress != null) { + sb.append("Loading-Progress: ").append(loadingProgress.floatValue()).append("\n"); + } + if (incrementalMetrics != null) { + sb.append("Incremental: Yes").append("\n"); + final long millisSinceOldestPendingRead = + incrementalMetrics.getMillisSinceOldestPendingRead(); + if (millisSinceOldestPendingRead > 0) { + sb.append("Millis-Since-Oldest-Pending-Read: ").append( + millisSinceOldestPendingRead).append("\n"); + } + } sb.append("\n"); // Do the rest in a worker thread to avoid blocking the caller on I/O @@ -11917,7 +11932,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessRecord proc = startProcessLocked(app.processName, app, false, 0, new HostingRecord("backup", hostingName), - ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false, false); + ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false); if (proc == null) { Slog.e(TAG, "Unable to start backup agent process " + r); return false; @@ -13627,7 +13642,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessRecord app; synchronized (mProcLock) { if (noRestart) { - app = getProcessRecordLocked(ai.processName, ai.uid, true); + app = getProcessRecordLocked(ai.processName, ai.uid); } else { // Instrumentation can kill and relaunch even persistent processes forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, @@ -13670,7 +13685,7 @@ public class ActivityManagerService extends IActivityManager.Stub ApplicationInfo targetInfo) { ProcessRecord pr; synchronized (this) { - pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid, true); + pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid); } try { @@ -15417,8 +15432,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void killProcess(String processName, int uid, String reason) { synchronized (ActivityManagerService.this) { - final ProcessRecord proc = getProcessRecordLocked(processName, uid, - true /* keepIfLarge */); + final ProcessRecord proc = getProcessRecordLocked(processName, uid); if (proc != null) { mProcessList.removeProcessLocked(proc, false /* callerWillRestart */, true /* allowRestart */, ApplicationExitInfo.REASON_OTHER, reason); @@ -15825,7 +15839,7 @@ public class ActivityManagerService extends IActivityManager.Stub startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */, new HostingRecord(hostingType, hostingName, isTop), ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */, - false /* isolated */, true /* keepIfLarge */); + false /* isolated */); } } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 31ea14a73409..074e8fe42016 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1627,7 +1627,7 @@ public class AppProfiler { dropBuilder.append(catSw.toString()); FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED); mService.addErrorToDropBox("lowmem", null, "system_server", null, - null, null, tag.toString(), dropBuilder.toString(), null, null); + null, null, tag.toString(), dropBuilder.toString(), null, null, null, null); synchronized (mService) { long now = SystemClock.uptimeMillis(); if (mLastMemUsageReportTime < now) { diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index e74c936af02d..60e8d54676ec 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -487,8 +487,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { Slog.wtf(TAG, "Error updating external stats: ", e); } - synchronized (BatteryExternalStatsWorker.this) { - mLastCollectionTimeStamp = SystemClock.elapsedRealtime(); + if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) { + synchronized (BatteryExternalStatsWorker.this) { + mLastCollectionTimeStamp = SystemClock.elapsedRealtime(); + } } } }; @@ -658,6 +660,11 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState, elapsedRealtime); } + + final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC; + if (gnssChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) { + mStats.updateGnssMeasuredEnergyStatsLocked(displayChargeUC, elapsedRealtime); + } } // Inform mStats about each applicable custom energy bucket. if (measuredEnergyDeltas != null @@ -698,7 +705,10 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } if (modemInfo != null) { - mStats.noteModemControllerActivity(modemInfo, elapsedRealtime, uptime); + final long mobileRadioChargeUC = measuredEnergyDeltas != null + ? measuredEnergyDeltas.mobileRadioChargeUC : MeasuredEnergySnapshot.UNAVAILABLE; + mStats.noteModemControllerActivity(modemInfo, mobileRadioChargeUC, elapsedRealtime, + uptime); } if (updateFlags == UPDATE_ALL) { @@ -830,6 +840,12 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { case EnergyConsumerType.CPU_CLUSTER: buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true; break; + case EnergyConsumerType.GNSS: + buckets[MeasuredEnergyStats.POWER_BUCKET_GNSS] = true; + break; + case EnergyConsumerType.MOBILE_RADIO: + buckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO] = true; + break; case EnergyConsumerType.DISPLAY: buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true; buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE] = true; @@ -883,6 +899,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { if ((flags & UPDATE_DISPLAY) != 0) { addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY); } + if ((flags & UPDATE_RADIO) != 0) { + addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.MOBILE_RADIO); + } if ((flags & UPDATE_WIFI) != 0) { addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.WIFI); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c3f97adbd9c3..5937a18673d9 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -674,6 +674,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null); + awaitCompletion(); + + if (mBatteryUsageStatsProvider.shouldUpdateStats(queries, + mWorker.getLastCollectionTimeStamp())) { + syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL); + } + return mBatteryUsageStatsProvider.getBatteryUsageStats(queries); } @@ -1966,7 +1973,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { - mStats.noteModemControllerActivity(info, elapsedRealtime, uptime); + mStats.noteModemControllerActivity(info, POWER_DATA_UNAVAILABLE, elapsedRealtime, + uptime); }); } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index e79f09665153..9ecae42ce2c9 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1544,7 +1544,7 @@ public final class BroadcastQueue { } String targetProcess = info.activityInfo.processName; ProcessRecord app = mService.getProcessRecordLocked(targetProcess, - info.activityInfo.applicationInfo.uid, false); + info.activityInfo.applicationInfo.uid); if (!skip) { final int allowed = mService.getAppStartModeLOSP( @@ -1678,7 +1678,7 @@ public final class BroadcastQueue { r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, new HostingRecord("broadcast", r.curComponent), isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY, - (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false); + (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false); if (r.curApp == null) { // Ah, this recipient is unavailable. Finish it if necessary, // and mark the broadcast record as ready for the next. diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index c5f082a0f9e3..1839e2a9cee4 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -382,10 +382,13 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") void compactAppSome(ProcessRecord app) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( - COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); + if (!app.mOptRecord.hasPendingCompact()) { + app.mOptRecord.setHasPendingCompact(true); + mPendingCompactionProcesses.add(app); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( + COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); + } } @GuardedBy("mProcLock") @@ -396,10 +399,13 @@ public final class CachedAppOptimizer { && app.mState.getCurAdj() >= mCompactThrottleMinOomAdj && app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( - COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); + if (!app.mOptRecord.hasPendingCompact()) { + app.mOptRecord.setHasPendingCompact(true); + mPendingCompactionProcesses.add(app); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( + COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); + } } else { if (DEBUG_COMPACTION) { Slog.d(TAG_AM, "Skipping full compaction for " + app.processName @@ -412,10 +418,13 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") void compactAppPersistent(ProcessRecord app) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( + if (!app.mOptRecord.hasPendingCompact()) { + app.mOptRecord.setHasPendingCompact(true); + mPendingCompactionProcesses.add(app); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState())); + } } @GuardedBy("mProcLock") @@ -427,10 +436,13 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") void compactAppBfgs(ProcessRecord app) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( + if (!app.mOptRecord.hasPendingCompact()) { + app.mOptRecord.setHasPendingCompact(true); + mPendingCompactionProcesses.add(app); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState())); + } } @GuardedBy("mProcLock") @@ -954,6 +966,7 @@ public final class CachedAppOptimizer { pendingAction = opt.getReqCompactAction(); pid = proc.getPid(); name = proc.processName; + opt.setHasPendingCompact(false); // don't compact if the process has returned to perceptible // and this is only a cached/home/prev compaction diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 2c8794d75795..ee4526b99d1c 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -436,7 +436,7 @@ public class ContentProviderHelper { // Use existing process if already started checkTime(startTime, "getContentProviderImpl: looking for process record"); ProcessRecord proc = mService.getProcessRecordLocked( - cpi.processName, cpr.appInfo.uid, false); + cpi.processName, cpr.appInfo.uid); IApplicationThread thread; if (proc != null && (thread = proc.getThread()) != null && !proc.isKilled()) { @@ -459,7 +459,7 @@ public class ContentProviderHelper { new HostingRecord("content provider", new ComponentName( cpi.applicationInfo.packageName, cpi.name)), - Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false, false); + Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false); checkTime(startTime, "getContentProviderImpl: after start process"); if (proc == null) { Slog.w(TAG, "Unable to launch app " diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java index d789bbb04d7a..f9296a0e64fc 100644 --- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java +++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java @@ -118,6 +118,12 @@ public class MeasuredEnergySnapshot { /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */ public long displayChargeUC = UNAVAILABLE; + /** The chargeUC for {@link EnergyConsumerType#GNSS}. */ + public long gnssChargeUC = UNAVAILABLE; + + /** The chargeUC for {@link EnergyConsumerType#MOBILE_RADIO}. */ + public long mobileRadioChargeUC = UNAVAILABLE; + /** The chargeUC for {@link EnergyConsumerType#WIFI}. */ public long wifiChargeUC = UNAVAILABLE; @@ -217,6 +223,14 @@ public class MeasuredEnergySnapshot { output.displayChargeUC = deltaChargeUC; break; + case EnergyConsumerType.GNSS: + output.gnssChargeUC = deltaChargeUC; + break; + + case EnergyConsumerType.MOBILE_RADIO: + output.mobileRadioChargeUC = deltaChargeUC; + break; + case EnergyConsumerType.WIFI: output.wifiChargeUC = deltaChargeUC; break; diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index 464361076dc8..f4ce7230f72a 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -47,6 +47,12 @@ final class ProcessCachedOptimizerRecord { private int mLastCompactAction; /** + * This process has been scheduled for a memory compaction. + */ + @GuardedBy("mProcLock") + private boolean mPendingCompact; + + /** * True when the process is frozen. */ @GuardedBy("mProcLock") @@ -101,6 +107,16 @@ final class ProcessCachedOptimizerRecord { } @GuardedBy("mProcLock") + boolean hasPendingCompact() { + return mPendingCompact; + } + + @GuardedBy("mProcLock") + void setHasPendingCompact(boolean pendingCompact) { + mPendingCompact = pendingCompact; + } + + @GuardedBy("mProcLock") boolean isFrozen() { return mFrozen; } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 93f30cc8ac5e..ab4a2d59ed90 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -300,9 +300,8 @@ class ProcessErrorStateRecord { } // Check if package is still being loaded - boolean isIncremental = false; float loadingProgress = 1; - long millisSinceOldestPendingRead = 0; + IncrementalMetrics incrementalMetrics = null; final PackageManagerInternal packageManagerInternal = mService.getPackageManagerInternal(); if (aInfo != null && aInfo.packageName != null) { IncrementalStatesInfo incrementalStatesInfo = @@ -312,8 +311,7 @@ class ProcessErrorStateRecord { loadingProgress = incrementalStatesInfo.getProgress(); } final String codePath = aInfo.getCodePath(); - isIncremental = IncrementalManager.isIncrementalPath(codePath); - if (isIncremental) { + if (IncrementalManager.isIncrementalPath(codePath)) { // Report in the main log that the incremental package is still loading Slog.e(TAG, "App crashed on incremental package " + aInfo.packageName + " which is " + ((int) (loadingProgress * 100)) + "% loaded."); @@ -322,8 +320,7 @@ class ProcessErrorStateRecord { if (incrementalService != null) { final IncrementalManager incrementalManager = new IncrementalManager( IIncrementalService.Stub.asInterface(incrementalService)); - IncrementalMetrics metrics = incrementalManager.getMetrics(codePath); - millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead(); + incrementalMetrics = incrementalManager.getMetrics(codePath); } } } @@ -345,7 +342,7 @@ class ProcessErrorStateRecord { info.append("Parent: ").append(parentShortComponentName).append("\n"); } - if (isIncremental) { + if (incrementalMetrics != null) { // Report in the main log about the incremental package info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n"); } @@ -434,12 +431,14 @@ class ProcessErrorStateRecord { : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND, mApp.getProcessClassEnum(), (mApp.info != null) ? mApp.info.packageName : "", - isIncremental, loadingProgress, millisSinceOldestPendingRead); + incrementalMetrics != null /* isIncremental */, loadingProgress, + incrementalMetrics != null ? incrementalMetrics.getMillisSinceOldestPendingRead() + : -1); final ProcessRecord parentPr = parentProcess != null ? (ProcessRecord) parentProcess.mOwner : null; mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName, parentShortComponentName, parentPr, annotation, report.toString(), tracesFile, - null); + null, new Float(loadingProgress), incrementalMetrics); if (mApp.getWindowProcessController().appNotResponding(info.toString(), () -> { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 51bcde869189..2cde423bd0a3 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -36,7 +36,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; @@ -54,7 +53,6 @@ import static com.android.server.am.ActivityManagerService.TAG_LRU; import static com.android.server.am.ActivityManagerService.TAG_NETWORK; import static com.android.server.am.ActivityManagerService.TAG_PROCESSES; import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; -import static com.android.server.am.AppProfiler.TAG_PSS; import android.annotation.NonNull; import android.app.ActivityManager; @@ -1501,8 +1499,7 @@ public final class ProcessList { } @GuardedBy("mService") - final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean - keepIfLarge) { + ProcessRecord getProcessRecordLocked(String processName, int uid) { if (uid == SYSTEM_UID) { // The system gets to run in any process. If there are multiple // processes with the same uid, just pick the first (this @@ -1519,41 +1516,7 @@ public final class ProcessList { return procs.valueAt(i); } } - ProcessRecord proc = mProcessNames.get(processName, uid); - if (false && proc != null && !keepIfLarge - && proc.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY - && proc.mProfile.getLastCachedPss() >= 4000) { - // Turn this condition on to cause killing to happen regularly, for testing. - synchronized (mService.mAppProfiler.mProfilerLock) { - proc.mProfile.reportCachedKill(); - } - proc.killLocked(Long.toString(proc.mProfile.getLastCachedPss()) + "k from cached", - ApplicationExitInfo.REASON_OTHER, - ApplicationExitInfo.SUBREASON_LARGE_CACHED, - true); - } else if (proc != null && !keepIfLarge - && !mService.mAppProfiler.isLastMemoryLevelNormal() - && proc.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) { - final long lastCachedPss; - boolean doKilling = false; - synchronized (mService.mAppProfiler.mProfilerLock) { - lastCachedPss = proc.mProfile.getLastCachedPss(); - if (lastCachedPss >= getCachedRestoreThresholdKb()) { - proc.mProfile.reportCachedKill(); - doKilling = true; - } - } - if (DEBUG_PSS) { - Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + lastCachedPss); - } - if (doKilling) { - proc.killLocked(Long.toString(lastCachedPss) + "k from cached", - ApplicationExitInfo.REASON_OTHER, - ApplicationExitInfo.SUBREASON_LARGE_CACHED, - true); - } - } - return proc; + return mProcessNames.get(processName, uid); } void getMemoryInfo(ActivityManager.MemoryInfo outInfo) { @@ -1756,12 +1719,13 @@ public final class ProcessList { private boolean enableNativeHeapZeroInit(ProcessRecord app) { // Look at the process attribute first. - if (app.processInfo != null && app.processInfo.nativeHeapZeroInit != null) { - return app.processInfo.nativeHeapZeroInit; + if (app.processInfo != null + && app.processInfo.nativeHeapZeroInitialized != ApplicationInfo.ZEROINIT_DEFAULT) { + return app.processInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_ENABLED; } // Then at the application attribute. - if (app.info.isNativeHeapZeroInit() != null) { - return app.info.isNativeHeapZeroInit(); + if (app.info.getNativeHeapZeroInitialized() != ApplicationInfo.ZEROINIT_DEFAULT) { + return app.info.getNativeHeapZeroInitialized() == ApplicationInfo.ZEROINIT_ENABLED; } // Compat feature last. if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_ZERO_INIT, app.info)) { @@ -2408,12 +2372,11 @@ public final class ProcessList { ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid, - boolean keepIfLarge, String abiOverride, String entryPoint, String[] entryPointArgs, - Runnable crashHandler) { + String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) { long startTime = SystemClock.uptimeMillis(); ProcessRecord app; if (!isolated) { - app = getProcessRecordLocked(processName, info.uid, keepIfLarge); + app = getProcessRecordLocked(processName, info.uid); checkSlow(startTime, "startProcess: after getProcessRecord"); if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) { @@ -2476,12 +2439,8 @@ public final class ProcessList { ProcessList.killProcessGroup(app.uid, app.getPid()); checkSlow(startTime, "startProcess: done killing old proc"); - if (!app.isKilled() - || mService.mAppProfiler.isLastMemoryLevelNormal() - || app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_CACHED_EMPTY - || app.mProfile.getLastCachedPss() < getCachedRestoreThresholdKb()) { - // Throw a wtf if it's not killed, or killed but not because the system was in - // memory pressure + the app was in "cch-empty" and used large amount of memory + if (!app.isKilled()) { + // Throw a wtf if it's not killed Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process"); } else { Slog.w(TAG_PROCESSES, app.toString() + " is attached to a previous process"); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 42e7ff4c5724..cb23b898a467 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -479,7 +479,7 @@ class ProcessRecord implements WindowProcessListener { if (procInfo != null && procInfo.deniedPermissions == null && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT - && procInfo.nativeHeapZeroInit == null) { + && procInfo.nativeHeapZeroInitialized == ApplicationInfo.ZEROINIT_DEFAULT) { // If this process hasn't asked for permissions to be denied, or for a // non-default GwpAsan mode, or any other non-default setting, then we don't // care about it. 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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index ec2020f94969..31183cac50e1 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -334,13 +334,15 @@ class UserController implements Handler.Callback { volatile boolean mBootCompleted; /** - * In this mode, user is always stopped when switched out but locking of user data is + * In this mode, user is always stopped when switched out (unless overridden by the + * {@code fw.stop_bg_users_on_switch} system property) but locking of user data is * postponed until total number of unlocked users in the system reaches mMaxRunningUsers. * Once total number of unlocked users reach mMaxRunningUsers, least recently used user * will be locked. */ @GuardedBy("mLock") private boolean mDelayUserDataLocking; + /** * Keep track of last active users for mDelayUserDataLocking. * The latest stopped user is placed in front while the least recently stopped user in back. @@ -406,10 +408,9 @@ class UserController implements Handler.Callback { } } - private boolean isDelayUserDataLockingEnabled() { - synchronized (mLock) { - return mDelayUserDataLocking; - } + private boolean shouldStopBackgroundUsersOnSwitch() { + int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1); + return property == -1 ? mDelayUserDataLocking : property == 1; } void finishUserSwitch(UserState uss) { @@ -1041,6 +1042,11 @@ class UserController implements Handler.Callback { void finishUserStopped(UserState uss, boolean allowDelayedLocking) { final int userId = uss.mHandle.getIdentifier(); + if (DEBUG_MU) { + Slog.i(TAG, "finishUserStopped(%d): allowDelayedLocking=%b", userId, + allowDelayedLocking); + } + EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPED, userId); final boolean stopped; boolean lockUser = true; @@ -1153,11 +1159,9 @@ class UserController implements Handler.Callback { Slog.i(TAG, "finishUserStopped, stopping user:" + userId + " lock user:" + userIdToLock); } else { - Slog.i(TAG, "finishUserStopped, user:" + userId - + ",skip locking"); + Slog.i(TAG, "finishUserStopped, user:" + userId + ", skip locking"); // do not lock userIdToLock = UserHandle.USER_NULL; - } } return userIdToLock; @@ -1192,6 +1196,7 @@ class UserController implements Handler.Callback { } private void forceStopUser(@UserIdInt int userId, String reason) { + if (DEBUG_MU) Slog.i(TAG, "forceStopUser(%d): %s", userId, reason); mInjector.activityManagerForceStopPackage(userId, reason); if (mInjector.getUserManager().isPreCreated(userId)) { // Don't fire intent for precreated. @@ -1370,6 +1375,7 @@ class UserController implements Handler.Callback { private boolean startUserInternal(@UserIdInt int userId, boolean foreground, @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) { + if (DEBUG_MU) Slog.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : ""); EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId); final int callingUid = Binder.getCallingUid(); @@ -1635,6 +1641,13 @@ class UserController implements Handler.Callback { * PIN or pattern. */ private boolean maybeUnlockUser(final @UserIdInt int userId) { + if (mInjector.isFileEncryptedNativeOnly() && mLockPatternUtils.isSecure(userId)) { + // A token is needed, so don't bother trying to unlock without one. + // This keeps misleading error messages from being logged. + Slog.d(TAG, "Not unlocking user " + userId + + "'s CE storage yet because a credential token is needed"); + return false; + } // Try unlocking storage using empty token return unlockUserCleared(userId, null, null, null); } @@ -1790,20 +1803,28 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } - private void stopBackgroundUsersIfEnforced(int oldUserId) { + private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) { // Never stop system user if (oldUserId == UserHandle.USER_SYSTEM) { return; } - // If running in background is disabled or mDelayUserDataLocking mode, stop the user. - boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, - oldUserId) || isDelayUserDataLockingEnabled(); - if (!disallowRunInBg) { - return; - } + boolean hasRestriction = + hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); synchronized (mLock) { - if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId - + " and related users"); + // If running in background is disabled or mStopBackgroundUsersOnSwitch mode, + // stop the user. + boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch(); + if (!disallowRunInBg) { + if (DEBUG_MU) { + Slog.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related users", + oldUserId); + } + return; + } + if (DEBUG_MU) { + Slog.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users", + oldUserId); + } stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true, null, null); } @@ -1904,7 +1925,7 @@ class UserController implements Handler.Callback { mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); stopGuestOrEphemeralUserIfBackground(oldUserId); - stopBackgroundUsersIfEnforced(oldUserId); + stopBackgroundUsersOnSwitchIfEnforced(oldUserId); } private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) { @@ -2594,6 +2615,8 @@ class UserController implements Handler.Callback { pw.println(" mTargetUserId:" + mTargetUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); + pw.println(" shouldStopBackgroundUsersOnSwitch:" + + shouldStopBackgroundUsersOnSwitch()); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); @@ -3085,5 +3108,11 @@ class UserController implements Handler.Callback { protected IStorageManager getStorageManager() { return IStorageManager.Stub.asInterface(ServiceManager.getService("mount")); } + + // This is needed because isFileEncryptedNativeOnly is a static method, + // but it needs to be mocked out in tests. + protected boolean isFileEncryptedNativeOnly() { + return StorageManager.isFileEncryptedNativeOnly(); + } } } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 0eae6617ba86..57de70858179 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -16,6 +16,17 @@ package com.android.server.app; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_CHANGED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; + +import static com.android.server.wm.CompatModePackages.DOWNSCALED; +import static com.android.server.wm.CompatModePackages.DOWNSCALE_50; +import static com.android.server.wm.CompatModePackages.DOWNSCALE_60; +import static com.android.server.wm.CompatModePackages.DOWNSCALE_70; +import static com.android.server.wm.CompatModePackages.DOWNSCALE_80; +import static com.android.server.wm.CompatModePackages.DOWNSCALE_90; + import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -23,23 +34,42 @@ import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; import android.app.IGameManagerService; +import android.compat.Compatibility; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ServiceManager; +import android.os.ShellCallback; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; import android.util.ArrayMap; -import android.util.Log; +import android.util.KeyValueListParser; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.compat.CompatibilityChangeConfig; +import com.android.internal.compat.IPlatformCompat; import com.android.server.ServiceThread; import com.android.server.SystemService; +import java.io.FileDescriptor; +import java.util.HashSet; +import java.util.List; + /** * Service to manage game related features. * @@ -55,13 +85,20 @@ public final class GameManagerService extends IGameManagerService.Stub { static final int WRITE_SETTINGS = 1; static final int REMOVE_SETTINGS = 2; + static final int POPULATE_GAME_MODE_SETTINGS = 3; static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds private final Context mContext; private final Object mLock = new Object(); + private final Object mDeviceConfigLock = new Object(); private final Handler mHandler; + private final PackageManager mPackageManager; + private final IPlatformCompat mPlatformCompat; + private DeviceConfigListener mDeviceConfigListener; @GuardedBy("mLock") private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>(); + @GuardedBy("mDeviceConfigLock") + private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>(); public GameManagerService(Context context) { this(context, createServiceThread().getLooper()); @@ -70,6 +107,15 @@ public final class GameManagerService extends IGameManagerService.Stub { GameManagerService(Context context, Looper looper) { mContext = context; mHandler = new SettingsHandler(looper); + mPackageManager = mContext.getPackageManager(); + mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver result) { + new GameManagerShellCommand().exec(this, in, out, err, args, callback, result); } class SettingsHandler extends Handler { @@ -130,7 +176,183 @@ public final class GameManagerService extends IGameManagerService.Stub { } break; } + case POPULATE_GAME_MODE_SETTINGS: { + removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj); + loadDeviceConfigLocked(); + break; + } + } + } + } + + private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { + + DeviceConfigListener() { + super(); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_GAME_OVERLAY, + mContext.getMainExecutor(), this); + } + + @Override + public void onPropertiesChanged(Properties properties) { + synchronized (mDeviceConfigLock) { + for (String key : properties.getKeyset()) { + try { + // Check if the package is installed before caching it. + final String packageName = keyToPackageName(key); + mPackageManager.getPackageInfo(packageName, 0); + final GamePackageConfiguration config = + GamePackageConfiguration.fromProperties(key, properties); + if (config.isValid()) { + putConfig(config); + } else { + // This means that we received a bad config, or the config was deleted. + Slog.i(TAG, "Removing config for: " + packageName); + mConfigs.remove(packageName); + disableCompatScale(packageName); + } + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) { + Slog.v(TAG, "Package name not found", e); + } + } + } + } + } + + @Override + public void finalize() { + DeviceConfig.removeOnPropertiesChangedListener(this); + } + } + + private static class GameModeConfiguration { + public static final String TAG = "GameManagerService_GameModeConfiguration"; + public static final String MODE_KEY = "mode"; + public static final String SCALING_KEY = "downscaleFactor"; + + private final @GameMode int mGameMode; + private final String mScaling; + + private GameModeConfiguration(@NonNull int gameMode, + @NonNull String scaling) { + mGameMode = gameMode; + mScaling = scaling; + } + + public static GameModeConfiguration fromKeyValueListParser(KeyValueListParser parser) { + return new GameModeConfiguration( + parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED), + parser.getString(SCALING_KEY, "1.0") + ); + } + + public int getGameMode() { + return mGameMode; + } + + public String getScaling() { + return mScaling; + } + + public boolean isValid() { + return (mGameMode == GameManager.GAME_MODE_PERFORMANCE + || mGameMode == GameManager.GAME_MODE_BATTERY) && getCompatChangeId() != 0; + } + + public String toString() { + return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]"; + } + + public long getCompatChangeId() { + switch (mScaling) { + case "0.5": + return DOWNSCALE_50; + case "0.6": + return DOWNSCALE_60; + case "0.7": + return DOWNSCALE_70; + case "0.8": + return DOWNSCALE_80; + case "0.9": + return DOWNSCALE_90; + } + return 0; + } + } + + private static class GamePackageConfiguration { + public static final String TAG = "GameManagerService_GamePackageConfiguration"; + + private final String mPackageName; + private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs; + + private GamePackageConfiguration(String keyName) { + mPackageName = keyToPackageName(keyName); + mModeConfigs = new ArrayMap<>(); + } + + public String getPackageName() { + return mPackageName; + } + + public @GameMode int[] getAvailableGameModes() { + if (mModeConfigs.keySet().size() > 0) { + return mModeConfigs.keySet().stream() + .mapToInt(Integer::intValue).toArray(); + } + return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; + } + + /** + * Get a GameModeConfiguration for a given game mode. + * + * @return The package's GameModeConfiguration for the provided mode or null if absent + */ + public GameModeConfiguration getGameModeConfiguration(@GameMode int gameMode) { + return mModeConfigs.get(gameMode); + } + + /** + * Insert a new GameModeConfiguration + */ + public void addModeConfig(GameModeConfiguration config) { + if (config.isValid()) { + mModeConfigs.put(config.getGameMode(), config); + } else { + Slog.w(TAG, "Invalid game mode config for " + + mPackageName + ":" + config.toString()); + } + } + + /** + * Create a new instance from a package name and DeviceConfig.Properties instance + */ + public static GamePackageConfiguration fromProperties(String key, + Properties properties) { + final GamePackageConfiguration packageConfig = new GamePackageConfiguration(key); + final String configString = properties.getString(key, ""); + final String[] gameModeConfigStrings = configString.split(":"); + for (String gameModeConfigString : gameModeConfigStrings) { + try { + final KeyValueListParser parser = new KeyValueListParser(','); + parser.setString(gameModeConfigString); + final GameModeConfiguration config = + GameModeConfiguration.fromKeyValueListParser(parser); + packageConfig.addModeConfig(config); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Invalid config string"); + } } + return packageConfig; + } + + public boolean isValid() { + return mModeConfigs.size() > 0; + } + + public String toString() { + return "[Name:" + mPackageName + " Modes: " + mModeConfigs.toString() + "]"; } } @@ -150,6 +372,8 @@ public final class GameManagerService extends IGameManagerService.Stub { public void onStart() { mService = new GameManagerService(getContext()); publishBinderService(Context.GAME_SERVICE, mService); + mService.registerDeviceConfigListener(); + mService.registerPackageReceiver(); } @Override @@ -171,9 +395,8 @@ public final class GameManagerService extends IGameManagerService.Stub { } private boolean isValidPackageName(String packageName) { - final PackageManager pm = mContext.getPackageManager(); try { - return pm.getPackageUid(packageName, 0) == Binder.getCallingUid(); + return mPackageManager.getPackageUid(packageName, 0) == Binder.getCallingUid(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return false; @@ -188,10 +411,27 @@ public final class GameManagerService extends IGameManagerService.Stub { } } + /** + * Get an array of game modes available for a given package. + * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}. + */ + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + synchronized (mDeviceConfigLock) { + final GamePackageConfiguration config = mConfigs.get(packageName); + if (config == null) { + return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; + } + return config.getAvailableGameModes(); + } + } + private @GameMode int getGameModeFromSettings(String packageName, int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { - Log.w(TAG, "User ID '" + userId + "' does not have a Game Mode" + Slog.w(TAG, "User ID '" + userId + "' does not have a Game Mode" + " selected for package: '" + packageName + "'"); return GameManager.GAME_MODE_UNSUPPORTED; } @@ -208,16 +448,36 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public @GameMode int getGameMode(String packageName, int userId) throws SecurityException { - // TODO(b/178860939): Restrict to games only. - userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "getGameMode", "com.android.server.app.GameManagerService"); + // Restrict to games only. + try { + final ApplicationInfo applicationInfo = mPackageManager + .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); + if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { + Slog.e(TAG, "Ignoring attempt to get the Game Mode for '" + packageName + + "' which is not categorized as a game: applicationInfo.flags = " + + applicationInfo.flags + ", category = " + applicationInfo.category); + return GameManager.GAME_MODE_UNSUPPORTED; + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return GameManager.GAME_MODE_UNSUPPORTED; + } + + // This function handles two types of queries: + // 1.) A normal, non-privileged app querying its own Game Mode. + // 2.) A privileged system service querying the Game Mode of another package. + // The least privileged case is a normal app performing a query, so check that first and + // return a value if the package name is valid. Next, check if the caller has the necessary + // permission and return a value. Do this check last, since it can throw an exception. if (isValidPackageName(packageName)) { return getGameModeFromSettings(packageName, userId); } + // Since the package name doesn't match, check the caller has the necessary permission. checkPermission(Manifest.permission.MANAGE_GAME_MODE); return getGameModeFromSettings(packageName, userId); } @@ -230,15 +490,28 @@ public final class GameManagerService extends IGameManagerService.Stub { @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(String packageName, @GameMode int gameMode, int userId) throws SecurityException { - // TODO(b/178860939): Restrict to games only. - checkPermission(Manifest.permission.MANAGE_GAME_MODE); - userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), userId, false, true, "setGameMode", - "com.android.server.app.GameManagerService"); + // Restrict to games only. + try { + final ApplicationInfo applicationInfo = mPackageManager + .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); + if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { + Slog.e(TAG, "Ignoring attempt to set the Game Mode for '" + packageName + + "' which is not categorized as a game: applicationInfo.flags = " + + applicationInfo.flags + ", category = " + applicationInfo.category); + return; + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return; + } synchronized (mLock) { + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, true, "setGameMode", + "com.android.server.app.GameManagerService"); + if (!mSettings.containsKey(userId)) { return; } @@ -250,6 +523,7 @@ public final class GameManagerService extends IGameManagerService.Stub { mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY); } } + updateCompatModeDownscale(packageName, gameMode); } /** @@ -258,6 +532,8 @@ public final class GameManagerService extends IGameManagerService.Stub { @VisibleForTesting void onBootCompleted() { Slog.d(TAG, "onBootCompleted"); + final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS); + mHandler.sendMessage(msg); } void onUserStarting(int userId) { @@ -284,6 +560,187 @@ public final class GameManagerService extends IGameManagerService.Stub { } } + private void loadDeviceConfigLocked() { + final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0); + final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName)) + .toArray(String[]::new); + synchronized (mDeviceConfigLock) { + final Properties properties = DeviceConfig.getProperties( + DeviceConfig.NAMESPACE_GAME_OVERLAY, packageNames); + for (String key : properties.getKeyset()) { + final GamePackageConfiguration config = + GamePackageConfiguration.fromProperties(key, properties); + putConfig(config); + } + } + } + + private void disableCompatScale(String packageName) { + final long uid = Binder.clearCallingIdentity(); + try { + final HashSet<Long> disabledSet = new HashSet<>(); + disabledSet.add(DOWNSCALED); + final CompatibilityChangeConfig changeConfig = new CompatibilityChangeConfig( + new Compatibility.ChangeConfig(new HashSet<>(), disabledSet)); + // TODO: switch to new API provided by aosp/1599153 once merged + try { + mPlatformCompat.setOverridesForTest(changeConfig, packageName); + } catch (SecurityException e) { + Slog.e(TAG, "Missing compat override permission", e); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to call IPlatformCompat#setOverridesForTest", e); + } + } finally { + Binder.restoreCallingIdentity(uid); + } + } + + private void enableCompatScale(String packageName, long scaleId) { + final long uid = Binder.clearCallingIdentity(); + try { + final HashSet<Long> disabledSet = new HashSet<>(); + final HashSet<Long> enabledSet = new HashSet<>(); + disabledSet.add(DOWNSCALE_50); + disabledSet.add(DOWNSCALE_60); + disabledSet.add(DOWNSCALE_70); + disabledSet.add(DOWNSCALE_80); + disabledSet.add(DOWNSCALE_90); + disabledSet.remove(scaleId); + enabledSet.add(DOWNSCALED); + enabledSet.add(scaleId); + final CompatibilityChangeConfig changeConfig = new CompatibilityChangeConfig( + new Compatibility.ChangeConfig(enabledSet, disabledSet)); + // TODO: switch to new API provided by aosp/1599153 once merged + try { + mPlatformCompat.setOverridesForTest(changeConfig, packageName); + } catch (SecurityException e) { + Slog.e(TAG, "Missing compat override permission", e); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to call IPlatformCompat#setOverridesForTest", e); + } + } finally { + Binder.restoreCallingIdentity(uid); + } + } + + private void updateCompatModeDownscale(String packageName, @GameMode int gameMode) { + synchronized (mDeviceConfigLock) { + if (gameMode == GameManager.GAME_MODE_STANDARD + || gameMode == GameManager.GAME_MODE_UNSUPPORTED) { + disableCompatScale(packageName); + Slog.v(TAG, "Disabling downscale"); + return; + } + if (DEBUG) { + Slog.v(TAG, dumpDeviceConfigs()); + } + final GamePackageConfiguration packageConfig = mConfigs.get(packageName); + if (packageConfig == null) { + Slog.w(TAG, "Package configuration not found for " + packageName); + return; + } + final GameModeConfiguration modeConfig = packageConfig.getGameModeConfiguration( + gameMode); + if (modeConfig == null) { + Slog.w(TAG, "Game mode " + gameMode + " not found for " + packageName); + return; + } + long scaleId = modeConfig.getCompatChangeId(); + if (scaleId == 0) { + Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for " + + packageName); + return; + } + Slog.i(TAG, "Enabling downscale: " + scaleId + " for " + packageName); + enableCompatScale(packageName, scaleId); + } + } + + private void putConfig(GamePackageConfiguration config) { + if (config.isValid()) { + if (DEBUG) { + Slog.i(TAG, "Adding config: " + config.toString()); + } + mConfigs.put(config.getPackageName(), config); + } else { + Slog.w(TAG, "Invalid package config for " + + config.getPackageName() + ":" + config.toString()); + } + } + + private void registerPackageReceiver() { + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(ACTION_PACKAGE_ADDED); + packageFilter.addAction(ACTION_PACKAGE_CHANGED); + packageFilter.addAction(ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme("package"); + final BroadcastReceiver packageReceiver = new BroadcastReceiver() { + @Override + public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { + final Uri data = intent.getData(); + try { + final String packageName = data.getSchemeSpecificPart(); + switch (intent.getAction()) { + case ACTION_PACKAGE_ADDED: + case ACTION_PACKAGE_CHANGED: + synchronized (mDeviceConfigLock) { + Properties properties = DeviceConfig.getProperties( + DeviceConfig.NAMESPACE_GAME_OVERLAY, + packageNameToKey(packageName)); + for (String key : properties.getKeyset()) { + GamePackageConfiguration config = + GamePackageConfiguration.fromProperties(key, + properties); + putConfig(config); + } + } + break; + case ACTION_PACKAGE_REMOVED: + disableCompatScale(packageName); + mConfigs.remove(packageName); + break; + default: + // do nothing + break; + } + } catch (NullPointerException e) { + Slog.e(TAG, "Failed to get package name for new package", e); + } + } + }; + mContext.registerReceiver(packageReceiver, packageFilter); + } + + private void registerDeviceConfigListener() { + mDeviceConfigListener = new DeviceConfigListener(); + } + + /** + * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use + * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing + * sequence of '_', then we replace all '.' chars with '_'; + */ + private static String packageNameToKey(String name) { + return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_"); + } + + /** + * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the + * resulting special case '_.' with just '_' to get the original package name. + */ + private static String keyToPackageName(String key) { + return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_"); + } + + private String dumpDeviceConfigs() { + StringBuilder out = new StringBuilder(); + for (String key : mConfigs.keySet()) { + out.append("[\nName: ").append(key) + .append("\nConfig: ").append(mConfigs.get(key).toString()).append("\n]"); + } + return out.toString(); + } + private static ServiceThread createServiceThread() { ServiceThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java new file mode 100644 index 000000000000..e4c0002ab513 --- /dev/null +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -0,0 +1,136 @@ +/* + * 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.app; + +import android.compat.Compatibility; +import android.content.Context; +import android.os.ServiceManager; +import android.os.ShellCommand; +import android.util.ArraySet; + +import com.android.internal.compat.CompatibilityChangeConfig; +import com.android.server.compat.PlatformCompat; +import com.android.server.wm.CompatModePackages; + +import java.io.PrintWriter; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * ShellCommands for GameManagerService. + * + * Use with {@code adb shell cmd game ...}. + */ +public class GameManagerShellCommand extends ShellCommand { + + public GameManagerShellCommand() {} + + private static final ArraySet<Long> DOWNSCALE_CHANGE_IDS = new ArraySet<>(new Long[]{ + CompatModePackages.DOWNSCALED, + CompatModePackages.DOWNSCALE_90, + CompatModePackages.DOWNSCALE_80, + CompatModePackages.DOWNSCALE_70, + CompatModePackages.DOWNSCALE_60, + CompatModePackages.DOWNSCALE_50}); + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + try { + switch (cmd) { + case "downscale": + final String ratio = getNextArgRequired(); + final String packageName = getNextArgRequired(); + + final long changeId; + switch (ratio) { + case "0.5": + changeId = CompatModePackages.DOWNSCALE_50; + break; + case "0.6": + changeId = CompatModePackages.DOWNSCALE_60; + break; + case "0.7": + changeId = CompatModePackages.DOWNSCALE_70; + break; + case "0.8": + changeId = CompatModePackages.DOWNSCALE_80; + break; + case "0.9": + changeId = CompatModePackages.DOWNSCALE_90; + break; + case "disable": + changeId = 0; + break; + default: + changeId = -1; + pw.println("Invalid scaling ratio '" + ratio + "'"); + break; + } + if (changeId == -1) { + break; + } + + Set<Long> enabled = new ArraySet<>(); + Set<Long> disabled; + if (changeId == 0) { + disabled = DOWNSCALE_CHANGE_IDS; + } else { + enabled.add(CompatModePackages.DOWNSCALED); + enabled.add(changeId); + disabled = DOWNSCALE_CHANGE_IDS.stream() + .filter(it -> it != CompatModePackages.DOWNSCALED && it != changeId) + .collect(Collectors.toSet()); + } + + final PlatformCompat platformCompat = (PlatformCompat) + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + final CompatibilityChangeConfig overrides = + new CompatibilityChangeConfig( + new Compatibility.ChangeConfig(enabled, disabled)); + + platformCompat.setOverrides(overrides, packageName); + if (changeId == 0) { + pw.println("Disable downscaling for " + packageName + "."); + } else { + pw.println("Enable downscaling ratio for " + packageName + " to " + ratio); + } + + break; + default: + return handleDefaultCommands(cmd); + } + } catch (Exception e) { + pw.println("Error: " + e); + } + return -1; + } + + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Game manager (game) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" downscale [0.5|0.6|0.7|0.8|0.9|disable] <PACKAGE_NAME>"); + pw.println(" Force app to run at the specified scaling ratio."); + } +} diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index c6824d16cffb..5a99e0e77a6c 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -38,6 +38,7 @@ import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Environment; import android.os.RemoteException; @@ -69,6 +70,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -91,8 +93,10 @@ public final class AppHibernationService extends SystemService { private final Object mLock = new Object(); private final Context mContext; private final IPackageManager mIPackageManager; + private final PackageManagerInternal mPackageManagerInternal; private final IActivityManager mIActivityManager; private final UserManager mUserManager; + @GuardedBy("mLock") private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>(); private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores = @@ -101,6 +105,7 @@ public final class AppHibernationService extends SystemService { private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>(); private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore; private final Injector mInjector; + private final Executor mBackgroundExecutor; @VisibleForTesting boolean mIsServiceEnabled; @@ -123,9 +128,11 @@ public final class AppHibernationService extends SystemService { super(injector.getContext()); mContext = injector.getContext(); mIPackageManager = injector.getPackageManager(); + mPackageManagerInternal = injector.getPackageManagerInternal(); mIActivityManager = injector.getActivityManager(); mUserManager = injector.getUserManager(); mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); + mBackgroundExecutor = injector.getBackgroundExecutor(); mInjector = injector; final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); @@ -147,11 +154,13 @@ public final class AppHibernationService extends SystemService { @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { - List<GlobalLevelState> states = - mGlobalLevelHibernationDiskStore.readHibernationStates(); - synchronized (mLock) { - initializeGlobalHibernationStates(states); - } + mBackgroundExecutor.execute(() -> { + List<GlobalLevelState> states = + mGlobalLevelHibernationDiskStore.readHibernationStates(); + synchronized (mLock) { + initializeGlobalHibernationStates(states); + } + }); } if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { mIsServiceEnabled = isAppHibernationEnabled(); @@ -170,16 +179,15 @@ public final class AppHibernationService extends SystemService { * @return true if package is hibernating for the user */ boolean isHibernatingForUser(String packageName, int userId) { - if (!checkHibernationEnabled("isHibernatingForUser")) { + String methodName = "isHibernatingForUser"; + if (!checkHibernationEnabled(methodName)) { return false; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); - userId = handleIncomingUser(userId, "isHibernating"); - if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user " - + userId); + userId = handleIncomingUser(userId, methodName); + if (!checkUserStatesExist(userId, methodName)) { return false; } synchronized (mLock) { @@ -210,8 +218,9 @@ public final class AppHibernationService extends SystemService { synchronized (mLock) { GlobalLevelState state = mGlobalHibernationStates.get(packageName); if (state == null) { - throw new IllegalArgumentException( - String.format("Package %s is not installed", packageName)); + // This API can be legitimately called before installation finishes as part of + // dex optimization, so we just return false here. + return false; } return state.hibernated; } @@ -225,16 +234,15 @@ public final class AppHibernationService extends SystemService { * @param isHibernating new hibernation state */ void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { - if (!checkHibernationEnabled("setHibernatingForUser")) { + String methodName = "setHibernatingForUser"; + if (!checkHibernationEnabled(methodName)) { return; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); - userId = handleIncomingUser(userId, "setHibernating"); - if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user " - + userId); + userId = handleIncomingUser(userId, methodName); + if (!checkUserStatesExist(userId, methodName)) { return; } synchronized (mLock) { @@ -298,16 +306,15 @@ public final class AppHibernationService extends SystemService { */ @NonNull List<String> getHibernatingPackagesForUser(int userId) { ArrayList<String> hibernatingPackages = new ArrayList<>(); - if (!checkHibernationEnabled("getHibernatingPackagesForUser")) { + String methodName = "getHibernatingPackagesForUser"; + if (!checkHibernationEnabled(methodName)) { return hibernatingPackages; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); - userId = handleIncomingUser(userId, "getHibernatingPackagesForUser"); - if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - Slog.w(TAG, "Attempt to get hibernating packages for a stopped or nonexistent user " - + userId); + userId = handleIncomingUser(userId, methodName); + if (!checkUserStatesExist(userId, methodName)) { return hibernatingPackages; } synchronized (mLock) { @@ -364,7 +371,7 @@ public final class AppHibernationService extends SystemService { @GuardedBy("mLock") private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally"); - // TODO(175830194): Delete vdex/odex when DexManager API is built out + mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName); state.hibernated = true; Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } @@ -468,10 +475,15 @@ public final class AppHibernationService extends SystemService { HibernationStateDiskStore<UserLevelState> diskStore = mInjector.getUserLevelDiskStore(userId); mUserDiskStores.put(userId, diskStore); - List<UserLevelState> storedStates = diskStore.readHibernationStates(); - synchronized (mLock) { - initializeUserHibernationStates(userId, storedStates); - } + mBackgroundExecutor.execute(() -> { + List<UserLevelState> storedStates = diskStore.readHibernationStates(); + synchronized (mLock) { + // Ensure user hasn't stopped in the time to execute. + if (mUserManager.isUserUnlockingOrUnlocked(userId)) { + initializeUserHibernationStates(userId, storedStates); + } + } + }); } @Override @@ -541,6 +553,20 @@ public final class AppHibernationService extends SystemService { } } + private boolean checkUserStatesExist(int userId, String methodName) { + if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { + Slog.e(TAG, String.format( + "Attempt to call %s on stopped or nonexistent user %d", methodName, userId)); + return false; + } + if (!mUserStates.contains(userId)) { + Slog.w(TAG, String.format( + "Attempt to call %s before states have been read from disk", methodName)); + return false; + } + return true; + } + private boolean checkHibernationEnabled(String methodName) { if (!mIsServiceEnabled) { Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName)); @@ -707,10 +733,14 @@ public final class AppHibernationService extends SystemService { IPackageManager getPackageManager(); + PackageManagerInternal getPackageManagerInternal(); + IActivityManager getActivityManager(); UserManager getUserManager(); + Executor getBackgroundExecutor(); + HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore(); HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId); @@ -739,6 +769,11 @@ public final class AppHibernationService extends SystemService { } @Override + public PackageManagerInternal getPackageManagerInternal() { + return LocalServices.getService(PackageManagerInternal.class); + } + + @Override public IActivityManager getActivityManager() { return ActivityManager.getService(); } @@ -749,6 +784,11 @@ public final class AppHibernationService extends SystemService { } @Override + public Executor getBackgroundExecutor() { + return mScheduledExecutorService; + } + + @Override public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME); return new HibernationStateDiskStore<>( diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java index c83659d2ff56..24cf43339847 100644 --- a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java +++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java @@ -109,6 +109,7 @@ class HibernationStateDiskStore<T> { * @return the parsed list of hibernation states, null if file does not exist */ @Nullable + @WorkerThread List<T> readHibernationStates() { synchronized (this) { if (!mHibernationFile.exists()) { 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/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 3cfaaf7c23e0..677ea5dc8f03 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -52,7 +52,8 @@ public abstract class BaseClientMonitor extends LoggableMonitor * * @param clientMonitor Reference of the ClientMonitor that is starting. */ - default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {} + default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { + } /** * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous @@ -63,10 +64,11 @@ public abstract class BaseClientMonitor extends LoggableMonitor * @param clientMonitor Reference of the ClientMonitor that finished. * @param success True if the operation completed successfully. */ - default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {} + default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { + } } - protected final int mSequentialId; + private final int mSequentialId; @NonNull private final Context mContext; private final int mTargetUserId; @NonNull private final String mOwner; diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java index 8b9be83bc8c3..3d6932639c1c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java @@ -25,18 +25,27 @@ import android.os.IBinder; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; -public abstract class StartUserClient<T> extends HalClientMonitor<T> { - - public interface UserStartedCallback { - void onUserStarted(int newUserId); +/** + * Abstract class for starting a new user. + * @param <T> Interface to request a new user. + * @param <U> Newly created user object. + */ +public abstract class StartUserClient<T, U> extends HalClientMonitor<T> { + + /** + * Invoked when the new user is started. + * @param <U> New user object. + */ + public interface UserStartedCallback<U> { + void onUserStarted(int newUserId, U newUser); } @NonNull @VisibleForTesting - protected final UserStartedCallback mUserStartedCallback; + protected final UserStartedCallback<U> mUserStartedCallback; public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, - @NonNull UserStartedCallback callback) { + @NonNull UserStartedCallback<U> callback) { super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(), 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java index 62cd673babac..1f6e1e95050d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java @@ -25,6 +25,10 @@ import android.os.IBinder; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; +/** + * Abstract class for stopping a user. + * @param <T> Interface for stopping the user. + */ public abstract class StopUserClient<T> extends HalClientMonitor<T> { public interface UserStoppedCallback { @@ -32,7 +36,12 @@ public abstract class StopUserClient<T> extends HalClientMonitor<T> { } @NonNull @VisibleForTesting - protected final UserStoppedCallback mUserStoppedCallback; + private final UserStoppedCallback mUserStoppedCallback; + + public void onUserStopped() { + mUserStoppedCallback.onUserStopped(); + getCallback().onClientFinished(this, true /* success */); + } public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index c0ea2b3f8b93..f015a80d338d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -45,13 +45,15 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { public interface UserSwitchCallback { @NonNull StopUserClient<?> getStopUserClient(int userId); - @NonNull StartUserClient<?> getStartUserClient(int newUserId); + @NonNull StartUserClient<?, ?> getStartUserClient(int newUserId); } @NonNull private final CurrentUserRetriever mCurrentUserRetriever; @NonNull private final UserSwitchCallback mUserSwitchCallback; @NonNull @VisibleForTesting final ClientFinishedCallback mClientFinishedCallback; + @Nullable private StopUserClient<?> mStopUserClient; + @VisibleForTesting class ClientFinishedCallback implements BaseClientMonitor.Callback { @Override @@ -108,16 +110,31 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { if (nextUserId == currentUserId) { super.startNextOperationIfIdle(); } else if (currentUserId == UserHandle.USER_NULL) { - Slog.d(getTag(), "User switch required, current user null, next: " + nextUserId); final BaseClientMonitor startClient = mUserSwitchCallback.getStartUserClient(nextUserId); + Slog.d(getTag(), "[Starting User] " + startClient); startClient.start(mClientFinishedCallback); } else { - final BaseClientMonitor stopClient = mUserSwitchCallback - .getStopUserClient(currentUserId); - Slog.d(getTag(), "User switch required, current: " + currentUserId - + ", next: " + nextUserId + ". " + stopClient); - stopClient.start(mClientFinishedCallback); + if (mStopUserClient != null) { + Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient); + } else { + mStopUserClient = mUserSwitchCallback + .getStopUserClient(currentUserId); + Slog.d(getTag(), "[Stopping User] current: " + currentUserId + + ", next: " + nextUserId + ". " + mStopUserClient); + mStopUserClient.start(mClientFinishedCallback); + } + } + } + + public void onUserStopped() { + if (mStopUserClient == null) { + Slog.e(getTag(), "Unexpected onUserStopped"); + return; } + + Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient); + mStopUserClient.onUserStopped(); + mStopUserClient = null; } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 9f5dc69b404e..d10fd4f7fa25 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -502,9 +502,6 @@ public class FaceService extends SystemService implements BiometricServiceCallba return false; } - final boolean enrolled = provider.getEnrolledFaces(sensorId, userId).size() > 0; - Slog.d(TAG, "hasEnrolledFaces, sensor: " + sensorId + ", enrolled: " + enrolled); - return provider.getEnrolledFaces(sensorId, userId).size() > 0; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 07d173c8da02..0c883b047fe4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -92,7 +92,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Override protected void startHalOperation() { try { - mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, mOperationId); + mCancellationSignal = getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index 0eb51fdba159..58eb3ba9e722 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -160,7 +160,7 @@ public class FaceEnrollClient extends EnrollClient<ISession> { features = new byte[0]; } - mCancellationSignal = getFreshDaemon().enroll(mSequentialId, + mCancellationSignal = getFreshDaemon().enroll( HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken), EnrollmentType.DEFAULT, features, mPreviewSurface); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java index 7a846f5b14a5..8cbb896f80a2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java @@ -42,7 +42,7 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<ISessio @Override protected void startHalOperation() { try { - getFreshDaemon().generateChallenge(mSequentialId); + getFreshDaemon().generateChallenge(); } catch (RemoteException e) { Slog.e(TAG, "Unable to generateChallenge", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java index 773647b4bf20..af826c2f68ba 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java @@ -56,7 +56,7 @@ class FaceGetAuthenticatorIdClient extends HalClientMonitor<ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().getAuthenticatorId(mSequentialId); + getFreshDaemon().getAuthenticatorId(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java index 75888a5f28c9..0ece884ecff8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java @@ -48,7 +48,7 @@ class FaceInternalEnumerateClient extends InternalEnumerateClient<ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().enumerateEnrollments(mSequentialId); + getFreshDaemon().enumerateEnrollments(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting enumerate", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java index 855ee1dbf52f..405e2b23b3af 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java @@ -40,7 +40,7 @@ public class FaceInvalidationClient extends InvalidationClient<Face, ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().invalidateAuthenticatorId(mSequentialId); + getFreshDaemon().invalidateAuthenticatorId(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index fdc3bb172a37..a9be8e1a707b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -78,7 +78,6 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull private final String mHalInstanceName; @NonNull @VisibleForTesting final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports - @NonNull private final HalClientMonitor.LazyDaemon<IFace> mLazyDaemon; @NonNull private final Handler mHandler; @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher; @NonNull private final UsageStats mUsageStats; @@ -126,7 +125,6 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mContext = context; mHalInstanceName = halInstanceName; mSensors = new SparseArray<>(); - mLazyDaemon = this::getHalInstance; mHandler = new Handler(Looper.getMainLooper()); mUsageStats = new UsageStats(context); mLockoutResetDispatcher = lockoutResetDispatcher; @@ -163,7 +161,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Nullable - private synchronized IFace getHalInstance() { + @VisibleForTesting + synchronized IFace getHalInstance() { if (mTestHalEnabled) { return new TestHal(); } @@ -214,22 +213,6 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback); } - private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId, - int userId) throws RemoteException { - // Note that per IFace createSession contract, this method will block until all - // existing operations are canceled/finished. However, also note that this is fine, since - // this method "withoutHandler" means it should only ever be invoked from the worker thread, - // so callers will never be blocked. - mSensors.get(sensorId).createNewSession(daemon, sensorId, userId); - - if (FaceUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) { - Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId - + ", user: " + userId); - scheduleInvalidationRequest(sensorId, userId); - } - } - - private void scheduleLoadAuthenticatorIds(int sensorId) { for (UserInfo user : UserManager.get(mContext).getAliveUsers()) { scheduleLoadAuthenticatorIdsForUser(sensorId, user.id); @@ -238,32 +221,16 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } + final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient( + mContext, mSensors.get(sensorId).getLazySession(), userId, + mContext.getOpPackageName(), sensorId, + mSensors.get(sensorId).getAuthenticatorIds()); - final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient( - mContext, mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, - mSensors.get(sensorId).getAuthenticatorIds()); - - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId" - + ", sensorId: " + sensorId - + ", userId: " + userId, e); - } + scheduleForSensor(sensorId, client); }); } - private void scheduleInvalidationRequest(int sensorId, int userId) { + void scheduleInvalidationRequest(int sensorId, int userId) { mHandler.post(() -> { final InvalidationRequesterClient<Face> client = new InvalidationRequesterClient<>(mContext, userId, sensorId, @@ -303,25 +270,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public void scheduleInvalidateAuthenticatorId(int sensorId, int userId, @NonNull IInvalidationCallback callback) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: " - + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FaceInvalidationClient client = new FaceInvalidationClient(mContext, - mSensors.get(sensorId).getLazySession(), userId, sensorId, - mSensors.get(sensorId).getAuthenticatorIds(), callback); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception", e); - } + final FaceInvalidationClient client = new FaceInvalidationClient(mContext, + mSensors.get(sensorId).getLazySession(), userId, sensorId, + mSensors.get(sensorId).getAuthenticatorIds(), callback); + scheduleForSensor(sensorId, client); }); } @@ -344,25 +296,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFaceServiceReceiver receiver, String opPackageName) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, - mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId); - - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e); - } + final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId); + scheduleForSensor(sensorId, client); }); } @@ -370,25 +307,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, - mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId, - challenge); - - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e); - } + final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, + mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId, + challenge); + scheduleForSensor(sensorId, client); }); } @@ -398,41 +320,24 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable NativeHandle previewSurface, boolean debugConsent) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId); - // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to - // this operation. We should not send the callback yet, since the scheduler may - // be processing something else. - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final int maxTemplatesPerUser = mSensors.get( - sensorId).getSensorProperties().maxEnrollmentsPerUser; - final FaceEnrollClient client = new FaceEnrollClient(mContext, - mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures, - ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, - debugConsent); - scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - if (success) { - scheduleLoadAuthenticatorIdsForUser(sensorId, userId); - scheduleInvalidationRequest(sensorId, userId); - } + final int maxTemplatesPerUser = mSensors.get( + sensorId).getSensorProperties().maxEnrollmentsPerUser; + final FaceEnrollClient client = new FaceEnrollClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, + opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures, + ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, + debugConsent); + scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (success) { + scheduleLoadAuthenticatorIdsForUser(sensorId, userId); + scheduleInvalidationRequest(sensorId, userId); } - }); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling enroll", e); - } + } + }); }); } @@ -447,31 +352,14 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull String opPackageName, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId); - // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to - // this operation. We should not send the callback yet, since the scheduler may - // be processing something else. - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); - final FaceAuthenticationClient client = new FaceAuthenticationClient( - mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId, - operationId, restricted, opPackageName, cookie, - false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, - mUsageStats, mSensors.get(sensorId).getLockoutCache(), - allowBackgroundAuthentication); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling authenticate", e); - } + final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); + final FaceAuthenticationClient client = new FaceAuthenticationClient( + mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId, + operationId, restricted, opPackageName, cookie, + false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, + mUsageStats, mSensors.get(sensorId).getLockoutCache(), + allowBackgroundAuthentication); + scheduleForSensor(sensorId, client); }); } @@ -503,56 +391,24 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId); - // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to - // this operation. We should not send the callback yet, since the scheduler may - // be processing something else. - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FaceRemovalClient client = new FaceRemovalClient(mContext, - mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), faceIds, userId, - opPackageName, FaceUtils.getInstance(sensorId), sensorId, - mSensors.get(sensorId).getAuthenticatorIds()); - - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling remove", e); - } + final FaceRemovalClient client = new FaceRemovalClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + new ClientMonitorCallbackConverter(receiver), faceIds, userId, + opPackageName, FaceUtils.getInstance(sensorId), sensorId, + mSensors.get(sensorId).getAuthenticatorIds()); + scheduleForSensor(sensorId, client); }); } @Override public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FaceResetLockoutClient client = new FaceResetLockoutClient( - mContext, mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, hardwareAuthToken, - mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); + final FaceResetLockoutClient client = new FaceResetLockoutClient( + mContext, mSensors.get(sensorId).getLazySession(), userId, + mContext.getOpPackageName(), sensorId, hardwareAuthToken, + mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling resetLockout", e); - } + scheduleForSensor(sensorId, client); }); } @@ -580,29 +436,14 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public void scheduleInternalCleanup(int sensorId, int userId, @Nullable BaseClientMonitor.Callback callback) { mHandler.post(() -> { - final IFace daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final List<Face> enrolledList = getEnrolledFaces(sensorId, userId); - final FaceInternalCleanupClient client = - new FaceInternalCleanupClient(mContext, - mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, enrolledList, - FaceUtils.getInstance(sensorId), - mSensors.get(sensorId).getAuthenticatorIds()); - - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e); - } + final List<Face> enrolledList = getEnrolledFaces(sensorId, userId); + final FaceInternalCleanupClient client = + new FaceInternalCleanupClient(mContext, + mSensors.get(sensorId).getLazySession(), userId, + mContext.getOpPackageName(), sensorId, enrolledList, + FaceUtils.getInstance(sensorId), + mSensors.get(sensorId).getAuthenticatorIds()); + scheduleForSensor(sensorId, client); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java index 48796c173dd8..ba678f3b19ae 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java @@ -53,7 +53,7 @@ class FaceRemovalClient extends RemovalClient<Face, ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds); + getFreshDaemon().removeEnrollments(mBiometricIds); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting remove", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index ce728bc7c315..5e57950ccf6e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -71,7 +71,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken); + getFreshDaemon().resetLockout(mHardwareAuthToken); } catch (RemoteException e) { Slog.e(TAG, "Unable to reset lockout", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java index 2863f9fc2959..229417361ddb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java @@ -45,7 +45,7 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().revokeChallenge(mSequentialId, mChallenge); + getFreshDaemon().revokeChallenge(mChallenge); } catch (RemoteException e) { Slog.e(TAG, "Unable to revokeChallenge", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java new file mode 100644 index 000000000000..c364dbb4d615 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.ISessionCallback; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.sensors.StartUserClient; + +public class FaceStartUserClient extends StartUserClient<IFace, ISession> { + private static final String TAG = "FaceStartUserClient"; + + @NonNull private final ISessionCallback mSessionCallback; + + public FaceStartUserClient(@NonNull Context context, @NonNull LazyDaemon<IFace> lazyDaemon, + @Nullable IBinder token, int userId, int sensorId, + @NonNull ISessionCallback sessionCallback, + @NonNull UserStartedCallback<ISession> callback) { + super(context, lazyDaemon, token, userId, sensorId, callback); + mSessionCallback = sessionCallback; + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override + protected void startHalOperation() { + try { + final ISession newSession = getFreshDaemon().createSession(getSensorId(), + getTargetUserId(), mSessionCallback); + mUserStartedCallback.onUserStarted(getTargetUserId(), newSession); + getCallback().onClientFinished(this, true /* success */); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + getCallback().onClientFinished(this, false /* success */); + } + } + + @Override + public void unableToStart() { + + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java new file mode 100644 index 000000000000..06328e311b06 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.sensors.StopUserClient; + +public class FaceStopUserClient extends StopUserClient<ISession> { + private static final String TAG = "FaceStopUserClient"; + + public FaceStopUserClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, + @Nullable IBinder token, int userId, int sensorId, + @NonNull UserStoppedCallback callback) { + super(context, lazyDaemon, token, userId, sensorId, callback); + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override + protected void startHalOperation() { + try { + getFreshDaemon().close(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + getCallback().onClientFinished(this, false /* success */); + } + } + + @Override + public void unableToStart() { + + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 3eb475921304..ee367756dc04 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -33,8 +33,11 @@ import android.hardware.face.Face; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.keymaster.HardwareAuthToken; +import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -54,6 +57,9 @@ import com.android.server.biometrics.sensors.Interruptable; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.StartUserClient; +import com.android.server.biometrics.sensors.StopUserClient; +import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.face.FaceUtils; import java.util.ArrayList; @@ -70,9 +76,10 @@ public class Sensor { @NonNull private final String mTag; @NonNull private final FaceProvider mProvider; @NonNull private final Context mContext; + @NonNull private final IBinder mToken; @NonNull private final Handler mHandler; @NonNull private final FaceSensorPropertiesInternal mSensorProperties; - @NonNull private final BiometricScheduler mScheduler; + @NonNull private final UserAwareBiometricScheduler mScheduler; @NonNull private final LockoutCache mLockoutCache; @NonNull private final Map<Integer, Long> mAuthenticatorIds; @NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession; @@ -112,14 +119,14 @@ public class Sensor { @NonNull private final String mTag; @NonNull - private final BiometricScheduler mScheduler; + private final UserAwareBiometricScheduler mScheduler; private final int mSensorId; private final int mUserId; @NonNull private final Callback mCallback; HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, @NonNull Callback callback) { mContext = context; mHandler = handler; @@ -131,11 +138,6 @@ public class Sensor { } @Override - public void onStateChanged(int cookie, byte state) { - // TODO(b/162973174) - } - - @Override public void onChallengeGenerated(long challenge) { mHandler.post(() -> { final BaseClientMonitor client = mScheduler.getCurrentClient(); @@ -426,9 +428,7 @@ public class Sensor { @Override public void onSessionClosed() { - mHandler.post(() -> { - // TODO: implement this. - }); + mHandler.post(mScheduler::onUserStopped); } } @@ -437,9 +437,53 @@ public class Sensor { mTag = tag; mProvider = provider; mContext = context; + mToken = new Binder(); mHandler = handler; mSensorProperties = sensorProperties; - mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */); + mScheduler = new UserAwareBiometricScheduler(tag, null /* gestureAvailabilityDispatcher */, + () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, + new UserAwareBiometricScheduler.UserSwitchCallback() { + @NonNull + @Override + public StopUserClient<?> getStopUserClient(int userId) { + return new FaceStopUserClient(mContext, mLazySession, mToken, userId, + mSensorProperties.sensorId, () -> mCurrentSession = null); + } + + @NonNull + @Override + public StartUserClient<?, ?> getStartUserClient(int newUserId) { + final HalSessionCallback.Callback callback = () -> { + Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); + mCurrentSession = null; + }; + + final int sensorId = mSensorProperties.sensorId; + + final HalSessionCallback resultController = new HalSessionCallback(mContext, + mHandler, mTag, mScheduler, sensorId, newUserId, callback); + + final StartUserClient.UserStartedCallback<ISession> userStartedCallback = + (userIdStarted, newSession) -> { + mCurrentSession = new Session(mTag, newSession, userIdStarted, + resultController); + if (FaceUtils.getLegacyInstance(sensorId) + .isInvalidationInProgress(mContext, userIdStarted)) { + Slog.w(mTag, + "Scheduling unfinished invalidation request for " + + "sensor: " + + sensorId + + ", user: " + userIdStarted); + provider.scheduleInvalidationRequest(sensorId, + userIdStarted); + } + }; + + return new FaceStartUserClient(mContext, provider::getHalInstance, + mToken, newUserId, mSensorProperties.sensorId, + resultController, userStartedCallback); + } + }); mLockoutCache = new LockoutCache(); mAuthenticatorIds = new HashMap<>(); mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null; @@ -453,11 +497,6 @@ public class Sensor { return mSensorProperties; } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean hasSessionForUser(int userId) { - return mCurrentSession != null && mCurrentSession.mUserId == userId; - } - @Nullable Session getSessionForUser(int userId) { if (mCurrentSession != null && mCurrentSession.mUserId == userId) { return mCurrentSession; @@ -471,20 +510,6 @@ public class Sensor { mProvider, this); } - void createNewSession(@NonNull IFace daemon, int sensorId, int userId) - throws RemoteException { - - final HalSessionCallback.Callback callback = () -> { - Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); - mCurrentSession = null; - }; - final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler, - mTag, mScheduler, sensorId, userId, callback); - - final ISession newSession = daemon.createSession(sensorId, userId, resultController); - mCurrentSession = new Session(mTag, newSession, userId, resultController); - } - @NonNull BiometricScheduler getScheduler() { return mScheduler; } @@ -505,7 +530,7 @@ public class Sensor { if (mCurrentSession != null && mCurrentSession.mSession != null) { // TODO(181984005): This should be scheduled instead of directly invoked Slog.d(mTag, "Closing old session"); - mCurrentSession.mSession.close(888 /* cookie */); + mCurrentSession.mSession.close(); } } catch (RemoteException e) { Slog.e(mTag, "RemoteException", e); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java index 4ca85d000d19..c63af7e4b60c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java @@ -22,7 +22,6 @@ import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.ISession; import android.hardware.biometrics.face.ISessionCallback; import android.hardware.biometrics.face.SensorProps; -import android.hardware.biometrics.face.SessionState; import android.hardware.common.NativeHandle; import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; @@ -45,21 +44,21 @@ public class TestHal extends IFace.Stub { return new ISession.Stub() { @Override - public void generateChallenge(int cookie) throws RemoteException { - Slog.w(TAG, "generateChallenge, cookie: " + cookie); + public void generateChallenge() throws RemoteException { + Slog.w(TAG, "generateChallenge"); cb.onChallengeGenerated(0L); } @Override - public void revokeChallenge(int cookie, long challenge) throws RemoteException { - Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie); + public void revokeChallenge(long challenge) throws RemoteException { + Slog.w(TAG, "revokeChallenge: " + challenge); cb.onChallengeRevoked(challenge); } @Override - public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, + public ICancellationSignal enroll(HardwareAuthToken hat, byte enrollmentType, byte[] features, NativeHandle previewSurface) { - Slog.w(TAG, "enroll, cookie: " + cookie); + Slog.w(TAG, "enroll"); return new ICancellationSignal.Stub() { @Override public void cancel() throws RemoteException { @@ -69,8 +68,8 @@ public class TestHal extends IFace.Stub { } @Override - public ICancellationSignal authenticate(int cookie, long operationId) { - Slog.w(TAG, "authenticate, cookie: " + cookie); + public ICancellationSignal authenticate(long operationId) { + Slog.w(TAG, "authenticate"); return new ICancellationSignal.Stub() { @Override public void cancel() throws RemoteException { @@ -80,8 +79,8 @@ public class TestHal extends IFace.Stub { } @Override - public ICancellationSignal detectInteraction(int cookie) { - Slog.w(TAG, "detectInteraction, cookie: " + cookie); + public ICancellationSignal detectInteraction() { + Slog.w(TAG, "detectInteraction"); return new ICancellationSignal.Stub() { @Override public void cancel() throws RemoteException { @@ -91,51 +90,52 @@ public class TestHal extends IFace.Stub { } @Override - public void enumerateEnrollments(int cookie) throws RemoteException { - Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie); + public void enumerateEnrollments() throws RemoteException { + Slog.w(TAG, "enumerateEnrollments"); cb.onEnrollmentsEnumerated(new int[0]); } @Override - public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException { - Slog.w(TAG, "removeEnrollments, cookie: " + cookie); + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + Slog.w(TAG, "removeEnrollments"); cb.onEnrollmentsRemoved(enrollmentIds); } @Override - public void getFeatures(int cookie, int enrollmentId) throws RemoteException { - Slog.w(TAG, "getFeatures, cookie: " + cookie); + public void getFeatures(int enrollmentId) throws RemoteException { + Slog.w(TAG, "getFeatures"); cb.onFeaturesRetrieved(new byte[0], enrollmentId); } @Override - public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, + public void setFeature(HardwareAuthToken hat, int enrollmentId, byte feature, boolean enabled) throws RemoteException { - Slog.w(TAG, "setFeature, cookie: " + cookie); + Slog.w(TAG, "setFeature"); cb.onFeatureSet(enrollmentId, feature); } @Override - public void getAuthenticatorId(int cookie) throws RemoteException { - Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie); + public void getAuthenticatorId() throws RemoteException { + Slog.w(TAG, "getAuthenticatorId"); cb.onAuthenticatorIdRetrieved(0L); } @Override - public void invalidateAuthenticatorId(int cookie) throws RemoteException { - Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie); + public void invalidateAuthenticatorId() throws RemoteException { + Slog.w(TAG, "invalidateAuthenticatorId"); cb.onAuthenticatorIdInvalidated(0L); } @Override - public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException { - Slog.w(TAG, "resetLockout, cookie: " + cookie); + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + Slog.w(TAG, "resetLockout"); cb.onLockoutCleared(); } @Override - public void close(int cookie) throws RemoteException { - cb.onStateChanged(cookie, SessionState.CLOSED); + public void close() throws RemoteException { + Slog.w(TAG, "close"); + cb.onSessionClosed(); } }; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 50756c89613c..79e361f531c7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -528,6 +528,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } } + scheduleUpdateActiveUserWithoutHandler(userId); + final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName, mSensorId, mCurrentChallengeOwner); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 76a47d382e4b..4e5d12d2021c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -92,7 +92,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this), mUdfpsOverlayController, this); try { - mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, mOperationId); + mCancellationSignal = getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 620a9cf3e6f2..9e9d0eec74ab 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -74,7 +74,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> { IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayController, this); try { - mCancellationSignal = getFreshDaemon().detectInteraction(mSequentialId); + mCancellationSignal = getFreshDaemon().detectInteraction(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting finger detect", e); UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 63fa66cdca20..fd4aece339f3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -123,7 +123,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { UdfpsHelper.getReasonFromEnrollReason(mEnrollReason), mUdfpsOverlayController, this); try { - mCancellationSignal = getFreshDaemon().enroll(mSequentialId, + mCancellationSignal = getFreshDaemon().enroll( HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken)); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting enroll", e); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java index 3c9cceddb5fd..83c64210d6f6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java @@ -44,7 +44,7 @@ class FingerprintGenerateChallengeClient extends GenerateChallengeClient<ISessio @Override protected void startHalOperation() { try { - getFreshDaemon().generateChallenge(mSequentialId); + getFreshDaemon().generateChallenge(); } catch (RemoteException e) { Slog.e(TAG, "Unable to generateChallenge", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java index ce1a31899a0c..ed2345e3362e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java @@ -56,7 +56,7 @@ class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().getAuthenticatorId(mSequentialId); + getFreshDaemon().getAuthenticatorId(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java index c9303609c8dc..e20544a86d01 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java @@ -48,7 +48,7 @@ class FingerprintInternalEnumerateClient extends InternalEnumerateClient<ISessio @Override protected void startHalOperation() { try { - getFreshDaemon().enumerateEnrollments(mSequentialId); + getFreshDaemon().enumerateEnrollments(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting enumerate", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java index 80d1a0ffea6b..6cd2ef1aa0e6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java @@ -40,7 +40,7 @@ public class FingerprintInvalidationClient extends InvalidationClient<Fingerprin @Override protected void startHalOperation() { try { - getFreshDaemon().invalidateAuthenticatorId(mSequentialId); + getFreshDaemon().invalidateAuthenticatorId(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index bcca69b3ad35..972071c6f3ee 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -82,7 +82,6 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull private final String mHalInstanceName; @NonNull @VisibleForTesting final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports - @NonNull private final HalClientMonitor.LazyDaemon<IFingerprint> mLazyDaemon; @NonNull private final Handler mHandler; @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher; @NonNull private final ActivityTaskManager mActivityTaskManager; @@ -131,7 +130,6 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mContext = context; mHalInstanceName = halInstanceName; mSensors = new SparseArray<>(); - mLazyDaemon = this::getHalInstance; mHandler = new Handler(Looper.getMainLooper()); mLockoutResetDispatcher = lockoutResetDispatcher; mActivityTaskManager = ActivityTaskManager.getInstance(); @@ -169,7 +167,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Nullable - private synchronized IFingerprint getHalInstance() { + @VisibleForTesting + synchronized IFingerprint getHalInstance() { if (mTestHalEnabled) { // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables // the test HAL for all sensors under that HAL. This can be updated in the future if @@ -224,21 +223,6 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback); } - private void createNewSessionWithoutHandler(@NonNull IFingerprint daemon, int sensorId, - int userId) throws RemoteException { - // Note that per IFingerprint createSession contract, this method will block until all - // existing operations are canceled/finished. However, also note that this is fine, since - // this method "withoutHandler" means it should only ever be invoked from the worker thread, - // so callers will never be blocked. - mSensors.get(sensorId).createNewSession(daemon, sensorId, userId); - - if (FingerprintUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) { - Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId - + ", user: " + userId); - scheduleInvalidationRequest(sensorId, userId); - } - } - @Override public boolean containsSensor(int sensorId) { return mSensors.contains(sensorId); @@ -275,32 +259,16 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FingerprintGetAuthenticatorIdClient client = - new FingerprintGetAuthenticatorIdClient(mContext, - mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, - mSensors.get(sensorId).getAuthenticatorIds()); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId" - + ", sensorId: " + sensorId - + ", userId: " + userId, e); - } + final FingerprintGetAuthenticatorIdClient client = + new FingerprintGetAuthenticatorIdClient(mContext, + mSensors.get(sensorId).getLazySession(), userId, + mContext.getOpPackageName(), sensorId, + mSensors.get(sensorId).getAuthenticatorIds()); + scheduleForSensor(sensorId, client); }); } - private void scheduleInvalidationRequest(int sensorId, int userId) { + void scheduleInvalidationRequest(int sensorId, int userId) { mHandler.post(() -> { final InvalidationRequesterClient<Fingerprint> client = new InvalidationRequesterClient<>(mContext, userId, sensorId, @@ -312,25 +280,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient( - mContext, mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, hardwareAuthToken, - mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling resetLockout", e); - } + final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient( + mContext, mSensors.get(sensorId).getLazySession(), userId, + mContext.getOpPackageName(), sensorId, hardwareAuthToken, + mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); + scheduleForSensor(sensorId, client); }); } @@ -338,26 +292,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver, String opPackageName) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FingerprintGenerateChallengeClient client = - new FingerprintGenerateChallengeClient(mContext, - mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), opPackageName, - sensorId); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e); - } + final FingerprintGenerateChallengeClient client = + new FingerprintGenerateChallengeClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + new ClientMonitorCallbackConverter(receiver), opPackageName, + sensorId); + scheduleForSensor(sensorId, client); }); } @@ -365,25 +305,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FingerprintRevokeChallengeClient client = - new FingerprintRevokeChallengeClient(mContext, - mSensors.get(sensorId).getLazySession(), token, - opPackageName, sensorId, challenge); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e); - } + final FingerprintRevokeChallengeClient client = + new FingerprintRevokeChallengeClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + opPackageName, sensorId, challenge); + scheduleForSensor(sensorId, client); }); } @@ -392,40 +318,23 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId); - // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to - // this operation. We should not send the callback yet, since the scheduler may - // be processing something else. - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties() - .maxEnrollmentsPerUser; - final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, - mUdfpsOverlayController, maxTemplatesPerUser, enrollReason); - scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { - @Override - public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, - boolean success) { - if (success) { - scheduleLoadAuthenticatorIdsForUser(sensorId, userId); - scheduleInvalidationRequest(sensorId, userId); - } + final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties() + .maxEnrollmentsPerUser; + final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, + opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, + mUdfpsOverlayController, maxTemplatesPerUser, enrollReason); + scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + if (success) { + scheduleLoadAuthenticatorIdsForUser(sensorId, userId); + scheduleInvalidationRequest(sensorId, userId); } - }); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling enroll", e); - } + } + }); }); } @@ -439,29 +348,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, int statsClient) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during finger detect, sensorId: " + sensorId); - // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to - // this operation. We should not send the callback yet, since the scheduler may - // be processing something else. - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); - final FingerprintDetectClient client = new FingerprintDetectClient(mContext, - mSensors.get(sensorId).getLazySession(), token, callback, userId, - opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric, - statsClient); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling finger detect", e); - } + final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); + final FingerprintDetectClient client = new FingerprintDetectClient(mContext, + mSensors.get(sensorId).getLazySession(), token, callback, userId, + opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric, + statsClient); + scheduleForSensor(sensorId, client); }); } @@ -471,31 +363,14 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull String opPackageName, boolean restricted, int statsClient, boolean allowBackgroundAuthentication) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId); - // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to - // this operation. We should not send the callback yet, since the scheduler may - // be processing something else. - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); - final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( - mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId, - operationId, restricted, opPackageName, cookie, - false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, - mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), - mUdfpsOverlayController, allowBackgroundAuthentication); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling authenticate", e); - } + final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); + final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( + mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId, + operationId, restricted, opPackageName, cookie, + false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, + mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), + mUdfpsOverlayController, allowBackgroundAuthentication); + scheduleForSensor(sensorId, client); }); } @@ -535,29 +410,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi int[] fingerprintIds, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId); - // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to - // this operation. We should not send the callback yet, since the scheduler may - // be processing something else. - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, - mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId, - opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, - mSensors.get(sensorId).getAuthenticatorIds()); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling remove", e); - } + final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, + mSensors.get(sensorId).getLazySession(), token, + new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId, + opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, + mSensors.get(sensorId).getAuthenticatorIds()); + scheduleForSensor(sensorId, client); }); } @@ -565,28 +423,14 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void scheduleInternalCleanup(int sensorId, int userId, @Nullable BaseClientMonitor.Callback callback) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId); - final FingerprintInternalCleanupClient client = - new FingerprintInternalCleanupClient(mContext, - mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, enrolledList, - FingerprintUtils.getInstance(sensorId), - mSensors.get(sensorId).getAuthenticatorIds()); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e); - } + final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId); + final FingerprintInternalCleanupClient client = + new FingerprintInternalCleanupClient(mContext, + mSensors.get(sensorId).getLazySession(), userId, + mContext.getOpPackageName(), sensorId, enrolledList, + FingerprintUtils.getInstance(sensorId), + mSensors.get(sensorId).getAuthenticatorIds()); + scheduleForSensor(sensorId, client); }); } @@ -611,26 +455,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi public void scheduleInvalidateAuthenticatorId(int sensorId, int userId, @NonNull IInvalidationCallback callback) { mHandler.post(() -> { - final IFingerprint daemon = getHalInstance(); - if (daemon == null) { - Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: " - + sensorId); - return; - } - - try { - if (!mSensors.get(sensorId).hasSessionForUser(userId)) { - createNewSessionWithoutHandler(daemon, sensorId, userId); - } - - final FingerprintInvalidationClient client = - new FingerprintInvalidationClient(mContext, - mSensors.get(sensorId).getLazySession(), userId, sensorId, - mSensors.get(sensorId).getAuthenticatorIds(), callback); - scheduleForSensor(sensorId, client); - } catch (RemoteException e) { - Slog.e(getTag(), "Remote exception", e); - } + final FingerprintInvalidationClient client = + new FingerprintInvalidationClient(mContext, + mSensors.get(sensorId).getLazySession(), userId, sensorId, + mSensors.get(sensorId).getAuthenticatorIds(), callback); + scheduleForSensor(sensorId, client); }); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java index c622208262e0..9a9d6ab19eb6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java @@ -54,7 +54,7 @@ class FingerprintRemovalClient extends RemovalClient<Fingerprint, ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().removeEnrollments(mSequentialId, mBiometricIds); + getFreshDaemon().removeEnrollments(mBiometricIds); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting remove", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index adffba280c7f..b00c592e83ff 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -71,7 +71,7 @@ class FingerprintResetLockoutClient extends HalClientMonitor<ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken); + getFreshDaemon().resetLockout(mHardwareAuthToken); } catch (RemoteException e) { Slog.e(TAG, "Unable to reset lockout", e); mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java index ebb4fe63e4a1..d9bf1c3aa303 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java @@ -45,7 +45,7 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<ISession> { @Override protected void startHalOperation() { try { - getFreshDaemon().revokeChallenge(mSequentialId, mChallenge); + getFreshDaemon().revokeChallenge(mChallenge); } catch (RemoteException e) { Slog.e(TAG, "Unable to revokeChallenge", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java new file mode 100644 index 000000000000..2d40c91cbc75 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.fingerprint.IFingerprint; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.sensors.StartUserClient; + +public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> { + private static final String TAG = "FingerprintStartUserClient"; + + @NonNull private final ISessionCallback mSessionCallback; + + public FingerprintStartUserClient(@NonNull Context context, + @NonNull LazyDaemon<IFingerprint> lazyDaemon, + @Nullable IBinder token, int userId, int sensorId, + @NonNull ISessionCallback sessionCallback, + @NonNull UserStartedCallback<ISession> callback) { + super(context, lazyDaemon, token, userId, sensorId, callback); + mSessionCallback = sessionCallback; + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override + protected void startHalOperation() { + try { + final ISession newSession = getFreshDaemon().createSession(getSensorId(), + getTargetUserId(), mSessionCallback); + mUserStartedCallback.onUserStarted(getTargetUserId(), newSession); + getCallback().onClientFinished(this, true /* success */); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + getCallback().onClientFinished(this, false /* success */); + } + } + + @Override + public void unableToStart() { + + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java new file mode 100644 index 000000000000..7055d653dd16 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.biometrics.sensors.StopUserClient; + +public class FingerprintStopUserClient extends StopUserClient<ISession> { + private static final String TAG = "FingerprintStopUserClient"; + + public FingerprintStopUserClient(@NonNull Context context, + @NonNull LazyDaemon<ISession> lazyDaemon, @Nullable IBinder token, int userId, + int sensorId, @NonNull UserStoppedCallback callback) { + super(context, lazyDaemon, token, userId, sensorId, callback); + } + + @Override + public void start(@NonNull Callback callback) { + super.start(callback); + startHalOperation(); + } + + @Override + protected void startHalOperation() { + try { + getFreshDaemon().close(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + getCallback().onClientFinished(this, false /* success */); + } + } + + @Override + public void unableToStart() { + + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index d843bc94455c..4862d849613d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -24,15 +24,17 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.fingerprint.Error; -import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.ISessionCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.keymaster.HardwareAuthToken; +import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -53,6 +55,9 @@ import com.android.server.biometrics.sensors.Interruptable; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.StartUserClient; +import com.android.server.biometrics.sensors.StopUserClient; +import com.android.server.biometrics.sensors.UserAwareBiometricScheduler; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; @@ -72,9 +77,10 @@ class Sensor { @NonNull private final String mTag; @NonNull private final FingerprintProvider mProvider; @NonNull private final Context mContext; + @NonNull private final IBinder mToken; @NonNull private final Handler mHandler; @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties; - @NonNull private final BiometricScheduler mScheduler; + @NonNull private final UserAwareBiometricScheduler mScheduler; @NonNull private final LockoutCache mLockoutCache; @NonNull private final Map<Integer, Long> mAuthenticatorIds; @@ -112,13 +118,13 @@ class Sensor { @NonNull private final Context mContext; @NonNull private final Handler mHandler; @NonNull private final String mTag; - @NonNull private final BiometricScheduler mScheduler; + @NonNull private final UserAwareBiometricScheduler mScheduler; private final int mSensorId; private final int mUserId; @NonNull private final Callback mCallback; HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, - @NonNull BiometricScheduler scheduler, int sensorId, int userId, + @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, @NonNull Callback callback) { mContext = context; mHandler = handler; @@ -130,11 +136,6 @@ class Sensor { } @Override - public void onStateChanged(int cookie, byte state) { - // TODO(b/162973174) - } - - @Override public void onChallengeGenerated(long challenge) { mHandler.post(() -> { final BaseClientMonitor client = mScheduler.getCurrentClient(); @@ -406,9 +407,7 @@ class Sensor { @Override public void onSessionClosed() { - mHandler.post(() -> { - // TODO: implement this. - }); + mHandler.post(mScheduler::onUserStopped); } } @@ -418,9 +417,53 @@ class Sensor { mTag = tag; mProvider = provider; mContext = context; + mToken = new Binder(); mHandler = handler; mSensorProperties = sensorProperties; - mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher); + mScheduler = new UserAwareBiometricScheduler(tag, gestureAvailabilityDispatcher, + () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, + new UserAwareBiometricScheduler.UserSwitchCallback() { + @NonNull + @Override + public StopUserClient<?> getStopUserClient(int userId) { + return new FingerprintStopUserClient(mContext, mLazySession, mToken, + userId, mSensorProperties.sensorId, () -> mCurrentSession = null); + } + + @NonNull + @Override + public StartUserClient<?, ?> getStartUserClient(int newUserId) { + final HalSessionCallback.Callback callback = () -> { + Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); + mCurrentSession = null; + }; + + final int sensorId = mSensorProperties.sensorId; + + final HalSessionCallback resultController = new HalSessionCallback(mContext, + mHandler, mTag, mScheduler, sensorId, newUserId, callback); + + final StartUserClient.UserStartedCallback<ISession> userStartedCallback = + (userIdStarted, newSession) -> { + mCurrentSession = new Session(mTag, + newSession, userIdStarted, resultController); + if (FingerprintUtils.getInstance(sensorId) + .isInvalidationInProgress(mContext, userIdStarted)) { + Slog.w(mTag, + "Scheduling unfinished invalidation request for " + + "sensor: " + + sensorId + + ", user: " + userIdStarted); + provider.scheduleInvalidationRequest(sensorId, + userIdStarted); + } + }; + + return new FingerprintStartUserClient(mContext, provider::getHalInstance, + mToken, newUserId, mSensorProperties.sensorId, + resultController, userStartedCallback); + } + }); mLockoutCache = new LockoutCache(); mAuthenticatorIds = new HashMap<>(); mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null; @@ -434,11 +477,6 @@ class Sensor { return mSensorProperties; } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean hasSessionForUser(int userId) { - return mCurrentSession != null && mCurrentSession.mUserId == userId; - } - @Nullable Session getSessionForUser(int userId) { if (mCurrentSession != null && mCurrentSession.mUserId == userId) { return mCurrentSession; @@ -452,20 +490,6 @@ class Sensor { mProvider, this); } - void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId) - throws RemoteException { - - final HalSessionCallback.Callback callback = () -> { - Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE"); - mCurrentSession = null; - }; - final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler, - mTag, mScheduler, sensorId, userId, callback); - - final ISession newSession = daemon.createSession(sensorId, userId, resultController); - mCurrentSession = new Session(mTag, newSession, userId, resultController); - } - @NonNull BiometricScheduler getScheduler() { return mScheduler; } @@ -486,7 +510,7 @@ class Sensor { if (mCurrentSession != null && mCurrentSession.mSession != null) { // TODO(181984005): This should be scheduled instead of directly invoked Slog.d(mTag, "Closing old session"); - mCurrentSession.mSession.close(999 /* cookie */); + mCurrentSession.mSession.close(); } } catch (RemoteException e) { Slog.e(mTag, "RemoteException", e); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java index 0b7f3abc9005..abc3597a5183 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java @@ -22,7 +22,6 @@ import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.ISessionCallback; import android.hardware.biometrics.fingerprint.SensorProps; -import android.hardware.biometrics.fingerprint.SessionState; import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; import android.util.Slog; @@ -45,20 +44,20 @@ public class TestHal extends IFingerprint.Stub { return new ISession.Stub() { @Override - public void generateChallenge(int cookie) throws RemoteException { - Slog.w(TAG, "generateChallenge, cookie: " + cookie); + public void generateChallenge() throws RemoteException { + Slog.w(TAG, "generateChallenge"); cb.onChallengeGenerated(0L); } @Override - public void revokeChallenge(int cookie, long challenge) throws RemoteException { - Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie); + public void revokeChallenge(long challenge) throws RemoteException { + Slog.w(TAG, "revokeChallenge: " + challenge); cb.onChallengeRevoked(challenge); } @Override - public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) { - Slog.w(TAG, "enroll, cookie: " + cookie); + public ICancellationSignal enroll(HardwareAuthToken hat) { + Slog.w(TAG, "enroll"); return new ICancellationSignal.Stub() { @Override public void cancel() throws RemoteException { @@ -68,8 +67,8 @@ public class TestHal extends IFingerprint.Stub { } @Override - public ICancellationSignal authenticate(int cookie, long operationId) { - Slog.w(TAG, "authenticate, cookie: " + cookie); + public ICancellationSignal authenticate(long operationId) { + Slog.w(TAG, "authenticate"); return new ICancellationSignal.Stub() { @Override public void cancel() throws RemoteException { @@ -79,8 +78,8 @@ public class TestHal extends IFingerprint.Stub { } @Override - public ICancellationSignal detectInteraction(int cookie) { - Slog.w(TAG, "detectInteraction, cookie: " + cookie); + public ICancellationSignal detectInteraction() { + Slog.w(TAG, "detectInteraction"); return new ICancellationSignal.Stub() { @Override public void cancel() throws RemoteException { @@ -90,38 +89,39 @@ public class TestHal extends IFingerprint.Stub { } @Override - public void enumerateEnrollments(int cookie) throws RemoteException { - Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie); + public void enumerateEnrollments() throws RemoteException { + Slog.w(TAG, "enumerateEnrollments"); cb.onEnrollmentsEnumerated(new int[0]); } @Override - public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException { - Slog.w(TAG, "removeEnrollments, cookie: " + cookie); + public void removeEnrollments(int[] enrollmentIds) throws RemoteException { + Slog.w(TAG, "removeEnrollments"); cb.onEnrollmentsRemoved(enrollmentIds); } @Override - public void getAuthenticatorId(int cookie) throws RemoteException { - Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie); + public void getAuthenticatorId() throws RemoteException { + Slog.w(TAG, "getAuthenticatorId"); cb.onAuthenticatorIdRetrieved(0L); } @Override - public void invalidateAuthenticatorId(int cookie) throws RemoteException { - Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie); + public void invalidateAuthenticatorId() throws RemoteException { + Slog.w(TAG, "invalidateAuthenticatorId"); cb.onAuthenticatorIdInvalidated(0L); } @Override - public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException { - Slog.w(TAG, "resetLockout, cookie: " + cookie); + public void resetLockout(HardwareAuthToken hat) throws RemoteException { + Slog.w(TAG, "resetLockout"); cb.onLockoutCleared(); } @Override - public void close(int cookie) throws RemoteException { - cb.onStateChanged(cookie, SessionState.CLOSED); + public void close() throws RemoteException { + Slog.w(TAG, "close"); + cb.onSessionClosed(); } @Override diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 00b39f11a4c6..cd2457635932 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -15,17 +15,33 @@ */ package com.android.server.camera; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.os.Build.VERSION_CODES.M; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.TaskStackListener; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Rect; import android.hardware.CameraSessionStats; import android.hardware.CameraStreamStats; import android.hardware.ICameraService; import android.hardware.ICameraServiceProxy; +import android.hardware.camera2.CameraMetadata; +import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.metrics.LogMaker; import android.nfc.INfcAdapter; @@ -41,7 +57,10 @@ import android.os.UserManager; import android.stats.camera.nano.CameraProtos.CameraStreamProto; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.util.Slog; +import android.view.Display; +import android.view.Surface; import com.android.internal.annotations.GuardedBy; import com.android.framework.protobuf.nano.MessageNano; @@ -61,6 +80,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -203,6 +223,63 @@ public class CameraServiceProxy extends SystemService } } + private final TaskStateHandler mTaskStackListener = new TaskStateHandler(); + + private final class TaskInfo { + private int frontTaskId; + private boolean isResizeable; + private boolean isFixedOrientationLandscape; + private boolean isFixedOrientationPortrait; + private int displayId; + } + + private final class TaskStateHandler extends TaskStackListener { + private final Object mMapLock = new Object(); + + // maps the current top level task id to its corresponding package name + @GuardedBy("mMapLock") + private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>(); + + @Override + public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) + throws RemoteException { + synchronized (mMapLock) { + TaskInfo info = new TaskInfo(); + info.frontTaskId = taskInfo.taskId; + info.isResizeable = taskInfo.isResizeable; + info.displayId = taskInfo.displayId; + info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape( + taskInfo.topActivityInfo.screenOrientation); + info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait( + taskInfo.topActivityInfo.screenOrientation); + mTaskInfoMap.put(taskInfo.topActivityInfo.packageName, info); + } + } + + @Override + public void onTaskRemoved(int taskId) throws RemoteException { + synchronized (mMapLock) { + for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){ + if (entry.getValue().frontTaskId == taskId) { + mTaskInfoMap.remove(entry.getKey()); + break; + } + } + } + } + + public @Nullable TaskInfo getFrontTaskInfo(String packageName) { + synchronized (mMapLock) { + if (mTaskInfoMap.containsKey(packageName)) { + return mTaskInfoMap.get(packageName); + } + } + + Log.e(TAG, "Top task with package name: " + packageName + " not found!"); + return null; + } + }; + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -229,6 +306,102 @@ public class CameraServiceProxy extends SystemService }; private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { + private boolean isMOrBelow(Context ctx, String packageName) { + try { + return ctx.getPackageManager().getPackageInfo( + packageName, 0).applicationInfo.targetSdkVersion <= M; + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG,"Package name not found!"); + } + return false; + } + + /** + * Gets whether crop-rotate-scale is needed. + */ + private boolean getNeedCropRotateScale(Context ctx, String packageName, + @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing) { + if (taskInfo == null) { + return false; + } + + // External cameras do not need crop-rotate-scale. + if (lensFacing != CameraMetadata.LENS_FACING_FRONT + && lensFacing != CameraMetadata.LENS_FACING_BACK) { + Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled."); + return false; + } + + // Only enable the crop-rotate-scale workaround if the app targets M or below and is not + // resizeable. + if ((ctx != null) && !isMOrBelow(ctx, packageName) && taskInfo.isResizeable) { + Slog.v(TAG, + "The activity is N or above and claims to support resizeable-activity. " + + "Crop-rotate-scale is disabled."); + return false; + } + + DisplayManager displayManager = ctx.getSystemService(DisplayManager.class); + Display display = displayManager.getDisplay(taskInfo.displayId); + int rotation = display.getRotation(); + int rotationDegree = 0; + switch (rotation) { + case Surface.ROTATION_0: + rotationDegree = 0; + break; + case Surface.ROTATION_90: + rotationDegree = 90; + break; + case Surface.ROTATION_180: + rotationDegree = 180; + break; + case Surface.ROTATION_270: + rotationDegree = 270; + break; + } + + // Here we only need to know whether the camera is landscape or portrait. Therefore we + // don't need to consider whether it is a front or back camera. The formula works for + // both. + boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0); + Slog.v(TAG, + "Display.getRotation()=" + rotationDegree + + " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation + + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait + + " isFixedOrientationLandscape=" + + taskInfo.isFixedOrientationLandscape); + // We need to do crop-rotate-scale when camera is landscape and activity is portrait or + // vice versa. + if ((taskInfo.isFixedOrientationPortrait && landscapeCamera) + || (taskInfo.isFixedOrientationLandscape && !landscapeCamera)) { + return true; + } else { + return false; + } + } + + @Override + public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation, + int lensFacing) { + if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { + Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + + " camera service UID!"); + return false; + } + + // A few remaining todos: + // 1) Do the same check when working in WM compatible mode. The sequence needs + // to be adjusted and use orientation events as triggers for all active camera + // clients. + // 2) Modify the sensor orientation in camera characteristics along with any 3A regions + // in capture requests/results to account for thea physical rotation. The former + // is somewhat tricky as it assumes that camera clients always check for the current + // value by retrieving the camera characteristics from the camera device. + return getNeedCropRotateScale(mContext, packageName, + mTaskStackListener.getFrontTaskInfo(packageName), sensorOrientation, + lensFacing); + } + @Override public void pingForUserUpdate() { if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { @@ -350,6 +523,12 @@ public class CameraServiceProxy extends SystemService publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy); publishLocalService(CameraServiceProxy.class, this); + try { + ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register task stack listener!"); + } + CameraStatsJobService.schedule(mContext); } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 781bad7aa183..874e9a6deda6 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -121,6 +121,17 @@ class HostClipboardMonitor implements Runnable { return false; } + private void closePipe() { + try { + final RandomAccessFile pipe = mPipe; + mPipe = null; + if (pipe != null) { + pipe.close(); + } + } catch (IOException ignore) { + } + } + public HostClipboardMonitor(HostClipboardCallback cb) { mHostClipboardCallback = cb; } @@ -142,10 +153,7 @@ class HostClipboardMonitor implements Runnable { mHostClipboardCallback.onHostClipboardUpdated( new String(receivedData)); } catch (IOException e) { - try { - mPipe.close(); - } catch (IOException ee) {} - mPipe = null; + closePipe(); } catch (InterruptedException e) {} } } @@ -1046,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( @@ -1077,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/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index f3d201289f0e..6ea84ce35002 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -18,12 +18,14 @@ package com.android.server.connectivity; import static android.util.TimeUtils.NANOS_PER_MS; +import android.annotation.Nullable; import android.content.Context; import android.net.ConnectivityManager; import android.net.INetdEventCallback; import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.net.metrics.ConnectStats; import android.net.metrics.DnsEvent; import android.net.metrics.INetdEventListener; @@ -98,6 +100,7 @@ public class NetdEventListenerService extends INetdEventListener.Stub { private final TokenBucket mConnectTb = new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT); + final TransportForNetIdNetworkCallback mCallback = new TransportForNetIdNetworkCallback(); /** * There are only 3 possible callbacks. @@ -158,6 +161,9 @@ public class NetdEventListenerService extends INetdEventListener.Stub { public NetdEventListenerService(ConnectivityManager cm) { // We are started when boot is complete, so ConnectivityService should already be running. mCm = cm; + // Clear all capabilities to listen all networks. + mCm.registerNetworkCallback(new NetworkRequest.Builder().clearCapabilities().build(), + mCallback); } private static long projectSnapshotTime(long timeMs) { @@ -389,18 +395,13 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } private long getTransports(int netId) { - // TODO: directly query ConnectivityService instead of going through Binder interface. - NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId)); + final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId); if (nc == null) { return 0; } return BitUtils.packBits(nc.getTransportTypes()); } - private static void maybeLog(String s, Object... args) { - if (DBG) Log.d(TAG, String.format(s, args)); - } - /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */ static class NetworkMetricsSnapshot { @@ -428,4 +429,29 @@ public class NetdEventListenerService extends INetdEventListener.Stub { return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString()); } } + + private class TransportForNetIdNetworkCallback extends ConnectivityManager.NetworkCallback { + private final SparseArray<NetworkCapabilities> mCapabilities = new SparseArray<>(); + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { + synchronized (mCapabilities) { + mCapabilities.put(network.getNetId(), nc); + } + } + + @Override + public void onLost(Network network) { + synchronized (mCapabilities) { + mCapabilities.remove(network.getNetId()); + } + } + + @Nullable + public NetworkCapabilities getNetworkCapabilities(int netId) { + synchronized (mCapabilities) { + return mCapabilities.get(netId); + } + } + } } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 103ab957f312..97df5bff4946 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -49,6 +49,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -576,6 +577,28 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } } + /** + * Notify the NetworkAgent that the network is successfully connected. + */ + public void onNetworkCreated() { + try { + networkAgent.onNetworkCreated(); + } catch (RemoteException e) { + Log.e(TAG, "Error sending network created event", e); + } + } + + /** + * Notify the NetworkAgent that the network is disconnected and destroyed. + */ + public void onNetworkDisconnected() { + try { + networkAgent.onNetworkDisconnected(); + } catch (RemoteException e) { + Log.e(TAG, "Error sending network disconnected event", e); + } + } + // TODO: consider moving out of NetworkAgentInfo into its own class private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub { private final Handler mHandler; @@ -633,7 +656,13 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { @Override public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session, final EpsBearerQosSessionAttributes attributes) { - mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes); + mQosCallbackTracker.sendEventEpsQosSessionAvailable(qosCallbackId, session, attributes); + } + + @Override + public void sendNrQosSessionAvailable(final int qosCallbackId, final QosSession session, + final NrQosSessionAttributes attributes) { + mQosCallbackTracker.sendEventNrQosSessionAvailable(qosCallbackId, session, attributes); } @Override 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/OsCompat.java b/services/core/java/com/android/server/connectivity/OsCompat.java new file mode 100644 index 000000000000..57e3dcdf0d73 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/OsCompat.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.system.ErrnoException; +import android.system.Os; + +import java.io.FileDescriptor; + +/** + * Compatibility utility for android.system.Os core platform APIs. + * + * Connectivity has access to such APIs, but they are not part of the module_current stubs yet + * (only core_current). Most stable core platform APIs are included manually in the connectivity + * build rules, but because Os is also part of the base java SDK that is earlier on the + * classpath, the extra core platform APIs are not seen. + * + * TODO (b/157639992, b/183097033): remove as soon as core_current is part of system_server_current + * @hide + */ +public class OsCompat { + // This value should be correct on all architectures supported by Android, but hardcoding ioctl + // numbers should be avoided. + /** + * @see android.system.OsConstants#TIOCOUTQ + */ + public static final int TIOCOUTQ = 0x5411; + + /** + * @see android.system.Os#getsockoptInt(FileDescriptor, int, int) + */ + public static int getsockoptInt(FileDescriptor fd, int level, int option) throws + ErrnoException { + try { + return (int) Os.class.getMethod( + "getsockoptInt", FileDescriptor.class, int.class, int.class) + .invoke(null, fd, level, option); + } catch (ReflectiveOperationException e) { + if (e.getCause() instanceof ErrnoException) { + throw (ErrnoException) e.getCause(); + } + throw new IllegalStateException("Error calling getsockoptInt", e); + } + } + + /** + * @see android.system.Os#ioctlInt(FileDescriptor, int) + */ + public static int ioctlInt(FileDescriptor fd, int cmd) throws + ErrnoException { + try { + return (int) Os.class.getMethod( + "ioctlInt", FileDescriptor.class, int.class).invoke(null, fd, cmd); + } catch (ReflectiveOperationException e) { + if (e.getCause() instanceof ErrnoException) { + throw (ErrnoException) e.getCause(); + } + throw new IllegalStateException("Error calling ioctlInt", e); + } + } +} diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 488677ac1b59..37116797d8d7 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -271,6 +271,13 @@ public class PermissionMonitor { return mApps.containsKey(uid); } + /** + * Returns whether the given uid has permission to use restricted networks. + */ + public synchronized boolean hasRestrictedNetworksPermission(int uid) { + return Boolean.TRUE.equals(mApps.get(uid)); + } + private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) { List<Integer> network = new ArrayList<>(); List<Integer> system = new ArrayList<>(); diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java index 0f5400d0f8e6..534dbe7699a7 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java @@ -27,6 +27,7 @@ import android.net.QosSession; import android.os.IBinder; import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import java.util.Objects; @@ -146,13 +147,23 @@ class QosCallbackAgentConnection implements IBinder.DeathRecipient { mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId); } - void sendEventQosSessionAvailable(final QosSession session, + void sendEventEpsQosSessionAvailable(final QosSession session, final EpsBearerQosSessionAttributes attributes) { try { - if (DBG) log("sendEventQosSessionAvailable: sending..."); + if (DBG) log("sendEventEpsQosSessionAvailable: sending..."); mCallback.onQosEpsBearerSessionAvailable(session, attributes); } catch (final RemoteException e) { - loge("sendEventQosSessionAvailable: remote exception", e); + loge("sendEventEpsQosSessionAvailable: remote exception", e); + } + } + + void sendEventNrQosSessionAvailable(final QosSession session, + final NrQosSessionAttributes attributes) { + try { + if (DBG) log("sendEventNrQosSessionAvailable: sending..."); + mCallback.onNrQosSessionAvailable(session, attributes); + } catch (final RemoteException e) { + loge("sendEventNrQosSessionAvailable: remote exception", e); } } diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java index 8bda5323e4f8..b6ab47b276e3 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import com.android.net.module.util.CollectionUtils; @@ -179,17 +180,31 @@ public class QosCallbackTracker { } /** - * Called when the NetworkAgent sends the qos session available event + * Called when the NetworkAgent sends the qos session available event for EPS * * @param qosCallbackId the callback id that the qos session is now available to * @param session the qos session that is now available * @param attributes the qos attributes that are now available on the qos session */ - public void sendEventQosSessionAvailable(final int qosCallbackId, + public void sendEventEpsQosSessionAvailable(final int qosCallbackId, final QosSession session, final EpsBearerQosSessionAttributes attributes) { - runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ", - ac -> ac.sendEventQosSessionAvailable(session, attributes)); + runOnAgentConnection(qosCallbackId, "sendEventEpsQosSessionAvailable: ", + ac -> ac.sendEventEpsQosSessionAvailable(session, attributes)); + } + + /** + * Called when the NetworkAgent sends the qos session available event for NR + * + * @param qosCallbackId the callback id that the qos session is now available to + * @param session the qos session that is now available + * @param attributes the qos attributes that are now available on the qos session + */ + public void sendEventNrQosSessionAvailable(final int qosCallbackId, + final QosSession session, + final NrQosSessionAttributes attributes) { + runOnAgentConnection(qosCallbackId, "sendEventNrQosSessionAvailable: ", + ac -> ac.sendEventNrQosSessionAvailable(session, attributes)); } /** diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java index c480594b8c60..73f34751731e 100644 --- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java +++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java @@ -27,7 +27,8 @@ import static android.system.OsConstants.IPPROTO_IP; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IP_TOS; import static android.system.OsConstants.IP_TTL; -import static android.system.OsConstants.TIOCOUTQ; + +import static com.android.server.connectivity.OsCompat.TIOCOUTQ; import android.annotation.NonNull; import android.net.InvalidPacketException; @@ -175,10 +176,10 @@ public class TcpKeepaliveController { } // Query write sequence number from SEND_QUEUE. Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE); - tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); + tcpDetails.seq = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); // Query read sequence number from RECV_QUEUE. Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE); - tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); + tcpDetails.ack = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ); // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode. Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE); // Finally, check if socket is still idle. TODO : this check needs to move to @@ -198,9 +199,9 @@ public class TcpKeepaliveController { tcpDetails.rcvWndScale = trw.rcvWndScale; if (tcpDetails.srcAddress.length == 4 /* V4 address length */) { // Query TOS. - tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS); + tcpDetails.tos = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TOS); // Query TTL. - tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL); + tcpDetails.ttl = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TTL); } } catch (ErrnoException e) { Log.e(TAG, "Exception reading TCP state from socket", e); @@ -305,7 +306,7 @@ public class TcpKeepaliveController { private static boolean isReceiveQueueEmpty(FileDescriptor fd) throws ErrnoException { - final int result = Os.ioctlInt(fd, SIOCINQ); + final int result = OsCompat.ioctlInt(fd, SIOCINQ); if (result != 0) { Log.e(TAG, "Read queue has data"); return false; @@ -315,7 +316,7 @@ public class TcpKeepaliveController { private static boolean isSendQueueEmpty(FileDescriptor fd) throws ErrnoException { - final int result = Os.ioctlInt(fd, SIOCOUTQ); + final int result = OsCompat.ioctlInt(fd, SIOCOUTQ); if (result != 0) { Log.e(TAG, "Write queue has data"); return false; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 64173bbcc500..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; @@ -1248,8 +1250,9 @@ public class Vpn { mLegacyState = LegacyVpnInfo.STATE_CONNECTING; updateState(DetailedState.CONNECTING, "agentConnect"); - NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder().build(); - networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown; + final NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder() + .setBypassableVpn(mConfig.allowBypass && !mLockdown) + .build(); capsBuilder.setOwnerUid(mOwnerUID); capsBuilder.setAdministratorUids(new int[] {mOwnerUID}); @@ -1858,22 +1861,13 @@ public class Vpn { /** * Updates underlying network set. */ - public synchronized boolean setUnderlyingNetworks(Network[] networks) { + public synchronized boolean setUnderlyingNetworks(@Nullable Network[] networks) { if (!isCallerEstablishedOwnerLocked()) { return false; } - if (networks == null) { - mConfig.underlyingNetworks = null; - } else { - mConfig.underlyingNetworks = new Network[networks.length]; - for (int i = 0; i < networks.length; ++i) { - if (networks[i] == null) { - mConfig.underlyingNetworks[i] = null; - } else { - mConfig.underlyingNetworks[i] = new Network(networks[i].getNetId()); - } - } - } + // Make defensive copy since the content of array might be altered by the caller. + mConfig.underlyingNetworks = + (networks != null) ? Arrays.copyOf(networks, networks.length) : null; mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null) ? Arrays.asList(mConfig.underlyingNetworks) : null); return true; diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index ac7e01ed4d72..1786a51e0f7f 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -3978,6 +3978,9 @@ public class SyncManager { * @return true if the provided key is used by the SyncManager in scheduling the sync. */ private static boolean isSyncSetting(String key) { + if (key == null) { + return false; + } if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) { return true; } diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java new file mode 100644 index 000000000000..8ce7b66e6c7e --- /dev/null +++ b/services/core/java/com/android/server/display/BrightnessSetting.java @@ -0,0 +1,173 @@ +/* + * 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.display; + +import android.annotation.NonNull; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Slog; +import android.view.Display; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Saves brightness to a persistent data store, enabling each logical display to have its own + * brightness. + */ +public class BrightnessSetting { + private static final String TAG = "BrightnessSetting"; + + private static final int MSG_BRIGHTNESS_CHANGED = 1; + private static final Uri BRIGHTNESS_FLOAT_URI = + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT); + private final PersistentDataStore mPersistentDataStore; + + private final boolean mIsDefaultDisplay; + private final Context mContext; + private final LogicalDisplay mLogicalDisplay; + private final Object mLock = new Object(); + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_BRIGHTNESS_CHANGED) { + float brightnessVal = Float.intBitsToFloat(msg.arg1); + notifyListeners(brightnessVal); + } + } + }; + + private final ContentObserver mBrightnessSettingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + if (selfChange) { + return; + } + if (BRIGHTNESS_FLOAT_URI.equals(uri)) { + float brightness = getScreenBrightnessSettingFloat(); + setBrightness(brightness, true); + } + } + }; + + private final CopyOnWriteArrayList<BrightnessSettingListener> mListeners = + new CopyOnWriteArrayList<BrightnessSettingListener>(); + + private float mBrightness; + + BrightnessSetting(@NonNull PersistentDataStore persistentDataStore, + @NonNull LogicalDisplay logicalDisplay, + @NonNull Context context) { + mPersistentDataStore = persistentDataStore; + mLogicalDisplay = logicalDisplay; + mContext = context; + mIsDefaultDisplay = mLogicalDisplay.getDisplayIdLocked() == Display.DEFAULT_DISPLAY; + mBrightness = mPersistentDataStore.getBrightness( + mLogicalDisplay.getPrimaryDisplayDeviceLocked()); + if (mIsDefaultDisplay) { + mContext.getContentResolver().registerContentObserver(BRIGHTNESS_FLOAT_URI, + false, mBrightnessSettingsObserver); + } + } + + /** + * Returns the brightness from the brightness setting + * + * @return brightness for the current display + */ + public float getBrightness() { + return mBrightness; + } + + /** + * Registers listener for brightness setting change events. + */ + public void registerListener(BrightnessSettingListener l) { + if (!mListeners.contains(l)) { + mListeners.add(l); + } + } + + /** + * Unregisters listener for brightness setting change events. + * + * @param l listener + */ + public void unregisterListener(BrightnessSettingListener l) { + mListeners.remove(l); + } + + void setBrightness(float brightness) { + setBrightness(brightness, false); + } + + private void setBrightness(float brightness, boolean isFromSystemSetting) { + if (brightness == mBrightness) { + return; + } + if (Float.isNaN(brightness)) { + Slog.w(TAG, "Attempting to set invalid brightness"); + return; + } + synchronized (mLock) { + + mBrightness = brightness; + + // If it didn't come from us + if (mIsDefaultDisplay && !isFromSystemSetting) { + Settings.System.putFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightness, + UserHandle.USER_CURRENT); + } + mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(), + brightness); + int toSend = Float.floatToIntBits(mBrightness); + Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED, toSend, 0); + mHandler.sendMessage(msg); + } + } + + private float getScreenBrightnessSettingFloat() { + return Settings.System.getFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FLOAT, PowerManager.BRIGHTNESS_INVALID_FLOAT, + UserHandle.USER_CURRENT); + } + + private void notifyListeners(float brightness) { + for (BrightnessSettingListener l : mListeners) { + l.onBrightnessChanged(brightness); + } + } + + /** + * Listener for changes to system brightness. + */ + public interface BrightnessSettingListener { + + /** + * Notify that the brightness has changed. + */ + void onBrightnessChanged(float brightness); + } +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index c0109065fea4..e38d91ca9d33 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1955,9 +1955,12 @@ public final class DisplayManagerService extends SystemService { if (mBrightnessTracker == null) { mBrightnessTracker = new BrightnessTracker(mContext, null); } + + final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore, + display, mContext); final DisplayPowerController displayPowerController = new DisplayPowerController( mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, - mDisplayBlanker, display, mBrightnessTracker); + mDisplayBlanker, display, mBrightnessTracker, brightnessSetting); mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); } @@ -2662,6 +2665,48 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call + public void setBrightness(int displayId, float brightness) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS, + "Permission required to set the display's brightness"); + if (!isValidBrightness(brightness)) { + Slog.w(TAG, "Attempted to set invalid brightness" + brightness); + return; + } + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + if (dpc != null) { + dpc.putScreenBrightnessSetting(brightness); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public float getBrightness(int displayId) { + float brightness = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS, + "Permission required to set the display's brightness"); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); + if (dpc != null) { + brightness = dpc.getScreenBrightnessSetting(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + return brightness; + } + + @Override // Binder call public void setTemporaryAutoBrightnessAdjustment(float adjustment) { mContext.enforceCallingOrSelfPermission( Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS, @@ -2809,6 +2854,13 @@ public final class DisplayManagerService extends SystemService { Slog.w(TAG, msg); return false; } + + } + + private static boolean isValidBrightness(float brightness) { + return !Float.isNaN(brightness) + && (brightness >= PowerManager.BRIGHTNESS_MIN) + && (brightness <= PowerManager.BRIGHTNESS_MAX); } private final class LocalService extends DisplayManagerInternal { diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index d1d04966bdec..48edb73ac81d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -16,13 +16,11 @@ package com.android.server.display; -import android.Manifest; import android.content.Context; import android.content.Intent; -import android.os.Binder; +import android.hardware.display.DisplayManager; import android.os.ShellCommand; -import android.os.UserHandle; -import android.provider.Settings; +import android.view.Display; import java.io.PrintWriter; @@ -111,17 +109,8 @@ class DisplayManagerShellCommand extends ShellCommand { } final Context context = mService.getContext(); - context.enforceCallingOrSelfPermission( - Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS, - "Permission required to set the display's brightness"); - final long token = Binder.clearCallingIdentity(); - try { - Settings.System.putFloatForUser(context.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightness, - UserHandle.USER_CURRENT); - } finally { - Binder.restoreCallingIdentity(token); - } + final DisplayManager dm = context.getSystemService(DisplayManager.class); + dm.setBrightness(Display.DEFAULT_DISPLAY, brightness); return 0; } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 645ca7ac33e0..4bbd33817d28 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -263,6 +263,8 @@ public class DisplayModeDirector { highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE; } + // We try to find a range of priorities which define a non-empty set of allowed display + // modes. Each time we fail we increase the lowest priority. while (lowestConsideredPriority <= highestConsideredPriority) { summarizeVotes( votes, lowestConsideredPriority, highestConsideredPriority, primarySummary); @@ -343,8 +345,15 @@ public class DisplayModeDirector { } if (baseModeId == INVALID_DISPLAY_MODE_ID) { - throw new IllegalStateException("Can't select a base display mode for display " - + displayId + ". The votes are " + mVotesByDisplay.valueAt(displayId)); + Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling" + + " back to the default mode. Display = " + displayId + ", votes = " + votes + + ", supported modes = " + Arrays.toString(modes)); + + float fps = defaultMode.getRefreshRate(); + return new DesiredDisplayModeSpecs(defaultMode.getModeId(), + /*allowGroupSwitching */ false, + new RefreshRateRange(fps, fps), + new RefreshRateRange(fps, fps)); } if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 7110d3e6a7c1..56ad01b3c41b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -122,6 +122,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7; private static final int MSG_IGNORE_PROXIMITY = 8; private static final int MSG_STOP = 9; + private static final int MSG_UPDATE_BRIGHTNESS = 10; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -355,13 +356,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final HighBrightnessModeController mHbmController; + private final BrightnessSetting mBrightnessSetting; + // A record of state for skipping brightness ramps. private int mSkipRampState = RAMP_STATE_SKIP_NONE; // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL. private float mInitialAutoBrightness; - // The controller for the automatic brightness level. private AutomaticBrightnessController mAutomaticBrightnessController; @@ -410,6 +412,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private ObjectAnimator mColorFadeOnAnimator; private ObjectAnimator mColorFadeOffAnimator; private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; + private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener; // True if this DisplayPowerController has been stopped and should no longer be running. private boolean mStopped; @@ -420,7 +423,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public DisplayPowerController(Context context, DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, - BrightnessTracker brightnessTracker) { + BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting) { mLogicalDisplay = logicalDisplay; mDisplayId = mLogicalDisplay.getDisplayIdLocked(); mHandler = new DisplayControllerHandler(handler.getLooper()); @@ -439,7 +442,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mContext = context; mBrightnessTracker = brightnessTracker; - + mBrightnessSetting = brightnessSetting; PowerManager pm = context.getSystemService(PowerManager.class); final Resources resources = context.getResources(); @@ -785,6 +788,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.stop(); } + if (mBrightnessSetting != null) { + mBrightnessSetting.unregisterListener(mBrightnessSettingListener); + } + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); } } @@ -831,10 +838,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (brightness >= PowerManager.BRIGHTNESS_MIN) { mBrightnessTracker.start(brightness); } + mBrightnessSettingListener = brightnessValue -> { + Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue); + mHandler.sendMessage(msg); + }; - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT), - false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); + mBrightnessSetting.registerListener(mBrightnessSettingListener); mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT), false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); @@ -1150,7 +1159,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // before applying the low power or dim transformations so that the slider // accurately represents the full possible range, even if they range changes what // it means in absolute terms. - putScreenBrightnessSetting(brightnessState); + putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true); } // Apply dimming by at least some minimum amount when user activity @@ -1804,7 +1813,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void handleSettingsChange(boolean userSwitch) { mPendingScreenBrightnessSetting = getScreenBrightnessSetting(); - if (userSwitch) { // Don't treat user switches as user initiated change. mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting; @@ -1825,10 +1833,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj); } - private float getScreenBrightnessSetting() { - final float brightness = Settings.System.getFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, mScreenBrightnessDefault, - UserHandle.USER_CURRENT); + float getScreenBrightnessSetting() { + float brightness = mBrightnessSetting.getBrightness(); + if (Float.isNaN(brightness)) { + brightness = mScreenBrightnessDefault; + } return clampAbsoluteBrightness(brightness); } @@ -1839,13 +1848,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return clampScreenBrightnessForVr(brightnessFloat); } - private void putScreenBrightnessSetting(float brightnessValue) { - if (mDisplayId == Display.DEFAULT_DISPLAY) { + void putScreenBrightnessSetting(float brightnessValue) { + putScreenBrightnessSetting(brightnessValue, false); + } + + private void putScreenBrightnessSetting(float brightnessValue, boolean updateCurrent) { + if (updateCurrent) { mCurrentScreenBrightnessSetting = brightnessValue; - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessValue, - UserHandle.USER_CURRENT); } + mBrightnessSetting.setBrightness(brightnessValue); } private void putAutoBrightnessAdjustmentSetting(float adjustment) { @@ -2175,7 +2186,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } break; case MSG_CONFIGURE_BRIGHTNESS: - mBrightnessConfiguration = (BrightnessConfiguration)msg.obj; + mBrightnessConfiguration = (BrightnessConfiguration) msg.obj; updatePowerState(); break; @@ -2197,6 +2208,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call case MSG_STOP: cleanupHandlerThreadAfterStop(); break; + + case MSG_UPDATE_BRIGHTNESS: + if (mStopped) { + return; + } + handleSettingsChange(false /*userSwitch*/); } } } diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index a62642b1f842..c90ddf48a091 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -62,6 +62,7 @@ import java.util.Objects; * <display-states> * <display unique-id="XXXXXXX"> * <color-mode>0</color-mode> + * <brightness-value>0</brightness-value> * </display> * </display-states> * <stable-device-values> @@ -82,7 +83,7 @@ import java.util.Objects; * TODO: refactor this to extract common code shared with the input manager's data store */ final class PersistentDataStore { - static final String TAG = "DisplayManager"; + static final String TAG = "DisplayManager.PersistentDataStore"; private static final String TAG_DISPLAY_MANAGER_STATE = "display-manager-state"; @@ -95,6 +96,7 @@ final class PersistentDataStore { private static final String TAG_DISPLAY_STATES = "display-states"; private static final String TAG_DISPLAY = "display"; private static final String TAG_COLOR_MODE = "color-mode"; + private static final String TAG_BRIGHTNESS_VALUE = "brightness-value"; private static final String ATTR_UNIQUE_ID = "unique-id"; private static final String TAG_STABLE_DEVICE_VALUES = "stable-device-values"; @@ -255,6 +257,30 @@ final class PersistentDataStore { return false; } + public float getBrightness(DisplayDevice device) { + if (device == null || !device.hasStableUniqueId()) { + return Float.NaN; + } + final DisplayState state = getDisplayState(device.getUniqueId(), false); + if (state == null) { + return Float.NaN; + } + return state.getBrightness(); + } + + public boolean setBrightness(DisplayDevice displayDevice, float brightness) { + final String displayDeviceUniqueId = displayDevice.getUniqueId(); + if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) { + return false; + } + final DisplayState state = getDisplayState(displayDeviceUniqueId, true); + if (state.setBrightness(brightness)) { + setDirty(); + return true; + } + return false; + } + public Point getStableDisplaySize() { loadIfNeeded(); return mStableDeviceValues.getDisplaySize(); @@ -473,6 +499,7 @@ final class PersistentDataStore { private static final class DisplayState { private int mColorMode; + private float mBrightness; public boolean setColorMode(int colorMode) { if (colorMode == mColorMode) { @@ -486,14 +513,33 @@ final class PersistentDataStore { return mColorMode; } + public boolean setBrightness(float brightness) { + if (brightness == mBrightness) { + return false; + } + mBrightness = brightness; + return true; + } + + public float getBrightness() { + return mBrightness; + } + + public void loadFromXml(TypedXmlPullParser parser) throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { - if (parser.getName().equals(TAG_COLOR_MODE)) { - String value = parser.nextText(); - mColorMode = Integer.parseInt(value); + switch (parser.getName()) { + case TAG_COLOR_MODE: + String value = parser.nextText(); + mColorMode = Integer.parseInt(value); + break; + case TAG_BRIGHTNESS_VALUE: + String brightness = parser.nextText(); + mBrightness = Float.parseFloat(brightness); + break; } } } @@ -502,10 +548,15 @@ final class PersistentDataStore { serializer.startTag(null, TAG_COLOR_MODE); serializer.text(Integer.toString(mColorMode)); serializer.endTag(null, TAG_COLOR_MODE); + serializer.startTag(null, TAG_BRIGHTNESS_VALUE); + serializer.text(Float.toString(mBrightness)); + serializer.endTag(null, TAG_BRIGHTNESS_VALUE); + } public void dump(final PrintWriter pw, final String prefix) { pw.println(prefix + "ColorMode=" + mColorMode); + pw.println(prefix + "BrightnessValue=" + mBrightness); } } diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 4f95d27a085e..75181307dc42 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -41,11 +41,11 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; -import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; /** * Manages set of updatable font files. @@ -109,6 +109,7 @@ final class UpdatableFontDir { private final FsverityUtil mFsverityUtil; private final File mConfigFile; private final File mTmpConfigFile; + private final Supplier<Long> mCurrentTimeSupplier; private long mLastModifiedMillis; private int mConfigVersion = 1; @@ -128,18 +129,20 @@ final class UpdatableFontDir { UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, FsverityUtil fsverityUtil) { - this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE)); + this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE), + () -> System.currentTimeMillis()); } // For unit testing UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, - FsverityUtil fsverityUtil, File configFile) { + FsverityUtil fsverityUtil, File configFile, Supplier<Long> currentTimeSupplier) { mFilesDir = filesDir; mPreinstalledFontDirs = preinstalledFontDirs; mParser = parser; mFsverityUtil = fsverityUtil; mConfigFile = configFile; mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp"); + mCurrentTimeSupplier = currentTimeSupplier; } /* package */ void loadFontFileMap() { @@ -209,7 +212,7 @@ final class UpdatableFontDir { FileUtils.deleteContents(mFilesDir); mFontFamilyMap.clear(); - mLastModifiedMillis = System.currentTimeMillis(); + mLastModifiedMillis = mCurrentTimeSupplier.get(); try (FileOutputStream fos = new FileOutputStream(mConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { @@ -245,7 +248,7 @@ final class UpdatableFontDir { } // Write config file. - mLastModifiedMillis = Instant.now().getEpochSecond(); + mLastModifiedMillis = mCurrentTimeSupplier.get(); try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index d0e7e459e7f2..58308d8f1343 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -250,7 +250,19 @@ final class Constants { @Retention(RetentionPolicy.SOURCE) @IntDef({ - ABORT_NO_ERROR, + NOT_HANDLED, + HANDLED, + ABORT_UNRECOGNIZED_OPCODE, + ABORT_NOT_IN_CORRECT_MODE, + ABORT_CANNOT_PROVIDE_SOURCE, + ABORT_INVALID_OPERAND, + ABORT_REFUSED, + ABORT_UNABLE_TO_DETERMINE, + }) + public @interface HandleMessageResult {} + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ ABORT_UNRECOGNIZED_OPCODE, ABORT_NOT_IN_CORRECT_MODE, ABORT_CANNOT_PROVIDE_SOURCE, @@ -260,8 +272,11 @@ final class Constants { }) public @interface AbortReason {} - // Internal abort error code. It's the same as success. - static final int ABORT_NO_ERROR = -1; + // Indicates that a message was not handled, but could be handled by another local device. + // If no local devices handle the message, we send <Feature Abort>[Unrecognized Opcode]. + static final int NOT_HANDLED = -2; + // Indicates that a message has been handled successfully; no feature abort needed. + static final int HANDLED = -1; // Constants related to operands of HDMI CEC commands. // Refer to CEC Table 29 in HDMI Spec v1.4b. // [Abort Reason] diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 1643ec162bc2..ad2ef2a2b665 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -565,19 +565,24 @@ final class HdmiCecController { } @ServiceThreadOnly - private void onReceiveCommand(HdmiCecMessage message) { + @VisibleForTesting + void onReceiveCommand(HdmiCecMessage message) { assertRunOnServiceThread(); - if ((isAcceptableAddress(message.getDestination()) - || !mService.isAddressAllocated()) - && mService.handleCecCommand(message)) { + if (mService.isAddressAllocated() && !isAcceptableAddress(message.getDestination())) { return; } - // Not handled message, so we will reply it with <Feature Abort>. - maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + @Constants.HandleMessageResult int messageState = mService.handleCecCommand(message); + if (messageState == Constants.NOT_HANDLED) { + // Message was not handled + maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + } else if (messageState != Constants.HANDLED) { + // Message handler wants to send a feature abort + maySendFeatureAbortCommand(message, messageState); + } } @ServiceThreadOnly - void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) { + void maySendFeatureAbortCommand(HdmiCecMessage message, @Constants.AbortReason int reason) { assertRunOnServiceThread(); // Swap the source and the destination. int src = message.getDestination(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index bdc4e66cf7f9..505e743ed9a1 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -248,11 +248,13 @@ abstract class HdmiCecLocalDevice { * @return true if consumed a message; otherwise, return false. */ @ServiceThreadOnly - boolean dispatchMessage(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int dispatchMessage(HdmiCecMessage message) { assertRunOnServiceThread(); int dest = message.getDestination(); if (dest != mAddress && dest != Constants.ADDR_BROADCAST) { - return false; + return Constants.NOT_HANDLED; } // Cache incoming message if it is included in the list of cacheable opcodes. mCecMessageCache.cacheMessage(message); @@ -260,10 +262,11 @@ abstract class HdmiCecLocalDevice { } @ServiceThreadOnly - protected final boolean onMessage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected final int onMessage(HdmiCecMessage message) { assertRunOnServiceThread(); if (dispatchMessageToAction(message)) { - return true; + return Constants.HANDLED; } switch (message.getOpcode()) { case Constants.MESSAGE_ACTIVE_SOURCE: @@ -357,7 +360,7 @@ abstract class HdmiCecLocalDevice { case Constants.MESSAGE_GIVE_FEATURES: return handleGiveFeatures(message); default: - return false; + return Constants.NOT_HANDLED; } } @@ -375,7 +378,8 @@ abstract class HdmiCecLocalDevice { } @ServiceThreadOnly - protected boolean handleGivePhysicalAddress(@Nullable SendMessageCallback callback) { + @Constants.HandleMessageResult + protected int handleGivePhysicalAddress(@Nullable SendMessageCallback callback) { assertRunOnServiceThread(); int physicalAddress = mService.getPhysicalAddress(); @@ -383,76 +387,83 @@ abstract class HdmiCecLocalDevice { HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, physicalAddress, mDeviceType); mService.sendCecCommand(cecMessage, callback); - return true; + return Constants.HANDLED; } @ServiceThreadOnly - protected boolean handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) { + @Constants.HandleMessageResult + protected int handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) { assertRunOnServiceThread(); int vendorId = mService.getVendorId(); HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, vendorId); mService.sendCecCommand(cecMessage, callback); - return true; + return Constants.HANDLED; } @ServiceThreadOnly - protected boolean handleGetCecVersion(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGetCecVersion(HdmiCecMessage message) { assertRunOnServiceThread(); int version = mService.getCecVersion(); HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion( message.getDestination(), message.getSource(), version); mService.sendCecCommand(cecMessage); - return true; + return Constants.HANDLED; } @ServiceThreadOnly - private boolean handleCecVersion() { + @Constants.HandleMessageResult + protected int handleCecVersion() { assertRunOnServiceThread(); // Return true to avoid <Feature Abort> responses. Cec Version is tracked in HdmiCecNetwork. - return true; + return Constants.HANDLED; } @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleInactiveSource(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleInactiveSource(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestActiveSource(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleGetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); - // 'return false' will cause to reply with <Feature Abort>. - return false; + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleSetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); Slog.w(TAG, "Only Playback device can handle <Set Menu Language>:" + message.toString()); - // 'return false' will cause to reply with <Feature Abort>. - return false; + return Constants.NOT_HANDLED; } @ServiceThreadOnly - protected boolean handleGiveOsdName(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveOsdName(HdmiCecMessage message) { assertRunOnServiceThread(); // Note that since this method is called after logical address allocation is done, // mDeviceInfo should not be null. buildAndSendSetOsdName(message.getSource()); - return true; + return Constants.HANDLED; } protected void buildAndSendSetOsdName(int dest) { @@ -475,18 +486,21 @@ abstract class HdmiCecLocalDevice { // Audio System device with no Playback device type // needs to refactor this function if it's also a switch - protected boolean handleRoutingChange(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } // Audio System device with no Playback device type // needs to refactor this function if it's also a switch - protected boolean handleRoutingInformation(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRoutingInformation(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @CallSuper - protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportPhysicalAddress(HdmiCecMessage message) { // <Report Physical Address> is also handled in HdmiCecNetwork to update the local network // state @@ -495,7 +509,7 @@ abstract class HdmiCecLocalDevice { // Ignore if [Device Discovery Action] is going on. if (hasAction(DeviceDiscoveryAction.class)) { Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); - return true; + return Constants.HANDLED; } HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address); @@ -506,63 +520,77 @@ abstract class HdmiCecLocalDevice { HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address)); } - return true; + return Constants.HANDLED; } - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSystemAudioModeRequest(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleTerminateArc(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTerminateArc(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleInitiateArc(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleInitiateArc(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRequestArcInitiate(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestArcInitiate(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRequestArcTermination(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestArcTermination(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportArcInitiate(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportArcInitiate(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportArcTermination(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportArcTermination(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportAudioStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportAudioStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleGiveAudioStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleGiveAudioStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportShortAudioDescriptor(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleReportShortAudioDescriptor(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @Constants.RcProfile @@ -572,13 +600,14 @@ abstract class HdmiCecLocalDevice { protected abstract List<Integer> getDeviceFeatures(); - protected boolean handleGiveFeatures(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveFeatures(HdmiCecMessage message) { if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) { - return false; + return Constants.ABORT_UNRECOGNIZED_OPCODE; } reportFeatures(); - return true; + return Constants.HANDLED; } protected void reportFeatures() { @@ -598,32 +627,34 @@ abstract class HdmiCecLocalDevice { } @ServiceThreadOnly - protected boolean handleStandby(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleStandby(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #12 if (mService.isControlEnabled() && !mService.isProhibitMode() && mService.isPowerOnOrTransient()) { mService.standby(); - return true; + return Constants.HANDLED; } - return false; + return Constants.ABORT_NOT_IN_CORRECT_MODE; } @ServiceThreadOnly - protected boolean handleUserControlPressed(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleUserControlPressed(HdmiCecMessage message) { assertRunOnServiceThread(); mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) { mService.standby(); - return true; + return Constants.HANDLED; } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) { mService.wakeUp(); - return true; + return Constants.HANDLED; } else if (mService.getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_DISABLED && isVolumeOrMuteCommand( message)) { - return false; + return Constants.ABORT_REFUSED; } if (isPowerOffOrToggleCommand(message) || isPowerOnOrToggleCommand(message)) { @@ -631,7 +662,7 @@ abstract class HdmiCecLocalDevice { // keycode to Android keycode. // Do not <Feature Abort> as the local device should already be in the correct power // state. - return true; + return Constants.HANDLED; } final long downTime = SystemClock.uptimeMillis(); @@ -653,15 +684,15 @@ abstract class HdmiCecLocalDevice { mHandler.sendMessageDelayed( Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT), FOLLOWER_SAFETY_TIMEOUT); - return true; + return Constants.HANDLED; } - mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); - return true; + return Constants.ABORT_INVALID_OPERAND; } @ServiceThreadOnly - protected boolean handleUserControlReleased() { + @Constants.HandleMessageResult + protected int handleUserControlReleased() { assertRunOnServiceThread(); mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); mLastKeyRepeatCount = 0; @@ -670,7 +701,7 @@ abstract class HdmiCecLocalDevice { injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0); mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; } - return true; + return Constants.HANDLED; } static void injectKeyEvent(long time, int action, int keycode, int repeat) { @@ -717,38 +748,45 @@ abstract class HdmiCecLocalDevice { || params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); } - protected boolean handleTextViewOn(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTextViewOn(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleImageViewOn(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleImageViewOn(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleSetStreamPath(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleSetStreamPath(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveDevicePowerStatus(HdmiCecMessage message) { mService.sendCecCommand( HdmiCecMessageBuilder.buildReportPowerStatus( mAddress, message.getSource(), mService.getPowerStatus())); - return true; + return Constants.HANDLED; } - protected boolean handleMenuRequest(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleMenuRequest(HdmiCecMessage message) { // Always report menu active to receive Remote Control. mService.sendCecCommand( HdmiCecMessageBuilder.buildReportMenuStatus( mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED)); - return true; + return Constants.HANDLED; } - protected boolean handleMenuStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleMenuStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleVendorCommand(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleVendorCommand(HdmiCecMessage message) { if (!mService.invokeVendorCommandListenersOnReceived( mDeviceType, message.getSource(), @@ -757,57 +795,64 @@ abstract class HdmiCecLocalDevice { false)) { // Vendor command listener may not have been registered yet. Respond with // <Feature Abort> [Refused] so that the sender can try again later. - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; + return Constants.HANDLED; } - protected boolean handleVendorCommandWithId(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleVendorCommandWithId(HdmiCecMessage message) { byte[] params = message.getParams(); int vendorId = HdmiUtils.threeBytesToInt(params); if (vendorId == mService.getVendorId()) { if (!mService.invokeVendorCommandListenersOnReceived( mDeviceType, message.getSource(), message.getDestination(), params, true)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } } else if (message.getDestination() != Constants.ADDR_BROADCAST && message.getSource() != Constants.ADDR_UNREGISTERED) { Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>"); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + return Constants.ABORT_UNRECOGNIZED_OPCODE; } else { Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); } - return true; + return Constants.HANDLED; } protected void sendStandby(int deviceId) { // Do nothing. } - protected boolean handleSetOsdName(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetOsdName(HdmiCecMessage message) { // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state - return true; + return Constants.HANDLED; } - protected boolean handleRecordTvScreen(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRecordTvScreen(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleTimerClearedStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTimerClearedStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleReportPowerStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportPowerStatus(HdmiCecMessage message) { // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state - return true; + return Constants.HANDLED; } - protected boolean handleTimerStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleTimerStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } - protected boolean handleRecordStatus(HdmiCecMessage message) { - return false; + @Constants.HandleMessageResult + protected int handleRecordStatus(HdmiCecMessage message) { + return Constants.NOT_HANDLED; } @ServiceThreadOnly diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index bf5bf8bae6fc..790c067c1300 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -316,7 +316,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); @@ -339,52 +340,56 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mDelayedMessageBuffer.removeActiveSource(); return super.handleActiveSource(message); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleInitiateArc(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement initiate arc handler HdmiLogger.debug(TAG + "Stub handleInitiateArc"); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleReportArcInitiate(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportArcInitiate(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement report arc initiate handler HdmiLogger.debug(TAG + "Stub handleReportArcInitiate"); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleReportArcTermination(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportArcTermination(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement report arc terminate handler HdmiLogger.debug(TAG + "Stub handleReportArcTermination"); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleGiveAudioStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED) { reportAudioStatus(message.getSource()); - } else { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.HANDLED; } - return true; + return Constants.ABORT_REFUSED; } @Override @ServiceThreadOnly - protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); // If the audio system is initiating the system audio mode on and TV asks the sam status at // the same time, respond with true. Since we know TV supports sam in this situation. @@ -399,52 +404,53 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mService.sendCecCommand( HdmiCecMessageBuilder.buildReportSystemAudioMode( mAddress, message.getSource(), isSystemAudioModeOnOrTurningOn)); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRequestArcInitiate(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestArcInitiate(HdmiCecMessage message) { assertRunOnServiceThread(); removeAction(ArcInitiationActionFromAvr.class); if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + return Constants.ABORT_UNRECOGNIZED_OPCODE; } else if (!isDirectConnectToTv()) { HdmiLogger.debug("AVR device is not directly connected with TV"); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + return Constants.ABORT_NOT_IN_CORRECT_MODE; } else { addAndStartAction(new ArcInitiationActionFromAvr(this)); + return Constants.HANDLED; } - return true; } @Override @ServiceThreadOnly - protected boolean handleRequestArcTermination(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestArcTermination(HdmiCecMessage message) { assertRunOnServiceThread(); if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + return Constants.ABORT_UNRECOGNIZED_OPCODE; } else if (!isArcEnabled()) { HdmiLogger.debug("ARC is not established between TV and AVR device"); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + return Constants.ABORT_NOT_IN_CORRECT_MODE; } else { removeAction(ArcTerminationActionFromAvr.class); addAndStartAction(new ArcTerminationActionFromAvr(this)); + return Constants.HANDLED; } - return true; } @ServiceThreadOnly - protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) { assertRunOnServiceThread(); HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor"); if (!isSystemAudioControlFeatureEnabled()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } if (!isSystemAudioActivated()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); - return true; + return Constants.ABORT_NOT_IN_CORRECT_MODE; } List<DeviceConfig> config = null; @@ -468,21 +474,20 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } else { AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo(); if (deviceInfo == null) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE); - return true; + return Constants.ABORT_UNABLE_TO_DETERMINE; } sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes); } if (sadBytes.length == 0) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); + return Constants.ABORT_INVALID_OPERAND; } else { mService.sendCecCommand( HdmiCecMessageBuilder.buildReportShortAudioDescriptor( mAddress, message.getSource(), sadBytes)); + return Constants.HANDLED; } - return true; } private byte[] getSupportedShortAudioDescriptors( @@ -624,7 +629,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeRequest(HdmiCecMessage message) { assertRunOnServiceThread(); boolean systemAudioStatusOn = message.getParams().length != 0; // Check if the request comes from a non-TV device. @@ -632,8 +638,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // if non-TV device tries to turn on the feature if (message.getSource() != Constants.ADDR_TV) { if (systemAudioStatusOn) { - handleSystemAudioModeOnFromNonTvDevice(message); - return true; + return handleSystemAudioModeOnFromNonTvDevice(message); } } else { // If TV request the feature on @@ -644,8 +649,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // If TV or Audio System does not support the feature, // will send abort command. if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } mService.sendCecCommand( @@ -660,7 +664,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { if (HdmiUtils.getLocalPortFromPhysicalAddress( sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress()) != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) { - return true; + return Constants.HANDLED; } HdmiDeviceInfo safeDeviceInfoByPath = mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress); @@ -668,29 +672,31 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { switchInputOnReceivingNewActivePath(sourcePhysicalAddress); } } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); if (!checkSupportAndSetSystemAudioMode( HdmiUtils.parseCommandParamSystemAudioStatus(message))) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (!checkSupportAndSetSystemAudioMode( HdmiUtils.parseCommandParamSystemAudioStatus(message))) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; + return Constants.HANDLED; } @ServiceThreadOnly @@ -948,13 +954,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { /** * Handler of System Audio Mode Request on from non TV device */ - void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) { + @Constants.HandleMessageResult + int handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) { if (!isSystemAudioControlFeatureEnabled()) { HdmiLogger.debug( "Cannot turn on" + "system audio mode " + "because the System Audio Control feature is disabled."); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return; + return Constants.ABORT_REFUSED; } // Wake up device mService.wakeUp(); @@ -967,7 +973,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mService.sendCecCommand( HdmiCecMessageBuilder.buildSetSystemAudioMode( mAddress, Constants.ADDR_BROADCAST, true)); - return; + return Constants.HANDLED; } // Check if TV supports System Audio Control. // Handle broadcasting setSystemAudioMode on or aborting message on callback. @@ -983,6 +989,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } } }); + return Constants.HANDLED; } void setTvSystemAudioModeSupport(boolean supported) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 299525207a60..10f6948f8782 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -251,7 +251,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { } @ServiceThreadOnly - protected boolean handleUserControlPressed(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleUserControlPressed(HdmiCecMessage message) { assertRunOnServiceThread(); wakeUpIfActiveSource(); return super.handleUserControlPressed(message); @@ -270,10 +271,11 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { } @ServiceThreadOnly - protected boolean handleSetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); if (!SET_MENU_LANGUAGE) { - return false; + return Constants.ABORT_UNRECOGNIZED_OPCODE; } try { @@ -283,7 +285,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // Do not switch language if the new language is the same as the current one. // This helps avoid accidental country variant switching from en_US to en_AU // due to the limitation of CEC. See the warning below. - return true; + return Constants.HANDLED; } // Don't use Locale.getAvailableLocales() since it returns a locale @@ -298,36 +300,38 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // will always be mapped to en-AU among other variants like en-US, en-GB, // an en-IN, which may not be the expected one. LocalePicker.updateLocale(localeInfo.getLocale()); - return true; + return Constants.HANDLED; } } Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language); - return false; + return Constants.ABORT_INVALID_OPERAND; } catch (UnsupportedEncodingException e) { Slog.w(TAG, "Can't handle <Set Menu Language>", e); - return false; + return Constants.ABORT_INVALID_OPERAND; } } @Override - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { // System Audio Mode only turns on/off when Audio System broadcasts on/off message. // For device with type 4 and 5, it can set system audio mode on/off // when there is another audio system device connected into the system first. if (message.getDestination() != Constants.ADDR_BROADCAST || message.getSource() != Constants.ADDR_AUDIO_SYSTEM || mService.audioSystem() != null) { - return true; + return Constants.HANDLED; } boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message); if (mService.isSystemAudioActivated() != setSystemAudioModeOn) { mService.setSystemAudioActivated(setSystemAudioModeOn); } - return true; + return Constants.HANDLED; } @Override - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { // Only directly addressed System Audio Mode Status message can change internal // system audio mode status. if (message.getDestination() == mAddress @@ -337,25 +341,27 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { mService.setSystemAudioActivated(setSystemAudioModeOn); } } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2); handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingInformation(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingInformation(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 2ed84811250e..979a1d4b0718 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -203,7 +203,8 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { } @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); @@ -215,20 +216,22 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { if (isRoutingControlFeatureEnabled()) { switchInputOnReceivingNewActivePath(physicalAddress); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); maySendActiveSource(message.getSource()); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSetStreamPath(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetStreamPath(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); // If current device is the target path, set to Active Source. @@ -242,12 +245,13 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleSetStreamPath()"); } switchInputOnReceivingNewActivePath(physicalAddress); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2); if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) { @@ -256,16 +260,16 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingChange()"); } if (!isRoutingControlFeatureEnabled()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRoutingInformation(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingInformation(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) { @@ -274,11 +278,10 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingInformation()"); } if (!isRoutingControlFeatureEnabled()) { - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } handleRoutingChangeAndInformation(physicalAddress, message); - return true; + return Constants.HANDLED; } // Method to switch Input with the new Active Path. diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 90d64339eac0..cd66a8fc7f01 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -47,6 +47,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; @@ -210,11 +211,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - boolean dispatchMessage(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int dispatchMessage(HdmiCecMessage message) { assertRunOnServiceThread(); if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived() && mStandbyHandler.handleCommand(message)) { - return true; + return Constants.HANDLED; } return super.onMessage(message); } @@ -409,7 +412,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); @@ -429,21 +433,22 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); mDelayedMessageBuffer.add(message); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleInactiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleInactiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #10 // Ignore <Inactive Source> from non-active source device. if (getActiveSource().logicalAddress != message.getSource()) { - return true; + return Constants.HANDLED; } if (isProhibitMode()) { - return true; + return Constants.HANDLED; } int portId = getPrevPortId(); if (portId != Constants.INVALID_PORT_ID) { @@ -452,10 +457,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo( message.getSource()); if (inactiveSource == null) { - return true; + return Constants.HANDLED; } if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) { - return true; + return Constants.HANDLED; } // TODO: Switch the TV freeze mode off @@ -468,29 +473,31 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRequestActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #19 if (mAddress == getActiveSource().logicalAddress) { mService.sendCecCommand( HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath())); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleGetMenuLanguage(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleGetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); if (!broadcastMenuLanguage(mService.getLanguage())) { Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); } - return true; + return Constants.HANDLED; } @ServiceThreadOnly @@ -506,7 +513,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportPhysicalAddress(HdmiCecMessage message) { super.handleReportPhysicalAddress(message); int path = HdmiUtils.twoBytesToInt(message.getParams()); int address = message.getSource(); @@ -516,19 +524,21 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { handleNewDeviceAtTheTailOfActivePath(path); } startNewDeviceAction(ActiveSource.of(address, path), type); - return true; + return Constants.HANDLED; } @Override - protected boolean handleTimerStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTimerStatus(HdmiCecMessage message) { // Do nothing. - return true; + return Constants.HANDLED; } @Override - protected boolean handleRecordStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRecordStatus(HdmiCecMessage message) { // Do nothing. - return true; + return Constants.HANDLED; } void startNewDeviceAction(ActiveSource activeSource, int deviceType) { @@ -590,7 +600,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRoutingChange(HdmiCecMessage message) { assertRunOnServiceThread(); // Seq #21 byte[] params = message.getParams(); @@ -601,27 +612,29 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { int newPath = HdmiUtils.twoBytesToInt(params, 2); addAndStartAction(new RoutingControlAction(this, newPath, true, null)); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleReportAudioStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleReportAudioStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (mService.getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_DISABLED) { - return false; + return Constants.ABORT_REFUSED; } boolean mute = HdmiUtils.isAudioStatusMute(message); int volume = HdmiUtils.getAudioStatusVolume(message); setAudioStatus(mute, volume); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleTextViewOn(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTextViewOn(HdmiCecMessage message) { assertRunOnServiceThread(); // Note that <Text View On> (and <Image View On>) command won't be handled here in @@ -634,12 +647,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (mService.isPowerStandbyOrTransient() && getAutoWakeup()) { mService.wakeUp(); } - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleImageViewOn(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleImageViewOn(HdmiCecMessage message) { assertRunOnServiceThread(); // Currently, it's the same as <Text View On>. return handleTextViewOn(message); @@ -977,7 +991,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleInitiateArc(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); if (!canStartArcUpdateAction(message.getSource(), true)) { @@ -985,13 +1000,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (avrDeviceInfo == null) { // AVR may not have been discovered yet. Delay the message processing. mDelayedMessageBuffer.add(message); - return true; + return Constants.HANDLED; } - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) { displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); } - return true; + return Constants.ABORT_REFUSED; } // In case where <Initiate Arc> is started by <Request ARC Initiation> @@ -1000,7 +1014,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, message.getSource(), true); addAndStartAction(action); - return true; + return Constants.HANDLED; } private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) { @@ -1022,11 +1036,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected boolean handleTerminateArc(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTerminateArc(HdmiCecMessage message) { assertRunOnServiceThread(); if (mService .isPowerStandbyOrTransient()) { setArcStatus(false); - return true; + return Constants.HANDLED; } // Do not check ARC configuration since the AVR might have been already removed. // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by @@ -1035,12 +1050,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, message.getSource(), false); addAndStartAction(action); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message); if (!isMessageForSystemAudio(message)) { @@ -1049,30 +1065,29 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mDelayedMessageBuffer.add(message); } else { HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return Constants.ABORT_REFUSED; } - return true; } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) { HdmiLogger.debug("Ignoring <Set System Audio Mode> message " + "because the System Audio Control feature is disabled: %s", message); - mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); - return true; + return Constants.ABORT_REFUSED; } removeAction(SystemAudioAutoInitiationAction.class); SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, message.getSource(), systemAudioStatus, null); addAndStartAction(action); - return true; + return Constants.HANDLED; } @Override @ServiceThreadOnly - protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); if (!isMessageForSystemAudio(message)) { HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message); // Ignore this message. - return true; + return Constants.HANDLED; } boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled(); boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message); @@ -1089,13 +1104,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { setSystemAudioMode(tvSystemAudioMode); } - return true; + return Constants.HANDLED; } // Seq #53 @Override @ServiceThreadOnly - protected boolean handleRecordTvScreen(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleRecordTvScreen(HdmiCecMessage message) { List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); if (!actions.isEmpty()) { // Assumes only one OneTouchRecordAction. @@ -1107,25 +1123,21 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } // The default behavior of <Record TV Screen> is replying <Feature Abort> with // "Cannot provide source". - mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE); - return true; + return Constants.ABORT_CANNOT_PROVIDE_SOURCE; } int recorderAddress = message.getSource(); byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); - int reason = startOneTouchRecord(recorderAddress, recordSource); - if (reason != Constants.ABORT_NO_ERROR) { - mService.maySendFeatureAbortCommand(message, reason); - } - return true; + return startOneTouchRecord(recorderAddress, recordSource); } @Override - protected boolean handleTimerClearedStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleTimerClearedStatus(HdmiCecMessage message) { byte[] params = message.getParams(); int timerClearedStatusData = params[0] & 0xFF; announceTimerRecordingResult(message.getSource(), timerClearedStatusData); - return true; + return Constants.HANDLED; } void announceOneTouchRecordResult(int recorderAddress, int result) { @@ -1337,6 +1349,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Seq #54 and #55 @ServiceThreadOnly + @Constants.HandleMessageResult int startOneTouchRecord(int recorderAddress, byte[] recordSource) { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { @@ -1362,7 +1375,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" + Arrays.toString(recordSource)); - return Constants.ABORT_NO_ERROR; + return Constants.HANDLED; } @ServiceThreadOnly @@ -1494,9 +1507,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - protected boolean handleMenuStatus(HdmiCecMessage message) { + @Constants.HandleMessageResult + protected int handleMenuStatus(HdmiCecMessage message) { // Do nothing and just return true not to prevent from responding <Feature Abort>. - return true; + return Constants.HANDLED; } @Constants.RcProfile diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 03a83380246f..031c057018ad 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -372,8 +372,7 @@ public class HdmiControlService extends SystemService { private HdmiCecMessageValidator mMessageValidator; - private final HdmiCecPowerStatusController mPowerStatusController = - new HdmiCecPowerStatusController(this); + private HdmiCecPowerStatusController mPowerStatusController; @ServiceThreadOnly private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault()); @@ -427,7 +426,7 @@ public class HdmiControlService extends SystemService { // Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing. private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter(); - private CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer(this); + private CecMessageBuffer mCecMessageBuffer; private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer(); @@ -493,6 +492,9 @@ public class HdmiControlService extends SystemService { mIoLooper = mIoThread.getLooper(); } + if (mPowerStatusController == null) { + mPowerStatusController = new HdmiCecPowerStatusController(this); + } mPowerStatusController.setPowerStatus(getInitialPowerStatus()); mProhibitMode = false; mHdmiControlEnabled = mHdmiCecConfig.getIntValue( @@ -501,6 +503,9 @@ public class HdmiControlService extends SystemService { HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)); mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true); + if (mCecMessageBuffer == null) { + mCecMessageBuffer = new CecMessageBuffer(this); + } if (mCecController == null) { mCecController = HdmiCecController.create(this, getAtomWriter()); } @@ -948,11 +953,10 @@ public class HdmiControlService extends SystemService { /** * Returns {@link Looper} for IO operation. - * - * <p>Declared as package-private. */ @Nullable - Looper getIoLooper() { + @VisibleForTesting + protected Looper getIoLooper() { return mIoLooper; } @@ -974,10 +978,9 @@ public class HdmiControlService extends SystemService { /** * Returns {@link Looper} of main thread. Use this {@link Looper} instance * for tasks that are running on main service thread. - * - * <p>Declared as package-private. */ - Looper getServiceLooper() { + @VisibleForTesting + protected Looper getServiceLooper() { return mHandler.getLooper(); } @@ -1015,8 +1018,9 @@ public class HdmiControlService extends SystemService { /** * Returns version of CEC. */ + @VisibleForTesting @HdmiControlManager.HdmiCecVersion - int getCecVersion() { + protected int getCecVersion() { return mCecVersion; } @@ -1087,23 +1091,30 @@ public class HdmiControlService extends SystemService { } @ServiceThreadOnly - boolean handleCecCommand(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int handleCecCommand(HdmiCecMessage message) { assertRunOnServiceThread(); int errorCode = mMessageValidator.isValid(message); if (errorCode != HdmiCecMessageValidator.OK) { // We'll not response on the messages with the invalid source or destination // or with parameter length shorter than specified in the standard. if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) { - maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); + return Constants.ABORT_INVALID_OPERAND; } - return true; + return Constants.HANDLED; } getHdmiCecNetwork().handleCecMessage(message); - if (dispatchMessageToLocalDevice(message)) { - return true; + + @Constants.HandleMessageResult int handleMessageResult = + dispatchMessageToLocalDevice(message); + if (handleMessageResult == Constants.NOT_HANDLED + && !mAddressAllocated + && mCecMessageBuffer.bufferMessage(message)) { + return Constants.HANDLED; } - return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false; + return handleMessageResult; } void enableAudioReturnChannel(int portId, boolean enabled) { @@ -1111,19 +1122,25 @@ public class HdmiControlService extends SystemService { } @ServiceThreadOnly - private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { + @VisibleForTesting + @Constants.HandleMessageResult + protected int dispatchMessageToLocalDevice(HdmiCecMessage message) { assertRunOnServiceThread(); for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { - if (device.dispatchMessage(message) + @Constants.HandleMessageResult int messageResult = device.dispatchMessage(message); + if (messageResult != Constants.NOT_HANDLED && message.getDestination() != Constants.ADDR_BROADCAST) { - return true; + return messageResult; } } - if (message.getDestination() != Constants.ADDR_BROADCAST) { + // We should never respond <Feature Abort> to a broadcast message + if (message.getDestination() == Constants.ADDR_BROADCAST) { + return Constants.HANDLED; + } else { HdmiLogger.warning("Unhandled cec command:" + message); + return Constants.NOT_HANDLED; } - return false; } /** @@ -2970,7 +2987,7 @@ public class HdmiControlService extends SystemService { } @VisibleForTesting - boolean isStandbyMessageReceived() { + protected boolean isStandbyMessageReceived() { return mStandbyMessageReceived; } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 092502709f34..0f137418a89c 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -76,6 +76,8 @@ import android.os.ShellCallback; import android.os.SystemProperties; import android.os.UserHandle; import android.os.VibrationEffect; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -1917,9 +1919,9 @@ public class InputManagerService extends IInputManager.Stub } private static class VibrationInfo { - private long[] mPattern = new long[0]; - private int[] mAmplitudes = new int[0]; - private int mRepeat = -1; + private final long[] mPattern; + private final int[] mAmplitudes; + private final int mRepeat; public long[] getPattern() { return mPattern; @@ -1934,40 +1936,55 @@ public class InputManagerService extends IInputManager.Stub } VibrationInfo(VibrationEffect effect) { - // First replace prebaked effects with its fallback, if any available. - if (effect instanceof VibrationEffect.Prebaked) { - VibrationEffect fallback = ((VibrationEffect.Prebaked) effect).getFallbackEffect(); - if (fallback != null) { - effect = fallback; + long[] pattern = null; + int[] amplitudes = null; + int patternRepeatIndex = -1; + int amplitudeCount = -1; + + if (effect instanceof VibrationEffect.Composed) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + int segmentCount = composed.getSegments().size(); + pattern = new long[segmentCount]; + amplitudes = new int[segmentCount]; + patternRepeatIndex = composed.getRepeatIndex(); + amplitudeCount = 0; + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if (composed.getRepeatIndex() == i) { + patternRepeatIndex = amplitudeCount; + } + if (!(segment instanceof StepSegment)) { + Slog.w(TAG, "Input devices don't support segment " + segment); + amplitudeCount = -1; + break; + } + float amplitude = ((StepSegment) segment).getAmplitude(); + if (Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) { + amplitudes[amplitudeCount] = DEFAULT_VIBRATION_MAGNITUDE; + } else { + amplitudes[amplitudeCount] = + (int) (amplitude * VibrationEffect.MAX_AMPLITUDE); + } + pattern[amplitudeCount++] = segment.getDuration(); } } - if (effect instanceof VibrationEffect.OneShot) { - VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; - mPattern = new long[] { 0, oneShot.getDuration() }; - int amplitude = oneShot.getAmplitude(); - // android framework uses DEFAULT_AMPLITUDE to signal that the vibration - // should use some built-in default value, denoted here as - // DEFAULT_VIBRATION_MAGNITUDE - if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) { - amplitude = DEFAULT_VIBRATION_MAGNITUDE; - } - mAmplitudes = new int[] { 0, amplitude }; + + if (amplitudeCount < 0) { + Slog.w(TAG, "Only oneshot and step waveforms are supported on input devices"); + mPattern = new long[0]; + mAmplitudes = new int[0]; mRepeat = -1; - } else if (effect instanceof VibrationEffect.Waveform) { - VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; - mPattern = waveform.getTimings(); - mAmplitudes = waveform.getAmplitudes(); - for (int i = 0; i < mAmplitudes.length; i++) { - if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) { - mAmplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE; - } - } - mRepeat = waveform.getRepeatIndex(); + } else { + mRepeat = patternRepeatIndex; + mPattern = new long[amplitudeCount]; + mAmplitudes = new int[amplitudeCount]; + System.arraycopy(pattern, 0, mPattern, 0, amplitudeCount); + System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudeCount); if (mRepeat >= mPattern.length) { - throw new ArrayIndexOutOfBoundsException(); + throw new ArrayIndexOutOfBoundsException("Repeat index " + mRepeat + + " must be within the bounds of the pattern.length " + + mPattern.length); } - } else { - Slog.w(TAG, "Pre-baked and composed effects aren't supported on input devices"); } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index f0d86d29bacd..27f8fd3e8f5c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -17,6 +17,7 @@ package com.android.server.inputmethod; import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD; @@ -178,7 +179,6 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; @@ -198,6 +198,7 @@ import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeS import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings; import com.android.server.pm.UserManagerInternal; import com.android.server.statusbar.StatusBarManagerService; +import com.android.server.utils.PriorityDump; import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; @@ -1565,7 +1566,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl(mService)); publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/, - DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO); + DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO); } @Override @@ -4829,6 +4830,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodEnabledLocked(defaultImiId, true); } } + + updateDefaultVoiceImeIfNeededLocked(); + // Here is not the perfect place to reset the switching controller. Ideally // mSwitchingController and mSettings should be able to share the same state. // TODO: Make sure that mSwitchingController and mSettings are sharing the @@ -4841,6 +4845,37 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget(); } + @GuardedBy("mMethodMap") + private void updateDefaultVoiceImeIfNeededLocked() { + final String systemSpeechRecognizer = + mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); + final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod(); + final InputMethodInfo newSystemVoiceIme = InputMethodUtils.chooseSystemVoiceIme( + mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId); + if (newSystemVoiceIme == null) { + if (DEBUG) { + Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked," + + " this may be expected."); + } + // Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings + // does not update the actual Secure Settings until the user is unlocked. + if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) { + mSettings.putDefaultVoiceInputMethod(""); + // We don't support disabling the voice ime when a package is removed from the + // config. + } + return; + } + if (TextUtils.equals(currentDefaultVoiceImeId, newSystemVoiceIme.getId())) { + return; + } + if (DEBUG) { + Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme); + } + setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true); + mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId()); + } + // ---------------------------------------------------------------------- private void showInputMethodAndSubtypeEnabler(String inputMethodId) { @@ -5224,17 +5259,71 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { + /** + * {@inheritDoc} + */ + @BinderThread + @Override + public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { + if (asProto) { + dumpAsProtoNoCheck(fd); + } else { + dumpAsStringNoCheck(fd, pw, args, true /* isCritical */); + } + } + + /** + * {@inheritDoc} + */ + @BinderThread + @Override + public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + dumpNormal(fd, pw, args, asProto); + } + + /** + * {@inheritDoc} + */ + @BinderThread + @Override + public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + if (asProto) { + dumpAsProtoNoCheck(fd); + } else { + dumpAsStringNoCheck(fd, pw, args, false /* isCritical */); + } + } + + /** + * {@inheritDoc} + */ + @BinderThread + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + dumpNormal(fd, pw, args, asProto); + } - if (ArrayUtils.contains(args, PROTO_ARG)) { + @BinderThread + private void dumpAsProtoNoCheck(FileDescriptor fd) { final ProtoOutputStream proto = new ProtoOutputStream(fd); dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE); proto.flush(); - return; } + }; + + @BinderThread + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + + PriorityDump.dump(mPriorityDumper, fd, pw, args); + } + @BinderThread + private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args, + boolean isCritical) { IInputMethod method; ClientState client; ClientState focusedWindowClient; @@ -5298,6 +5387,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSoftInputShowHideHistory.dump(pw, " "); } + // Exit here for critical dump, as remaining sections require IPCs to other processes. + if (isCritical) { + return; + } + p.println(" "); if (client != null) { pw.flush(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 0e908d471f74..ac3c31d5cca4 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -337,6 +337,52 @@ final class InputMethodUtils { return getDefaultEnabledImes(context, imis, false /* onlyMinimum */); } + /** + * Chooses an eligible system voice IME from the given IMEs. + * + * @param methodMap Map from the IME ID to {@link InputMethodInfo}. + * @param systemSpeechRecognizerPackageName System speech recognizer configured by the system + * config. + * @param currentDefaultVoiceImeId IME ID currently set to + * {@link Settings.Secure#DEFAULT_VOICE_INPUT_METHOD} + * @return {@link InputMethodInfo} that is found in {@code methodMap} and most suitable for + * the system voice IME. + */ + @Nullable + static InputMethodInfo chooseSystemVoiceIme( + @NonNull ArrayMap<String, InputMethodInfo> methodMap, + @Nullable String systemSpeechRecognizerPackageName, + @Nullable String currentDefaultVoiceImeId) { + if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) { + return null; + } + final InputMethodInfo defaultVoiceIme = methodMap.get(currentDefaultVoiceImeId); + // If the config matches the package of the setting, use the current one. + if (defaultVoiceIme != null && defaultVoiceIme.isSystem() + && defaultVoiceIme.getPackageName().equals(systemSpeechRecognizerPackageName)) { + return defaultVoiceIme; + } + InputMethodInfo firstMatchingIme = null; + final int methodCount = methodMap.size(); + for (int i = 0; i < methodCount; ++i) { + final InputMethodInfo imi = methodMap.valueAt(i); + if (!imi.isSystem()) { + continue; + } + if (!TextUtils.equals(imi.getPackageName(), systemSpeechRecognizerPackageName)) { + continue; + } + if (firstMatchingIme != null) { + Slog.e(TAG, "At most one InputMethodService can be published in " + + "systemSpeechRecognizer: " + systemSpeechRecognizerPackageName + + ". Ignoring all of them."); + return null; + } + firstMatchingIme = imi; + } + return firstMatchingIme; + } + static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, boolean checkCountry, String mode) { if (locale == null) { @@ -1233,6 +1279,22 @@ final class InputMethodUtils { return imi; } + void putDefaultVoiceInputMethod(String imeId) { + if (DEBUG) { + Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId); + } + putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId); + } + + @Nullable + String getDefaultVoiceInputMethod() { + final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null); + if (DEBUG) { + Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi); + } + return imi; + } + boolean isSubtypeSelected() { return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; } 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..4d302b19d89b 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -259,13 +259,16 @@ public class ContextHubService extends IContextHubService.Stub { BroadcastReceiver wifiReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction()) + || WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED.equals( + intent.getAction())) { sendWifiSettingUpdate(false /* forceUpdate */); } } }; IntentFilter filter = new IntentFilter(); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED); mContext.registerReceiver(wifiReceiver, filter); mContext.getContentResolver().registerContentObserver( @@ -298,7 +301,7 @@ public class ContextHubService extends IContextHubService.Stub { mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers( SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> { if (userId == getCurrentUserId()) { - Log.d(TAG, "User: " + userId + " enabled: " + enabled); + Log.d(TAG, "User: " + userId + "mic privacy: " + enabled); sendMicrophoneDisableSettingUpdate(enabled); } }); @@ -691,6 +694,7 @@ public class ContextHubService extends IContextHubService.Stub { sendLocationSettingUpdate(); sendWifiSettingUpdate(true /* forceUpdate */); sendAirplaneModeSettingUpdate(); + sendMicrophoneDisableSettingUpdateForCurrentUser(); mTransactionManager.onHubReset(); queryNanoAppsInternal(contextHubId); @@ -1123,6 +1127,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/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 3245fdfaebd6..7be47a4e52a8 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -324,8 +324,11 @@ public abstract class IContextHubWrapper { } public void onMicrophoneDisableSettingChanged(boolean enabled) { - sendSettingChanged(android.hardware.contexthub.V1_2.Setting.GLOBAL_MIC_DISABLE, - enabled ? SettingValue.ENABLED : SettingValue.DISABLED); + // The SensorPrivacyManager reports if microphone privacy was enabled, + // which translates to microphone access being disabled (and vice-versa). + // With this in mind, we flip the argument before piping it to CHRE. + sendSettingChanged(android.hardware.contexthub.V1_2.Setting.MICROPHONE, + enabled ? SettingValue.DISABLED : SettingValue.ENABLED); } private void sendSettingChanged(byte setting, byte newValue) { diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 6e99cba6ea91..76ecc1acc7ac 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -15,14 +15,17 @@ */ package com.android.server.locksettings; + import static android.os.UserHandle.USER_SYSTEM; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.UserInfo; import android.os.Handler; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.Settings; @@ -35,6 +38,8 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.widget.RebootEscrowListener; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -65,6 +70,22 @@ class RebootEscrowManager { public static final String REBOOT_ESCROW_ARMED_KEY = "reboot_escrow_armed_count"; static final String REBOOT_ESCROW_KEY_ARMED_TIMESTAMP = "reboot_escrow_key_stored_timestamp"; + static final String REBOOT_ESCROW_KEY_PROVIDER = "reboot_escrow_key_provider"; + + /** + * The verified boot 2.0 vbmeta digest of the current slot, the property value is always + * available after boot. + */ + static final String VBMETA_DIGEST_PROP_NAME = "ro.boot.vbmeta.digest"; + /** + * The system prop contains vbmeta digest of the inactive slot. The build property is set after + * an OTA update. RebootEscrowManager will store it in disk before the OTA reboot, so the value + * is available for vbmeta digest verification after the device reboots. + */ + static final String OTHER_VBMETA_DIGEST_PROP_NAME = "ota.other.vbmeta_digest"; + static final String REBOOT_ESCROW_KEY_VBMETA_DIGEST = "reboot_escrow_key_vbmeta_digest"; + static final String REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST = + "reboot_escrow_key_other_vbmeta_digest"; /** * Number of boots until we consider the escrow data to be stale for the purposes of metrics. @@ -86,6 +107,31 @@ class RebootEscrowManager { private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3; private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30; + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_NONE, + ERROR_UNKNOWN, + ERROR_NO_PROVIDER, + ERROR_LOAD_ESCROW_KEY, + ERROR_RETRY_COUNT_EXHAUSTED, + ERROR_UNLOCK_ALL_USERS, + ERROR_PROVIDER_MISMATCH, + ERROR_KEYSTORE_FAILURE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RebootEscrowErrorCode { + } + + static final int ERROR_NONE = 0; + static final int ERROR_UNKNOWN = 1; + static final int ERROR_NO_PROVIDER = 2; + static final int ERROR_LOAD_ESCROW_KEY = 3; + static final int ERROR_RETRY_COUNT_EXHAUSTED = 4; + static final int ERROR_UNLOCK_ALL_USERS = 5; + static final int ERROR_PROVIDER_MISMATCH = 6; + static final int ERROR_KEYSTORE_FAILURE = 7; + + private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE; + /** * Logs events for later debugging in bugreports. */ @@ -199,6 +245,10 @@ class RebootEscrowManager { 0); } + public long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + public int getLoadEscrowDataRetryLimit() { return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); @@ -221,6 +271,11 @@ class RebootEscrowManager { public RebootEscrowEventLog getEventLog() { return new RebootEscrowEventLog(); } + + public String getVbmetaDigest(boolean other) { + return other ? SystemProperties.get(OTHER_VBMETA_DIGEST_PROP_NAME) + : SystemProperties.get(VBMETA_DIGEST_PROP_NAME); + } } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { @@ -261,6 +316,7 @@ class RebootEscrowManager { if (rebootEscrowUsers.isEmpty()) { Slog.i(TAG, "No reboot escrow data found for users," + " skipping loading escrow data"); + clearMetricsStorage(); return; } @@ -284,6 +340,7 @@ class RebootEscrowManager { } Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts"); + mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED; onGetRebootEscrowKeyFailed(users, attemptNumber); } @@ -307,6 +364,17 @@ class RebootEscrowManager { } if (escrowKey == null) { + if (mLoadEscrowDataErrorCode == ERROR_NONE) { + // Specifically check if the RoR provider has changed after reboot. + int providerType = mInjector.serverBasedResumeOnReboot() + ? RebootEscrowProviderInterface.TYPE_SERVER_BASED + : RebootEscrowProviderInterface.TYPE_HAL; + if (providerType != mStorage.getInt(REBOOT_ESCROW_KEY_PROVIDER, -1, USER_SYSTEM)) { + mLoadEscrowDataErrorCode = ERROR_PROVIDER_MISMATCH; + } else { + mLoadEscrowDataErrorCode = ERROR_LOAD_ESCROW_KEY; + } + } onGetRebootEscrowKeyFailed(users, attemptNumber + 1); return; } @@ -321,9 +389,49 @@ class RebootEscrowManager { // Clear the old key in keystore. A new key will be generated by new RoR requests. mKeyStoreManager.clearKeyStoreEncryptionKey(); + if (!allUsersUnlocked && mLoadEscrowDataErrorCode == ERROR_NONE) { + mLoadEscrowDataErrorCode = ERROR_UNLOCK_ALL_USERS; + } onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1); } + private void clearMetricsStorage() { + mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_VBMETA_DIGEST, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, USER_SYSTEM); + mStorage.removeKey(REBOOT_ESCROW_KEY_PROVIDER, USER_SYSTEM); + } + + private int getVbmetaDigestStatusOnRestoreComplete() { + String currentVbmetaDigest = mInjector.getVbmetaDigest(false); + String vbmetaDigestStored = mStorage.getString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, + "", USER_SYSTEM); + String vbmetaDigestOtherStored = mStorage.getString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, + "", USER_SYSTEM); + + // The other vbmeta digest is never set, assume no slot switch is attempted. + if (vbmetaDigestOtherStored.isEmpty()) { + if (currentVbmetaDigest.equals(vbmetaDigestStored)) { + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; + } + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH; + } + + // The other vbmeta digest is set, we expect to boot into the new slot. + if (currentVbmetaDigest.equals(vbmetaDigestOtherStored)) { + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; + } else if (currentVbmetaDigest.equals(vbmetaDigestStored)) { + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_FALLBACK_SLOT; + } + return FrameworkStatsLog + .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH; + } + private void reportMetricOnRestoreComplete(boolean success, int attemptCount) { int serviceType = mInjector.serverBasedResumeOnReboot() ? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED @@ -331,26 +439,32 @@ class RebootEscrowManager { long armedTimestamp = mStorage.getLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, -1, USER_SYSTEM); - mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM); - int escrowDurationInSeconds = armedTimestamp != -1 - ? (int) (System.currentTimeMillis() - armedTimestamp) / 1000 : -1; + int escrowDurationInSeconds = -1; + long currentTimeStamp = mInjector.getCurrentTimeMillis(); + if (armedTimestamp != -1 && currentTimeStamp > armedTimestamp) { + escrowDurationInSeconds = (int) (currentTimeStamp - armedTimestamp) / 1000; + } - // TODO(b/179105110) design error code; and report the true value for other fields. - int vbmetaDigestStatus = FrameworkStatsLog - .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT; + int vbmetaDigestStatus = getVbmetaDigestStatusOnRestoreComplete(); + if (!success && mLoadEscrowDataErrorCode == ERROR_NONE) { + mLoadEscrowDataErrorCode = ERROR_UNKNOWN; + } - mInjector.reportMetric(success, 0 /* error code */, serviceType, attemptCount, + // TODO(179105110) report the duration since boot complete. + mInjector.reportMetric(success, mLoadEscrowDataErrorCode, serviceType, attemptCount, escrowDurationInSeconds, vbmetaDigestStatus, -1); + + mLoadEscrowDataErrorCode = ERROR_NONE; } private void onEscrowRestoreComplete(boolean success, int attemptCount) { int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM); - mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); int bootCountDelta = mInjector.getBootCount() - previousBootCount; if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) { reportMetricOnRestoreComplete(success, attemptCount); } + clearMetricsStorage(); } private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException { @@ -358,6 +472,14 @@ class RebootEscrowManager { if (rebootEscrowProvider == null) { Slog.w(TAG, "Had reboot escrow data for users, but RebootEscrowProvider is unavailable"); + mLoadEscrowDataErrorCode = ERROR_NO_PROVIDER; + return null; + } + + // Server based RoR always need the decryption key from keystore. + if (rebootEscrowProvider.getType() == RebootEscrowProviderInterface.TYPE_SERVER_BASED + && kk == null) { + mLoadEscrowDataErrorCode = ERROR_KEYSTORE_FAILURE; return null; } @@ -463,7 +585,7 @@ class RebootEscrowManager { return; } - mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM); + clearMetricsStorage(); rebootEscrowProvider.clearRebootEscrowKey(); List<UserInfo> users = mUserManager.getUsers(); @@ -486,6 +608,9 @@ class RebootEscrowManager { return false; } + int actualProviderType = rebootEscrowProvider.getType(); + // TODO(b/183140900) Fail the reboot if provider type mismatches. + RebootEscrowKey escrowKey; synchronized (mKeyGenerationLock) { escrowKey = mPendingRebootEscrowKey; @@ -505,8 +630,14 @@ class RebootEscrowManager { boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk); if (armedRebootEscrow) { mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM); - mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, System.currentTimeMillis(), + mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(), + USER_SYSTEM); + // Store the vbmeta digest of both slots. + mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false), USER_SYSTEM); + mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, + mInjector.getVbmetaDigest(true), USER_SYSTEM); + mStorage.setInt(REBOOT_ESCROW_KEY_PROVIDER, actualProviderType, USER_SYSTEM); mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS); } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java index 4b00772088f2..e8f6f4abd030 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java @@ -60,6 +60,11 @@ class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface { } @Override + public int getType() { + return TYPE_HAL; + } + + @Override public boolean hasRebootEscrowSupport() { return mInjector.getRebootEscrow() != null; } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java index af6faad3c76e..e106d817c533 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java @@ -16,7 +16,11 @@ package com.android.server.locksettings; +import android.annotation.IntDef; + import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import javax.crypto.SecretKey; @@ -28,6 +32,21 @@ import javax.crypto.SecretKey; * @hide */ public interface RebootEscrowProviderInterface { + @IntDef(prefix = {"TYPE_"}, value = { + TYPE_HAL, + TYPE_SERVER_BASED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RebootEscrowProviderType { + } + int TYPE_HAL = 0; + int TYPE_SERVER_BASED = 1; + + /** + * Returns the reboot escrow provider type. + */ + @RebootEscrowProviderType int getType(); + /** * Returns true if the secure store/discard of reboot escrow key is supported. */ diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java index 697bf08a232e..28669875f1cd 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -95,6 +95,11 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } @Override + public int getType() { + return TYPE_SERVER_BASED; + } + + @Override public boolean hasRebootEscrowSupport() { return mInjector.getServiceConnection() != null; } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index db2e9085bdb3..a30d993136a7 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -1225,6 +1225,70 @@ public class MediaSessionService extends SystemService implements Monitor { } @Override + public MediaSession.Token getMediaKeyEventSession() { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); + final long token = Binder.clearCallingIdentity(); + try { + if (!hasMediaControlPermission(pid, uid)) { + throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to" + + " get the media key event session"); + } + MediaSessionRecordImpl record; + synchronized (mLock) { + FullUserRecord user = getFullUserRecordLocked(userId); + if (user == null) { + Log.w(TAG, "No matching user record to get the media key event session" + + ", userId=" + userId); + return null; + } + record = user.getMediaButtonSessionLocked(); + } + if (record instanceof MediaSessionRecord) { + return ((MediaSessionRecord) record).getSessionToken(); + } + //TODO: Handle media session 2 case + return null; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public String getMediaKeyEventSessionPackageName() { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); + final long token = Binder.clearCallingIdentity(); + try { + if (!hasMediaControlPermission(pid, uid)) { + throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to" + + " get the media key event session package"); + } + MediaSessionRecordImpl record; + synchronized (mLock) { + FullUserRecord user = getFullUserRecordLocked(userId); + if (user == null) { + Log.w(TAG, "No matching user record to get the media key event session" + + " package , userId=" + userId); + return ""; + } + record = user.getMediaButtonSessionLocked(); + if (record instanceof MediaSessionRecord) { + return record.getPackageName(); + //TODO: Handle media session 2 case + } else if (user.mLastMediaButtonReceiverHolder != null) { + return user.mLastMediaButtonReceiverHolder.getPackageName(); + } + } + return ""; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void addSessionsListener(IActiveSessionsListener listener, ComponentName componentName, int userId) throws RemoteException { final int pid = Binder.getCallingPid(); 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..385928525ada 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; @@ -422,15 +422,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_LIMIT_REACHED = 5; private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6; private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7; - private static final int MSG_UPDATE_INTERFACE_QUOTA = 10; - private static final int MSG_REMOVE_INTERFACE_QUOTA = 11; + private static final int MSG_UPDATE_INTERFACE_QUOTAS = 10; + private static final int MSG_REMOVE_INTERFACE_QUOTAS = 11; private static final int MSG_POLICIES_CHANGED = 13; private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15; private static final int MSG_SUBSCRIPTION_OVERRIDE = 16; 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,45 @@ 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; + long warningBytes = Long.MAX_VALUE; + if ((hasLimit || hasWarning) && 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 the limit notification is not snoozed, the limit quota needs to be calculated. + if (hasLimit && 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 the warning notification was snoozed by user, or the service already knows + // it is over warning bytes, doesn't need to calculate warning bytes. + if (hasWarning && policy.lastWarningSnooze < start + && !policy.isOverWarning(totalBytes)) { + warningBytes = Math.max(1, policy.warningBytes - totalBytes); + } + } + + if (hasWarning || 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 warning and limit. For interfaces which has no cycle, + // or metered with no policy quotas, or snoozed notification; we still need to put + // iptables rule hooks to restrict apps for data saver, so push really high quota. + // TODO: Push NetworkStatsProvider.QUOTA_UNLIMITED instead of Long.MAX_VALUE to + // providers. for (int j = matchingIfaces.size() - 1; j >= 0; j--) { final String iface = matchingIfaces.valueAt(j); - setInterfaceQuotaAsync(iface, quotaBytes); + setInterfaceQuotasAsync(iface, warningBytes, limitBytes); newMeteredIfaces.add(iface); } } @@ -2089,7 +2095,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int j = matchingIfaces.size() - 1; j >= 0; j--) { final String iface = matchingIfaces.valueAt(j); if (!newMeteredIfaces.contains(iface)) { - setInterfaceQuotaAsync(iface, Long.MAX_VALUE); + setInterfaceQuotasAsync(iface, Long.MAX_VALUE, Long.MAX_VALUE); newMeteredIfaces.add(iface); } } @@ -2101,7 +2107,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) { final String iface = mMeteredIfaces.valueAt(i); if (!newMeteredIfaces.contains(iface)) { - removeInterfaceQuotaAsync(iface); + removeInterfaceQuotasAsync(iface); } } mMeteredIfaces = newMeteredIfaces; @@ -4970,7 +4976,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) { @@ -5041,19 +5047,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mNetworkStats.advisePersistThreshold(persistThreshold); return true; } - case MSG_UPDATE_INTERFACE_QUOTA: { - final String iface = (String) msg.obj; - // int params need to be stitched back into a long - final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL); - removeInterfaceQuota(iface); - setInterfaceQuota(iface, quota); - mNetworkStats.setStatsProviderLimitAsync(iface, quota); + case MSG_UPDATE_INTERFACE_QUOTAS: { + final IfaceQuotas val = (IfaceQuotas) msg.obj; + // TODO: Consider set a new limit before removing the original one. + removeInterfaceLimit(val.iface); + setInterfaceLimit(val.iface, val.limit); + mNetworkStats.setStatsProviderWarningAndLimitAsync(val.iface, val.warning, + val.limit); return true; } - case MSG_REMOVE_INTERFACE_QUOTA: { + case MSG_REMOVE_INTERFACE_QUOTAS: { final String iface = (String) msg.obj; - removeInterfaceQuota(iface); - mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED); + removeInterfaceLimit(iface); + mNetworkStats.setStatsProviderWarningAndLimitAsync(iface, QUOTA_UNLIMITED, + QUOTA_UNLIMITED); return true; } case MSG_RESET_FIREWALL_RULES_BY_UID: { @@ -5201,15 +5208,32 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void setInterfaceQuotaAsync(String iface, long quotaBytes) { - // long quotaBytes split up into two ints to fit in message - mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA, (int) (quotaBytes >> 32), - (int) (quotaBytes & 0xFFFFFFFF), iface).sendToTarget(); + private static final class IfaceQuotas { + @NonNull public final String iface; + // Warning and limit bytes of interface qutoas, could be QUOTA_UNLIMITED or Long.MAX_VALUE + // if not set. 0 is not acceptable since kernel doesn't like 0-byte rules. + public final long warning; + public final long limit; + + private IfaceQuotas(@NonNull String iface, long warning, long limit) { + this.iface = iface; + this.warning = warning; + this.limit = limit; + } + } + + private void setInterfaceQuotasAsync(@NonNull String iface, + long warningBytes, long limitBytes) { + mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS, + new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget(); } - private void setInterfaceQuota(String iface, long quotaBytes) { + private void setInterfaceLimit(String iface, long limitBytes) { try { - mNetworkManager.setInterfaceQuota(iface, quotaBytes); + // For legacy design the data warning is covered by global alert, where the + // kernel will notify upper layer for a small amount of change of traffic + // statistics. Thus, passing warning is not needed. + mNetworkManager.setInterfaceQuota(iface, limitBytes); } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting interface quota", e); } catch (RemoteException e) { @@ -5217,11 +5241,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void removeInterfaceQuotaAsync(String iface) { - mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface).sendToTarget(); + private void removeInterfaceQuotasAsync(String iface) { + mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTAS, iface).sendToTarget(); } - private void removeInterfaceQuota(String iface) { + private void removeInterfaceLimit(String iface) { try { mNetworkManager.removeInterfaceQuota(iface); } catch (IllegalStateException e) { @@ -5726,9 +5750,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/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java index 0cb0bc2c0896..0e9a9da6804b 100644 --- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java @@ -37,8 +37,9 @@ public abstract class NetworkStatsManagerInternal { public abstract void forceUpdate(); /** - * Set the quota limit to all registered custom network stats providers. + * Set the warning and limit to all registered custom network stats providers. * Note that invocation of any interface will be sent to all providers. */ - public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota); + public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning, + long limit); } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index fe43c311da6f..de5aae07d6be 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -1674,9 +1674,14 @@ 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)); + public void setStatsProviderWarningAndLimitAsync( + @NonNull String iface, long warning, long limit) { + if (LOGV) { + Slog.v(TAG, "setStatsProviderWarningAndLimitAsync(" + + iface + "," + warning + "," + limit + ")"); + } + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, + warning, limit)); } } @@ -2071,10 +2076,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/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 619fc4e8e3f2..dc3b78afeedb 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -33,6 +33,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; @@ -73,7 +74,6 @@ import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; @@ -184,6 +184,8 @@ public class PreferencesHelper implements RankingConfig { private Map<String, List<String>> mOemLockedApps = new HashMap(); + private int mCurrentUserId = UserHandle.USER_NULL; + public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, @@ -199,7 +201,8 @@ public class PreferencesHelper implements RankingConfig { updateBadgingEnabled(); updateBubblesEnabled(); updateMediaNotificationFilteringEnabled(); - syncChannelsBypassingDnd(mContext.getUserId()); + mCurrentUserId = ActivityManager.getCurrentUser(); + syncChannelsBypassingDnd(); } public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId) @@ -806,7 +809,7 @@ public class PreferencesHelper implements RankingConfig { // but the system can if (group.isBlocked() != oldGroup.isBlocked()) { group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE); - updateChannelsBypassingDnd(mContext.getUserId()); + updateChannelsBypassingDnd(); } } } @@ -888,13 +891,13 @@ public class PreferencesHelper implements RankingConfig { // fields on the channel yet if (existing.getUserLockedFields() == 0 && hasDndAccess) { boolean bypassDnd = channel.canBypassDnd(); - if (bypassDnd != existing.canBypassDnd()) { + if (bypassDnd != existing.canBypassDnd() || wasUndeleted) { existing.setBypassDnd(bypassDnd); needsPolicyFileChange = true; if (bypassDnd != mAreChannelsBypassingDnd || previousExistingImportance != existing.getImportance()) { - updateChannelsBypassingDnd(mContext.getUserId()); + updateChannelsBypassingDnd(); } } } @@ -958,7 +961,7 @@ public class PreferencesHelper implements RankingConfig { r.channels.put(channel.getId(), channel); if (channel.canBypassDnd() != mAreChannelsBypassingDnd) { - updateChannelsBypassingDnd(mContext.getUserId()); + updateChannelsBypassingDnd(); } MetricsLogger.action(getChannelLog(channel, pkg).setType( com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN)); @@ -1047,7 +1050,7 @@ public class PreferencesHelper implements RankingConfig { if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd || channel.getImportance() != updatedChannel.getImportance()) { - updateChannelsBypassingDnd(mContext.getUserId()); + updateChannelsBypassingDnd(); } } updateConfig(); @@ -1145,7 +1148,7 @@ public class PreferencesHelper implements RankingConfig { mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg); if (mAreChannelsBypassingDnd && channel.canBypassDnd()) { - updateChannelsBypassingDnd(mContext.getUserId()); + updateChannelsBypassingDnd(); } } } @@ -1512,7 +1515,7 @@ public class PreferencesHelper implements RankingConfig { } } if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) { - updateChannelsBypassingDnd(mContext.getUserId()); + updateChannelsBypassingDnd(); } return deletedChannelIds; } @@ -1658,29 +1661,29 @@ public class PreferencesHelper implements RankingConfig { } /** - * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before + * Syncs {@link #mAreChannelsBypassingDnd} with the current user's notification policy before * updating - * @param userId */ - private void syncChannelsBypassingDnd(int userId) { + private void syncChannelsBypassingDnd() { mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1; - updateChannelsBypassingDnd(userId); + updateChannelsBypassingDnd(); } /** - * Updates the user's NotificationPolicy based on whether the given userId + * Updates the user's NotificationPolicy based on whether the current userId * has channels bypassing DND * @param userId */ - private void updateChannelsBypassingDnd(int userId) { + private void updateChannelsBypassingDnd() { synchronized (mPackagePreferences) { final int numPackagePreferences = mPackagePreferences.size(); for (int i = 0; i < numPackagePreferences; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); - // Package isn't associated with this userId or notifications from this package are - // blocked - if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) { + // Package isn't associated with the current userId or notifications from this + // package are blocked + if (mCurrentUserId != UserHandle.getUserId(r.uid) + || r.importance == IMPORTANCE_NONE) { continue; } @@ -2226,14 +2229,16 @@ public class PreferencesHelper implements RankingConfig { * Called when user switches */ public void onUserSwitched(int userId) { - syncChannelsBypassingDnd(userId); + mCurrentUserId = userId; + syncChannelsBypassingDnd(); } /** * Called when user is unlocked */ public void onUserUnlocked(int userId) { - syncChannelsBypassingDnd(userId); + mCurrentUserId = userId; + syncChannelsBypassingDnd(); } public void onUserRemoved(int userId) { diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 7aaab0c4d40b..e0a39f313012 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -260,7 +260,8 @@ public class PackageDexOptimizer { // Only report metrics for base apk for now. // TODO: add ISA and APK type to metrics. - if (pkg.getBaseApkPath().equals(path)) { + // OTAPreopt doesn't have stats so don't report in that case. + if (pkg.getBaseApkPath().equals(path) && packageStats != null) { Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics"); try { long sessionId = Math.randomLongInternal(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index bafe987cb546..67638bccf7fa 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1212,12 +1212,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f) + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f); } else { - // For incremental installs, continue publishing the install progress during committing. - mProgress = mIncrementalProgress; + // For incremental install, continue to publish incremental progress during committing. + if (isIncrementalInstallation() && (mIncrementalProgress - mProgress) >= 0.01) { + // It takes some time for data loader to write to incremental file system, so at the + // beginning of the commit, the incremental progress might be very small. + // Wait till the incremental progress is larger than what's already displayed. + // This way we don't see the progress ring going backwards. + mProgress = mIncrementalProgress; + } } // Only publish when meaningful change - if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) { + if (forcePublish || (mProgress - mReportedProgress) >= 0.01) { mReportedProgress = mProgress; mCallback.onSessionProgressChanged(this, mProgress); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b27c0bd7034b..764fa0218023 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -252,7 +252,6 @@ import android.content.pm.parsing.component.ParsedPermissionGroup; import android.content.pm.parsing.component.ParsedProcess; import android.content.pm.parsing.component.ParsedProvider; import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedUsesPermission; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.Resources; @@ -371,6 +370,7 @@ import com.android.server.SystemConfig; import com.android.server.SystemServerInitThreadPool; import com.android.server.Watchdog; import com.android.server.apphibernation.AppHibernationManagerInternal; +import com.android.server.apphibernation.AppHibernationService; import com.android.server.compat.CompatChange; import com.android.server.compat.PlatformCompat; import com.android.server.net.NetworkPolicyManagerInternal; @@ -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 { @@ -12304,9 +12281,15 @@ public class PackageManagerService extends IPackageManager.Stub public ArraySet<String> getOptimizablePackages() { ArraySet<String> pkgs = new ArraySet<>(); + final boolean hibernationEnabled = AppHibernationService.isAppHibernationEnabled(); + AppHibernationManagerInternal appHibernationManager = + mInjector.getLocalService(AppHibernationManagerInternal.class); synchronized (mLock) { for (AndroidPackage p : mPackages.values()) { - if (PackageDexOptimizer.canOptimizePackage(p)) { + // Checking hibernation state is an inexpensive call. + boolean isHibernating = hibernationEnabled + && appHibernationManager.isHibernatingGlobally(p.getPackageName()); + if (PackageDexOptimizer.canOptimizePackage(p) && !isHibernating) { pkgs.add(p.getPackageName()); } } @@ -15459,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); + } } /** @@ -15612,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); @@ -21182,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); @@ -21740,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, @@ -22257,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); @@ -22320,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); @@ -27329,6 +27316,11 @@ public class PackageManagerService extends IPackageManager.Stub return PackageManagerService.this.getPackageStartability( packageName, callingUid, userId) == PACKAGE_STARTABILITY_FROZEN; } + + @Override + public void deleteOatArtifactsOfPackage(String packageName) { + PackageManagerService.this.deleteOatArtifactsOfPackage(packageName); + } } @GuardedBy("mLock") 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/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 8c3c42374acb..fd8ec7f4bdeb 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -2347,7 +2347,7 @@ class ShortcutPackage extends ShortcutPackageItem { } final List<ShortcutInfo> page = new ArrayList<>(results.size()); for (SearchResult result : results) { - final ShortcutInfo si = new AppSearchShortcutInfo(result.getDocument()) + final ShortcutInfo si = new AppSearchShortcutInfo(result.getGenericDocument()) .toShortcutInfo(mShortcutUser.getUserId()); page.add(si); } @@ -2398,8 +2398,7 @@ class ShortcutPackage extends ShortcutPackageItem { @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) { final long callingIdentity = Binder.clearCallingIdentity(); final AppSearchManager.SearchContext searchContext = - new AppSearchManager.SearchContext.Builder() - .setDatabaseName(getPackageName()).build(); + new AppSearchManager.SearchContext.Builder(getPackageName()).build(); final AppSearchSession session; try { session = ConcurrentUtils.waitForFutureNoInterrupt( @@ -2408,6 +2407,8 @@ class ShortcutPackage extends ShortcutPackageItem { mAppSearchSession = session; return cb.apply(mAppSearchSession); } catch (Exception e) { + Slog.e(TAG, "Failed to initiate app search for shortcut package " + + getPackageName() + " user " + mShortcutUser.getUserId(), e); return AndroidFuture.completedFuture(null); } finally { Binder.restoreCallingIdentity(callingIdentity); @@ -2427,7 +2428,8 @@ class ShortcutPackage extends ShortcutPackageItem { AppSearchShortcutInfo.SCHEMA_TYPE, true, pi); } final AndroidFuture<AppSearchSession> future = new AndroidFuture<>(); - session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> { + session.setSchema( + schemaBuilder.build(), mShortcutUser.mExecutor, mShortcutUser.mExecutor, result -> { if (!result.isSuccess()) { future.completeExceptionally( new IllegalArgumentException(result.getErrorMessage())); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index fc88f39bb57a..e1d1c264e666 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -171,7 +171,7 @@ public class ShortcutService extends IShortcutService.Stub { static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10; @VisibleForTesting - static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15; + static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = Integer.MAX_VALUE; @VisibleForTesting static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; @@ -342,7 +342,7 @@ public class ShortcutService extends IShortcutService.Stub { private final IPackageManager mIPackageManager; private final PackageManagerInternal mPackageManagerInternal; - private final UserManagerInternal mUserManagerInternal; + final UserManagerInternal mUserManagerInternal; private final UsageStatsManagerInternal mUsageStatsManagerInternal; private final ActivityManagerInternal mActivityManagerInternal; private final IUriGrantsManager mUriGrantsManager; diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 51cb995aac91..ce49d8894fdb 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -722,6 +722,12 @@ class ShortcutUser { future.completeExceptionally(new RuntimeException("app search manager is null")); return future; } + if (!mService.mUserManagerInternal.isUserUnlockingOrUnlocked(getUserId())) { + // In rare cases the user might be stopped immediate after it started, in these cases + // any on-going session will need to be abandoned. + future.completeExceptionally(new RuntimeException("User " + getUserId() + " is ")); + return future; + } final long callingIdentity = Binder.clearCallingIdentity(); try { mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index fe19956ce8ce..2a0257dd2a20 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1507,6 +1507,26 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isCloneProfile(@UserIdInt int userId) { + checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isCloneProfile"); + synchronized (mUsersLock) { + UserInfo userInfo = getUserInfoLU(userId); + return userInfo != null && userInfo.isCloneProfile(); + } + } + + @Override + public boolean sharesMediaWithParent(@UserIdInt int userId) { + checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, + "sharesMediaWithParent"); + synchronized (mUsersLock) { + UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId); + return userTypeDetails != null ? userTypeDetails.isProfile() + && userTypeDetails.sharesMediaWithParent() : false; + } + } + + @Override public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) { checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isUserUnlockingOrUnlocked"); diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java index 17ce386e3770..6824f7d8fa55 100644 --- a/services/core/java/com/android/server/pm/UserTypeDetails.java +++ b/services/core/java/com/android/server/pm/UserTypeDetails.java @@ -149,6 +149,13 @@ public final class UserTypeDetails { */ private final @Nullable int[] mDarkThemeBadgeColors; + /** + * Denotes if the user shares media with its parent user. + * + * <p> Default value is false + */ + private final boolean mSharesMediaWithParent; + private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed, @UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label, int maxAllowedPerParent, @@ -158,7 +165,8 @@ public final class UserTypeDetails { @Nullable Bundle defaultRestrictions, @Nullable Bundle defaultSystemSettings, @Nullable Bundle defaultSecureSettings, - @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters) { + @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters, + boolean sharesMediaWithParent) { this.mName = name; this.mEnabled = enabled; this.mMaxAllowed = maxAllowed; @@ -177,6 +185,7 @@ public final class UserTypeDetails { this.mBadgeLabels = badgeLabels; this.mBadgeColors = badgeColors; this.mDarkThemeBadgeColors = darkThemeBadgeColors; + this.mSharesMediaWithParent = sharesMediaWithParent; } /** @@ -291,6 +300,13 @@ public final class UserTypeDetails { return (mBaseType & UserInfo.FLAG_SYSTEM) != 0; } + /** + * Returns true if the user has shared media with parent user or false otherwise. + */ + public boolean sharesMediaWithParent() { + return mSharesMediaWithParent; + } + /** Returns a {@link Bundle} representing the default user restrictions. */ @NonNull Bundle getDefaultRestrictions() { return BundleUtils.clone(mDefaultRestrictions); @@ -318,7 +334,6 @@ public final class UserTypeDetails { : Collections.emptyList(); } - /** Dumps details of the UserTypeDetails. Do not parse this. */ public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mName: "); pw.println(mName); @@ -383,6 +398,7 @@ public final class UserTypeDetails { private @DrawableRes int mIconBadge = Resources.ID_NULL; private @DrawableRes int mBadgePlain = Resources.ID_NULL; private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL; + private boolean mSharesMediaWithParent = false; public Builder setName(String name) { mName = name; @@ -473,6 +489,15 @@ public final class UserTypeDetails { return this; } + /** + * Sets shared media property for the user. + * @param sharesMediaWithParent the value to be set, true or false + */ + public Builder setSharesMediaWithParent(boolean sharesMediaWithParent) { + mSharesMediaWithParent = sharesMediaWithParent; + return this; + } + @UserInfoFlag int getBaseType() { return mBaseType; } @@ -502,7 +527,7 @@ public final class UserTypeDetails { mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors, mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors, mDefaultRestrictions, mDefaultSystemSettings, mDefaultSecureSettings, - mDefaultCrossProfileIntentFilters); + mDefaultCrossProfileIntentFilters, mSharesMediaWithParent); } private boolean hasBadge() { diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 6aac0b2f3d0f..e8421a5d966d 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -29,6 +29,7 @@ import static android.os.UserManager.USER_TYPE_FULL_GUEST; import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_FULL_SYSTEM; +import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.os.UserManager.USER_TYPE_PROFILE_TEST; import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS; @@ -100,6 +101,7 @@ public final class UserTypeFactory { builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo()); builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted()); builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless()); + builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone()); if (Build.IS_DEBUGGABLE) { builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest()); } @@ -108,6 +110,21 @@ public final class UserTypeFactory { } /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_CLONE} + * configuration. + */ + // TODO(b/182396009): Add default restrictions, if needed for clone user type. + private static UserTypeDetails.Builder getDefaultTypeProfileClone() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_PROFILE_CLONE) + .setBaseType(FLAG_PROFILE) + .setMaxAllowedPerParent(1) + .setLabel(0) + .setDefaultRestrictions(null) + .setSharesMediaWithParent(true); + } + + /** * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED} * configuration. */ diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 629f12035b39..21e44ab9b539 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -407,7 +407,7 @@ public class PackageInfoUtils { retProcs.put(proc.getName(), new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()), proc.getGwpAsanMode(), proc.getMemtagMode(), - proc.getNativeHeapZeroInit())); + proc.getNativeHeapZeroInitialized())); } return retProcs; } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 27bf8a13766a..e16c67f07020 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -206,6 +206,12 @@ final class DefaultPermissionGrantPolicy { STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION); } + private static final Set<String> NEARBY_DEVICES_PERMISSIONS = new ArraySet<>(); + static { + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT); + NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); + } + private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1; private static final String ACTION_TRACK = "com.android.fitness.TRACK"; @@ -733,14 +739,15 @@ final class DefaultPermissionGrantPolicy { PHONE_PERMISSIONS, SMS_PERMISSIONS, CAMERA_PERMISSIONS, SENSORS_PERMISSIONS, STORAGE_PERMISSIONS); grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId, - ALWAYS_LOCATION_PERMISSIONS, ACTIVITY_RECOGNITION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS, + ACTIVITY_RECOGNITION_PERMISSIONS); } } if (locationExtraPackageNames != null) { // Also grant location and activity recognition permission to location extra packages. for (String packageName : locationExtraPackageNames) { grantPermissionsToSystemPackage(pm, packageName, userId, - ALWAYS_LOCATION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS); grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId, ACTIVITY_RECOGNITION_PERMISSIONS); } @@ -809,7 +816,7 @@ final class DefaultPermissionGrantPolicy { // Companion devices grantSystemFixedPermissionsToSystemPackage(pm, CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, userId, - ALWAYS_LOCATION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS); // Ringtone Picker grantSystemFixedPermissionsToSystemPackage(pm, @@ -1722,6 +1729,14 @@ final class DefaultPermissionGrantPolicy { @Override public void grantPermission(@NonNull String permission, @NonNull PackageInfo pkg, @NonNull UserHandle user) { + if (PermissionManager.DEBUG_TRACE_GRANTS + && PermissionManager.shouldTraceGrant( + pkg.packageName, permission, user.getIdentifier())) { + Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, + "PregrantPolicy is granting " + pkg.packageName + " " + + permission + " for user " + user.getIdentifier(), + new RuntimeException()); + } PermissionState state = getPermissionState(permission, pkg, user); state.initGranted(); state.newGranted = true; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 3bb5c1694734..e63426f8a4db 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -35,6 +35,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGR import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; +import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; @@ -1337,7 +1338,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) { if (PermissionManager.DEBUG_TRACE_GRANTS && PermissionManager.shouldTraceGrant(packageName, permName, userId)) { - Log.i(TAG, "System is granting " + packageName + " " + Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, "System is granting " + packageName + " " + permName + " for user " + userId + " on behalf of uid " + callingUid + " " + mPackageManagerInt.getNameForUid(callingUid), new RuntimeException()); @@ -1657,7 +1658,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_REVOKED_COMPAT | FLAG_PERMISSION_REVIEW_REQUIRED - | FLAG_PERMISSION_ONE_TIME; + | FLAG_PERMISSION_ONE_TIME + | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index f4bcd3e65913..0b48b5c6dd70 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -70,11 +70,8 @@ public class DomainVerificationEnforcer { break; default: if (!proxy.isCallerVerifier(callingUid)) { - mContext.enforcePermission( - android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION, - Binder.getCallingPid(), callingUid, - "Caller " + callingUid - + " is not allowed to query domain verification state"); + throw new SecurityException( + "Caller is not allowed to query domain verification state"); } mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES, diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index a0e252a8a28a..1b2ff08bb185 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -148,6 +148,8 @@ public class DomainVerificationService extends SystemService @NonNull private DomainVerificationProxy mProxy = new DomainVerificationProxyUnavailable(); + private boolean mCanSendBroadcasts; + public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig, @NonNull PlatformCompat platformCompat) { super(context); @@ -181,11 +183,18 @@ public class DomainVerificationService extends SystemService @Override public void onBootPhase(int phase) { super.onBootPhase(phase); - if (phase != SystemService.PHASE_BOOT_COMPLETED || !hasRealVerifier()) { + if (!hasRealVerifier()) { return; } - verifyPackages(null, false); + switch (phase) { + case PHASE_ACTIVITY_MANAGER_READY: + mCanSendBroadcasts = true; + break; + case PHASE_BOOT_COMPLETED: + verifyPackages(null, false); + break; + } } @Override @@ -284,7 +293,8 @@ public class DomainVerificationService extends SystemService int state) throws NameNotFoundException { if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) { if (state != DomainVerificationState.STATE_SUCCESS) { - return DomainVerificationManager.ERROR_INVALID_STATE_CODE; + throw new IllegalArgumentException( + "Caller is not allowed to set state code " + state); } } @@ -857,7 +867,7 @@ public class DomainVerificationService extends SystemService } if (sendBroadcast) { - sendBroadcastForPackage(pkgName); + sendBroadcast(pkgName); } } @@ -936,7 +946,7 @@ public class DomainVerificationService extends SystemService } if (sendBroadcast && hasAutoVerifyDomains) { - sendBroadcastForPackage(pkgName); + sendBroadcast(pkgName); } } @@ -1097,8 +1107,19 @@ public class DomainVerificationService extends SystemService return mCollector; } - private void sendBroadcastForPackage(@NonNull String packageName) { - mProxy.sendBroadcastForPackages(Collections.singleton(packageName)); + private void sendBroadcast(@NonNull String packageName) { + sendBroadcast(Collections.singleton(packageName)); + } + + private void sendBroadcast(@NonNull Set<String> packageNames) { + if (!mCanSendBroadcasts) { + // If the system cannot send broadcasts, it's probably still in boot, so dropping this + // request should be fine. The verification agent should re-scan packages once boot + // completes. + return; + } + + mProxy.sendBroadcastForPackages(packageNames); } private boolean hasRealVerifier() { @@ -1119,7 +1140,7 @@ public class DomainVerificationService extends SystemService @NonNull Set<String> domains, boolean forAutoVerify, int callingUid, @Nullable Integer userIdForFilter) throws NameNotFoundException { if (domainSetId == null) { - return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL); + throw new IllegalArgumentException("domainSetId cannot be null"); } DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId); @@ -1140,9 +1161,9 @@ public class DomainVerificationService extends SystemService } if (CollectionUtils.isEmpty(domains)) { - return GetAttachedResult.error( - DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY); + throw new IllegalArgumentException("Provided domain set cannot be empty"); } + AndroidPackage pkg = pkgSetting.getPkg(); ArraySet<String> declaredDomains = forAutoVerify ? mCollector.collectValidAutoVerifyDomains(pkg) @@ -1182,7 +1203,7 @@ public class DomainVerificationService extends SystemService } if (!packagesToBroadcast.isEmpty()) { - mProxy.sendBroadcastForPackages(packagesToBroadcast); + sendBroadcast(packagesToBroadcast); } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1b5bb95fd40b..f18546474ece 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2622,23 +2622,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT_OR_SELF); } - float minFloat = mPowerManager.getBrightnessConstraint( + float min = mPowerManager.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - float maxFloat = mPowerManager.getBrightnessConstraint( + float max = mPowerManager.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); - float stepFloat = (maxFloat - minFloat) / BRIGHTNESS_STEPS * direction; - float brightnessFloat = Settings.System.getFloatForUser( - mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_FLOAT, - mContext.getDisplay().getBrightnessDefault(), - UserHandle.USER_CURRENT_OR_SELF); - brightnessFloat += stepFloat; + float step = (max - min) / BRIGHTNESS_STEPS * direction; + int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId; + float brightness = mDisplayManager.getBrightness(screenDisplayId); + brightness += step; // Make sure we don't go beyond the limits. - brightnessFloat = Math.min(maxFloat, brightnessFloat); - brightnessFloat = Math.max(minFloat, brightnessFloat); + brightness = Math.min(max, brightness); + brightness = Math.max(min, brightness); - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessFloat, - UserHandle.USER_CURRENT_OR_SELF); + mDisplayManager.setBrightness(screenDisplayId, brightness); startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), UserHandle.CURRENT_OR_SELF); } diff --git a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java index 2fcd178c3d15..9732eba33c22 100644 --- a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java +++ b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java @@ -248,6 +248,30 @@ public class DisplayGroupPowerStateMapper { return false; } + long getLastUserActivityTimeLocked(int groupId) { + return mDisplayGroupInfos.get(groupId).lastUserActivityTime; + } + + long getLastUserActivityTimeNoChangeLightsLocked(int groupId) { + return mDisplayGroupInfos.get(groupId).lastUserActivityTimeNoChangeLights; + } + + int getUserActivitySummaryLocked(int groupId) { + return mDisplayGroupInfos.get(groupId).userActivitySummary; + } + + void setLastUserActivityTimeLocked(int groupId, long time) { + mDisplayGroupInfos.get(groupId).lastUserActivityTime = time; + } + + void setLastUserActivityTimeNoChangeLightsLocked(int groupId, long time) { + mDisplayGroupInfos.get(groupId).lastUserActivityTimeNoChangeLights = time; + } + + void setUserActivitySummaryLocked(int groupId, int summary) { + mDisplayGroupInfos.get(groupId).userActivitySummary = summary; + } + /** * Interface through which an interested party may be informed of {@link DisplayGroup} events. */ @@ -265,6 +289,9 @@ public class DisplayGroupPowerStateMapper { public boolean ready; public long lastPowerOnTime; public boolean sandmanSummoned; + public long lastUserActivityTime; + public long lastUserActivityTimeNoChangeLights; + public int userActivitySummary; /** {@code true} if this DisplayGroup supports dreaming; otherwise {@code false}. */ public boolean supportsSandman; diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index d2a4cd604c01..54d5b7a8b67e 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -29,6 +29,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; +import static android.os.PowerManagerInternal.wakefulnessToString; import android.annotation.IntDef; import android.annotation.NonNull; @@ -92,6 +93,7 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.Display; +import android.view.DisplayInfo; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; @@ -180,8 +182,8 @@ public final class PowerManagerService extends SystemService private static final int DIRTY_VR_MODE_CHANGED = 1 << 13; // Dirty bit: attentive timer may have timed out private static final int DIRTY_ATTENTIVE = 1 << 14; - // Dirty bit: display group power state has changed - private static final int DIRTY_DISPLAY_GROUP_POWER_UPDATED = 1 << 16; + // Dirty bit: display group wakefulness has changed + private static final int DIRTY_DISPLAY_GROUP_WAKEFULNESS = 1 << 16; // Summarizes the state of all active wakelocks. private static final int WAKE_LOCK_CPU = 1 << 0; @@ -338,10 +340,6 @@ public final class PowerManagerService extends SystemService private @WakeReason int mLastWakeReason; private int mLastSleepReason; - // Timestamp of the last call to user activity. - private long mLastUserActivityTime; - private long mLastUserActivityTimeNoChangeLights; - // Timestamp of last time power boost interaction was sent. private long mLastInteractivePowerHintTime; @@ -349,9 +347,6 @@ public final class PowerManagerService extends SystemService private long mLastScreenBrightnessBoostTime; private boolean mScreenBrightnessBoostInProgress; - // A bitfield that summarizes the effect of the user activity timer. - private int mUserActivitySummary; - // Manages the desired power state of displays. The actual state may lag behind the // requested because it is updated asynchronously by the display power controller. private DisplayGroupPowerStateMapper mDisplayGroupPowerStateMapper; @@ -634,6 +629,13 @@ public final class PowerManagerService extends SystemService public void onDisplayGroupEventLocked(int event, int groupId) { final int oldWakefulness = getWakefulnessLocked(); final int newWakefulness = mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(); + + if (event == DISPLAY_GROUP_ADDED && newWakefulness == WAKEFULNESS_AWAKE) { + // Kick user activity to prevent newly added group from timing out instantly. + userActivityNoUpdateLocked(groupId, mClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, /* flags= */ 0, Process.SYSTEM_UID); + } + if (oldWakefulness != newWakefulness) { final int reason; switch (newWakefulness) { @@ -656,7 +658,7 @@ public final class PowerManagerService extends SystemService mContext.getOpPackageName(), "groupId: " + groupId); } - mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; + mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS; updatePowerStateLocked(); } } @@ -1059,6 +1061,10 @@ public final class PowerManagerService extends SystemService private void onFlip(boolean isFaceDown) { long millisUntilNormalTimeout = 0; synchronized (mLock) { + if (!mBootCompleted) { + return; + } + mIsFaceDown = isFaceDown; if (isFaceDown) { final long currentTime = mClock.uptimeMillis(); @@ -1066,8 +1072,9 @@ public final class PowerManagerService extends SystemService final long sleepTimeout = getSleepTimeoutLocked(-1L); final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, -1L); millisUntilNormalTimeout = - mLastUserActivityTime + screenOffTimeout - mClock.uptimeMillis(); - userActivityInternal(mClock.uptimeMillis(), + mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked( + Display.DEFAULT_DISPLAY_GROUP) + screenOffTimeout - currentTime; + userActivityInternal(Display.DEFAULT_DISPLAY, currentTime, PowerManager.USER_ACTIVITY_EVENT_FACE_DOWN, /* flags= */0, Process.SYSTEM_UID); } @@ -1645,12 +1652,28 @@ public final class PowerManagerService extends SystemService // Called from native code. private void userActivityFromNative(long eventTime, int event, int displayId, int flags) { - userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID); + userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID); } - private void userActivityInternal(long eventTime, int event, int flags, int uid) { + private void userActivityInternal(int displayId, long eventTime, int event, int flags, + int uid) { synchronized (mLock) { - if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) { + if (displayId == Display.INVALID_DISPLAY) { + if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) { + updatePowerStateLocked(); + } + return; + } + + final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(displayId); + if (displayInfo == null) { + return; + } + final int groupId = displayInfo.displayGroupId; + if (groupId == Display.INVALID_DISPLAY_GROUP) { + return; + } + if (userActivityNoUpdateLocked(groupId, eventTime, event, flags, uid)) { updatePowerStateLocked(); } } @@ -1658,7 +1681,7 @@ public final class PowerManagerService extends SystemService private void onUserAttention() { synchronized (mLock) { - if (userActivityNoUpdateLocked(mClock.uptimeMillis(), + if (userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */, Process.SYSTEM_UID)) { updatePowerStateLocked(); @@ -1667,10 +1690,22 @@ public final class PowerManagerService extends SystemService } private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { + boolean updatePowerState = false; + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + if (userActivityNoUpdateLocked(id, eventTime, event, flags, uid)) { + updatePowerState = true; + } + } + + return updatePowerState; + } + + private boolean userActivityNoUpdateLocked(int groupId, long eventTime, int event, int flags, + int uid) { if (DEBUG_SPEW) { - Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime - + ", event=" + event + ", flags=0x" + Integer.toHexString(flags) - + ", uid=" + uid); + Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId + + ", eventTime=" + eventTime + ", event=" + event + + ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid); } if (eventTime < mLastSleepTime || eventTime < mLastWakeTime || !mSystemReady) { @@ -1692,8 +1727,9 @@ public final class PowerManagerService extends SystemService mOverriddenTimeout = -1; } - if (getWakefulnessLocked() == WAKEFULNESS_ASLEEP - || getWakefulnessLocked() == WAKEFULNESS_DOZING + final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); + if (wakefulness == WAKEFULNESS_ASLEEP + || wakefulness == WAKEFULNESS_DOZING || (flags & PowerManager.USER_ACTIVITY_FLAG_INDIRECT) != 0) { return false; } @@ -1701,9 +1737,13 @@ public final class PowerManagerService extends SystemService maybeUpdateForegroundProfileLastActivityLocked(eventTime); if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { - if (eventTime > mLastUserActivityTimeNoChangeLights - && eventTime > mLastUserActivityTime) { - mLastUserActivityTimeNoChangeLights = eventTime; + if (eventTime + > mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked( + groupId) + && eventTime > mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked( + groupId)) { + mDisplayGroupPowerStateMapper.setLastUserActivityTimeNoChangeLightsLocked( + groupId, eventTime); mDirty |= DIRTY_USER_ACTIVITY; if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { mDirty |= DIRTY_QUIESCENT; @@ -1712,8 +1752,9 @@ public final class PowerManagerService extends SystemService return true; } } else { - if (eventTime > mLastUserActivityTime) { - mLastUserActivityTime = eventTime; + if (eventTime > mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked( + groupId)) { + mDisplayGroupPowerStateMapper.setLastUserActivityTimeLocked(groupId, eventTime); mDirty |= DIRTY_USER_ACTIVITY; if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { mDirty |= DIRTY_QUIESCENT; @@ -1778,7 +1819,6 @@ public final class PowerManagerService extends SystemService setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid, opPackageName, details); mDisplayGroupPowerStateMapper.setLastPowerOnTimeLocked(groupId, eventTime); - mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -1829,7 +1869,6 @@ public final class PowerManagerService extends SystemService if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid); } - mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -1862,7 +1901,6 @@ public final class PowerManagerService extends SystemService mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true); setWakefulnessLocked(groupId, WAKEFULNESS_DREAMING, eventTime, uid, /* reason= */ 0, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null); - mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); @@ -1890,7 +1928,6 @@ public final class PowerManagerService extends SystemService setWakefulnessLocked(groupId, WAKEFULNESS_ASLEEP, eventTime, uid, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null); - mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED; } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -1901,8 +1938,14 @@ public final class PowerManagerService extends SystemService void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason, int opUid, String opPackageName, String details) { if (mDisplayGroupPowerStateMapper.setWakefulnessLocked(groupId, wakefulness)) { + mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS; setGlobalWakefulnessLocked(mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(), eventTime, reason, uid, opUid, opPackageName, details); + if (wakefulness == WAKEFULNESS_AWAKE) { + // Kick user activity to prevent newly awake group from timing out instantly. + userActivityNoUpdateLocked( + groupId, eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); + } } } @@ -1972,8 +2015,6 @@ public final class PowerManagerService extends SystemService switch (wakefulness) { case WAKEFULNESS_AWAKE: mNotifier.onWakeUp(reason, details, uid, opPackageName, opUid); - userActivityNoUpdateLocked( - eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); if (sQuiescent) { mDirty |= DIRTY_QUIESCENT; } @@ -2163,8 +2204,9 @@ public final class PowerManagerService extends SystemService "android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID, mContext.getOpPackageName(), Process.SYSTEM_UID); } - userActivityNoUpdateLocked( - now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + + userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, now, + PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); // only play charging sounds if boot is completed so charging sounds don't play // with potential notification sounds @@ -2407,106 +2449,123 @@ public final class PowerManagerService extends SystemService */ private void updateUserActivitySummaryLocked(long now, int dirty) { // Update the status of the user activity timeout timer. - if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY - | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) { - mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); - - long nextTimeout = 0; - if (getWakefulnessLocked() == WAKEFULNESS_AWAKE - || getWakefulnessLocked() == WAKEFULNESS_DREAMING - || getWakefulnessLocked() == WAKEFULNESS_DOZING) { - final long attentiveTimeout = getAttentiveTimeoutLocked(); - final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout); - long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, - attentiveTimeout); - final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); - screenOffTimeout = - getScreenOffTimeoutWithFaceDownLocked(screenOffTimeout, screenDimDuration); - final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager; - final long nextProfileTimeout = getNextProfileTimeoutLocked(now); - - mUserActivitySummary = 0; - if (mLastUserActivityTime >= mLastWakeTime) { - nextTimeout = mLastUserActivityTime - + screenOffTimeout - screenDimDuration; - if (now < nextTimeout) { - mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; + if ((dirty & (DIRTY_DISPLAY_GROUP_WAKEFULNESS | DIRTY_WAKE_LOCKS + | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) == 0) { + return; + } + mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); + + final long attentiveTimeout = getAttentiveTimeoutLocked(); + final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout); + long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, + attentiveTimeout); + final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); + screenOffTimeout = + getScreenOffTimeoutWithFaceDownLocked(screenOffTimeout, screenDimDuration); + final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager; + long nextTimeout = -1; + boolean hasUserActivitySummary = false; + for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + int groupUserActivitySummary = 0; + long groupNextTimeout = 0; + if (mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != WAKEFULNESS_ASLEEP) { + final long lastUserActivityTime = + mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(groupId); + final long lastUserActivityTimeNoChangeLights = + mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked( + groupId); + if (lastUserActivityTime >= mLastWakeTime) { + groupNextTimeout = lastUserActivityTime + screenOffTimeout - screenDimDuration; + if (now < groupNextTimeout) { + groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; } else { - nextTimeout = mLastUserActivityTime + screenOffTimeout; - if (now < nextTimeout) { - mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; + groupNextTimeout = lastUserActivityTime + screenOffTimeout; + if (now < groupNextTimeout) { + groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; } } } - if (mUserActivitySummary == 0 - && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) { - nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout; - if (now < nextTimeout) { + if (groupUserActivitySummary == 0 + && lastUserActivityTimeNoChangeLights >= mLastWakeTime) { + groupNextTimeout = lastUserActivityTimeNoChangeLights + screenOffTimeout; + if (now < groupNextTimeout) { final DisplayPowerRequest displayPowerRequest = - mDisplayGroupPowerStateMapper.getPowerRequestLocked( - Display.DEFAULT_DISPLAY_GROUP); + mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId); if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT || displayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) { - mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; + groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; } else if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { - mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; + groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; } } } - if (mUserActivitySummary == 0) { + if (groupUserActivitySummary == 0) { if (sleepTimeout >= 0) { - final long anyUserActivity = Math.max(mLastUserActivityTime, - mLastUserActivityTimeNoChangeLights); + final long anyUserActivity = Math.max(lastUserActivityTime, + lastUserActivityTimeNoChangeLights); if (anyUserActivity >= mLastWakeTime) { - nextTimeout = anyUserActivity + sleepTimeout; - if (now < nextTimeout) { - mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; + groupNextTimeout = anyUserActivity + sleepTimeout; + if (now < groupNextTimeout) { + groupUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; } } } else { - mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; - nextTimeout = -1; + groupUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; + groupNextTimeout = -1; } } - if (mUserActivitySummary != USER_ACTIVITY_SCREEN_DREAM && userInactiveOverride) { - if ((mUserActivitySummary & + if (groupUserActivitySummary != USER_ACTIVITY_SCREEN_DREAM + && userInactiveOverride) { + if ((groupUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0) { // Device is being kept awake by recent user activity - if (nextTimeout >= now && mOverriddenTimeout == -1) { + if (mOverriddenTimeout == -1) { // Save when the next timeout would have occurred - mOverriddenTimeout = nextTimeout; + mOverriddenTimeout = groupNextTimeout; } } - mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; - nextTimeout = -1; + groupUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM; + groupNextTimeout = -1; } - if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 + if ((groupUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 && (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) == 0) { - nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout, + groupNextTimeout = mAttentionDetector.updateUserActivity(groupNextTimeout, screenDimDuration); } - if (nextProfileTimeout > 0) { - nextTimeout = Math.min(nextTimeout, nextProfileTimeout); - } + hasUserActivitySummary |= groupUserActivitySummary != 0; - if (mUserActivitySummary != 0 && nextTimeout >= 0) { - scheduleUserInactivityTimeout(nextTimeout); + if (nextTimeout == -1) { + nextTimeout = groupNextTimeout; + } else if (groupNextTimeout != -1) { + nextTimeout = Math.min(nextTimeout, groupNextTimeout); } - } else { - mUserActivitySummary = 0; } + mDisplayGroupPowerStateMapper.setUserActivitySummaryLocked(groupId, + groupUserActivitySummary); + if (DEBUG_SPEW) { - Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness=" - + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked()) - + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) - + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout)); + Slog.d(TAG, "updateUserActivitySummaryLocked: groupId=" + groupId + + ", mWakefulness=" + wakefulnessToString( + mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId)) + + ", mUserActivitySummary=0x" + Integer.toHexString( + groupUserActivitySummary) + + ", nextTimeout=" + TimeUtils.formatUptime(groupNextTimeout)); } } + + final long nextProfileTimeout = getNextProfileTimeoutLocked(now); + if (nextProfileTimeout > 0) { + nextTimeout = Math.min(nextTimeout, nextProfileTimeout); + } + + if (hasUserActivitySummary && nextTimeout >= 0) { + scheduleUserInactivityTimeout(nextTimeout); + } } private void scheduleUserInactivityTimeout(long timeMs) { @@ -2539,15 +2598,20 @@ public final class PowerManagerService extends SystemService private void updateAttentiveStateLocked(long now, int dirty) { long attentiveTimeout = getAttentiveTimeoutLocked(); - long goToSleepTime = mLastUserActivityTime + attentiveTimeout; + if (attentiveTimeout < 0) { + return; + } + // Attentive state only applies to the default display group. + long goToSleepTime = mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked( + Display.DEFAULT_DISPLAY_GROUP) + attentiveTimeout; long showWarningTime = goToSleepTime - mAttentiveWarningDurationConfig; boolean warningDismissed = maybeHideInattentiveSleepWarningLocked(now, showWarningTime); - if (attentiveTimeout >= 0 && (warningDismissed - || (dirty & (DIRTY_ATTENTIVE | DIRTY_STAY_ON | DIRTY_SCREEN_BRIGHTNESS_BOOST - | DIRTY_PROXIMITY_POSITIVE | DIRTY_WAKEFULNESS | DIRTY_BOOT_COMPLETED - | DIRTY_SETTINGS)) != 0)) { + if (warningDismissed || + (dirty & (DIRTY_ATTENTIVE | DIRTY_STAY_ON | DIRTY_SCREEN_BRIGHTNESS_BOOST + | DIRTY_PROXIMITY_POSITIVE | DIRTY_WAKEFULNESS | DIRTY_BOOT_COMPLETED + | DIRTY_SETTINGS)) != 0) { if (DEBUG_SPEW) { Slog.d(TAG, "Updating attentive state"); } @@ -2601,9 +2665,12 @@ public final class PowerManagerService extends SystemService return false; } - private boolean isAttentiveTimeoutExpired(long now) { + private boolean isAttentiveTimeoutExpired(int groupId, long now) { long attentiveTimeout = getAttentiveTimeoutLocked(); - return attentiveTimeout >= 0 && now >= mLastUserActivityTime + attentiveTimeout; + // Attentive state only applies to the default display group. + return groupId == Display.DEFAULT_DISPLAY_GROUP && attentiveTimeout >= 0 + && now >= mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(groupId) + + attentiveTimeout; } /** @@ -2703,26 +2770,20 @@ public final class PowerManagerService extends SystemService | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) { - if (getWakefulnessLocked() == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) { - if (DEBUG_SPEW) { - Slog.d(TAG, "updateWakefulnessLocked: Bed time..."); - } - final long time = mClock.uptimeMillis(); - if (isAttentiveTimeoutExpired(time)) { - // TODO (b/175764389): Support per-display timeouts. - for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + final long time = mClock.uptimeMillis(); + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + if (mDisplayGroupPowerStateMapper.getWakefulnessLocked(id) == WAKEFULNESS_AWAKE + && isItBedTimeYetLocked(id)) { + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakefulnessLocked: Bed time for group " + id); + } + if (isAttentiveTimeoutExpired(id, time)) { changed = sleepDisplayGroupNoUpdateLocked(id, time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID); - } - } else if (shouldNapAtBedTimeLocked()) { - // TODO (b/175764389): Support per-display timeouts. - for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + } else if (shouldNapAtBedTimeLocked()) { changed = dreamDisplayGroupNoUpdateLocked(id, time, Process.SYSTEM_UID); - } - } else { - // TODO (b/175764389): Support per-display timeouts. - for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + } else { changed = sleepDisplayGroupNoUpdateLocked(id, time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID); } @@ -2743,51 +2804,50 @@ public final class PowerManagerService extends SystemService } /** - * Returns true if the device should go to sleep now. - * Also used when exiting a dream to determine whether we should go back - * to being fully awake or else go to sleep for good. + * Returns true if the DisplayGroup with the provided {@code groupId} should go to sleep now. + * Also used when exiting a dream to determine whether we should go back to being fully awake or + * else go to sleep for good. */ - private boolean isItBedTimeYetLocked() { + private boolean isItBedTimeYetLocked(int groupId) { if (!mBootCompleted) { return false; } long now = mClock.uptimeMillis(); - if (isAttentiveTimeoutExpired(now)) { - return !isBeingKeptFromInattentiveSleepLocked(); + if (isAttentiveTimeoutExpired(groupId, now)) { + return !isBeingKeptFromInattentiveSleepLocked(groupId); } else { - return !isBeingKeptAwakeLocked(); + return !isBeingKeptAwakeLocked(groupId); } } /** - * Returns true if the device is being kept awake by a wake lock, user activity - * or the stay on while powered setting. We also keep the phone awake when - * the proximity sensor returns a positive result so that the device does not - * lock while in a phone call. This function only controls whether the device - * will go to sleep or dream which is independent of whether it will be allowed - * to suspend. + * Returns true if the DisplayGroup with the provided {@code groupId} is being kept awake by a + * wake lock, user activity or the stay on while powered setting. We also keep the phone awake + * when the proximity sensor returns a positive result so that the device does not lock while in + * a phone call. This function only controls whether the device will go to sleep or dream which + * is independent of whether it will be allowed to suspend. */ - private boolean isBeingKeptAwakeLocked() { + private boolean isBeingKeptAwakeLocked(int groupId) { return mStayOn || mProximityPositive || (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0 - || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT - | USER_ACTIVITY_SCREEN_DIM)) != 0 + || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & ( + USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0 || mScreenBrightnessBoostInProgress; } /** - * Returns true if the device is prevented from going into inattentive sleep by the stay on - * while powered setting. We also keep the device awake when the proximity sensor returns a - * positive result so that the device does not lock while in a phone call. This function only - * controls whether the device will go to sleep which is independent of whether it will be - * allowed to suspend. + * Returns true if the DisplayGroup with the provided {@code groupId} is prevented from going + * into inattentive sleep by the stay on while powered setting. We also keep the device awake + * when the proximity sensor returns a positive result so that the device does not lock while in + * a phone call. This function only controls whether the device will go to sleep which is + * independent of whether it will be allowed to suspend. */ - private boolean isBeingKeptFromInattentiveSleepLocked() { + private boolean isBeingKeptFromInattentiveSleepLocked(int groupId) { return mStayOn || mScreenBrightnessBoostInProgress || mProximityPositive - || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT - | USER_ACTIVITY_SCREEN_DIM)) != 0; + || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & ( + USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0; } private boolean isBeingKeptFromShowingInattentiveSleepWarningLocked() { @@ -2902,7 +2962,7 @@ public final class PowerManagerService extends SystemService if (mDreamsBatteryLevelDrainCutoffConfig >= 0 && mBatteryLevel < mBatteryLevelWhenDreamStarted - mDreamsBatteryLevelDrainCutoffConfig - && !isBeingKeptAwakeLocked()) { + && !isBeingKeptAwakeLocked(groupId)) { // If the user activity timeout expired and the battery appears // to be draining faster than it is charging then stop dreaming // and go to sleep. @@ -2917,18 +2977,17 @@ public final class PowerManagerService extends SystemService } // Dream has ended or will be stopped. Update the power state. - if (isItBedTimeYetLocked()) { - final int flags = isAttentiveTimeoutExpired(now) + if (isItBedTimeYetLocked(groupId)) { + final int flags = isAttentiveTimeoutExpired(groupId, now) ? PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE : 0; sleepDisplayGroupNoUpdateLocked(groupId, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags, Process.SYSTEM_UID); - updatePowerStateLocked(); } else { wakeDisplayGroupNoUpdateLocked(groupId, now, PowerManager.WAKE_REASON_UNKNOWN, "android.server.power:DREAM_FINISHED", Process.SYSTEM_UID, mContext.getOpPackageName(), Process.SYSTEM_UID); - updatePowerStateLocked(); } + updatePowerStateLocked(); } else if (wakefulness == WAKEFULNESS_DOZING) { if (isDreaming) { return; // continue dozing @@ -2952,17 +3011,18 @@ public final class PowerManagerService extends SystemService private boolean canDreamLocked(int groupId) { final DisplayPowerRequest displayPowerRequest = mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId); - if (getWakefulnessLocked() != WAKEFULNESS_DREAMING + if (!mBootCompleted + || getWakefulnessLocked() != WAKEFULNESS_DREAMING || !mDreamsSupportedConfig || !mDreamsEnabledSetting || !displayPowerRequest.isBrightOrDim() || displayPowerRequest.isVr() - || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT - | USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0 - || !mBootCompleted) { + || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & ( + USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM + | USER_ACTIVITY_SCREEN_DREAM)) == 0) { return false; } - if (!isBeingKeptAwakeLocked()) { + if (!isBeingKeptAwakeLocked(groupId)) { if (!mIsPowered && !mDreamsEnabledOnBatteryConfig) { return false; } @@ -2971,11 +3031,9 @@ public final class PowerManagerService extends SystemService && mBatteryLevel < mDreamsBatteryLevelMinimumWhenNotPoweredConfig) { return false; } - if (mIsPowered - && mDreamsBatteryLevelMinimumWhenPoweredConfig >= 0 - && mBatteryLevel < mDreamsBatteryLevelMinimumWhenPoweredConfig) { - return false; - } + return !mIsPowered + || mDreamsBatteryLevelMinimumWhenPoweredConfig < 0 + || mBatteryLevel >= mDreamsBatteryLevelMinimumWhenPoweredConfig; } return true; } @@ -3002,7 +3060,7 @@ public final class PowerManagerService extends SystemService if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED | - DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_POWER_UPDATED)) != 0) { + DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) != 0) { if ((dirty & DIRTY_QUIESCENT) != 0) { if (mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) { sQuiescent = false; @@ -3072,7 +3130,7 @@ public final class PowerManagerService extends SystemService mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId)) + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + ", mUserActivitySummary=0x" + Integer.toHexString( - mUserActivitySummary) + mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId)) + ", mBootCompleted=" + mBootCompleted + ", screenBrightnessOverride=" + displayPowerRequest.screenBrightnessOverride @@ -3156,8 +3214,9 @@ public final class PowerManagerService extends SystemService } if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0 - || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 || !mBootCompleted + || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) + & USER_ACTIVITY_SCREEN_BRIGHT) != 0 || mScreenBrightnessBoostInProgress) { return DisplayPowerRequest.POLICY_BRIGHT; } @@ -3190,7 +3249,7 @@ public final class PowerManagerService extends SystemService synchronized (mLock) { mProximityPositive = false; mDirty |= DIRTY_PROXIMITY_POSITIVE; - userActivityNoUpdateLocked(mClock.uptimeMillis(), + userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); updatePowerStateLocked(); } @@ -3758,7 +3817,7 @@ public final class PowerManagerService extends SystemService mScreenBrightnessBoostInProgress = true; mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST; - userActivityNoUpdateLocked(eventTime, + userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); updatePowerStateLocked(); } @@ -3863,14 +3922,16 @@ public final class PowerManagerService extends SystemService @VisibleForTesting boolean wasDeviceIdleForInternal(long ms) { synchronized (mLock) { - return mLastUserActivityTime + ms < mClock.uptimeMillis(); + return mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked( + Display.DEFAULT_DISPLAY_GROUP) + ms < mClock.uptimeMillis(); } } @VisibleForTesting void onUserActivity() { synchronized (mLock) { - mLastUserActivityTime = mClock.uptimeMillis(); + mDisplayGroupPowerStateMapper.setLastUserActivityTimeLocked( + Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis()); } } @@ -4024,7 +4085,6 @@ public final class PowerManagerService extends SystemService TimeUtils.formatDuration(mNotifyLongNextCheck, mClock.uptimeMillis(), pw); } pw.println(); - pw.println(" mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)); pw.println(" mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity); pw.println(" mSandmanScheduled=" + mSandmanScheduled); pw.println(" mBatteryLevelLow=" + mBatteryLevelLow); @@ -4035,9 +4095,6 @@ public final class PowerManagerService extends SystemService pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime)); pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime)); pw.println(" mLastSleepReason=" + PowerManager.sleepReasonToString(mLastSleepReason)); - pw.println(" mLastUserActivityTime=" + TimeUtils.formatUptime(mLastUserActivityTime)); - pw.println(" mLastUserActivityTimeNoChangeLights=" - + TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights)); pw.println(" mLastInteractivePowerHintTime=" + TimeUtils.formatUptime(mLastInteractivePowerHintTime)); pw.println(" mLastScreenBrightnessBoostTime=" @@ -4181,6 +4238,18 @@ public final class PowerManagerService extends SystemService pw.println(profile.mLockingNotified); } + pw.println("Display Group User Activity:"); + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + pw.println(" displayGroupId=" + id); + pw.println(" userActivitySummary=0x" + Integer.toHexString( + mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(id))); + pw.println(" lastUserActivityTime=" + TimeUtils.formatUptime( + mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(id))); + pw.println(" lastUserActivityTimeNoChangeLights=" + TimeUtils.formatUptime( + mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked( + id))); + } + wcd = mWirelessChargerDetector; } @@ -4266,17 +4335,27 @@ public final class PowerManagerService extends SystemService proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched); proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck); - final long userActivityToken = proto.start(PowerManagerServiceDumpProto.USER_ACTIVITY); - proto.write( - PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT, - (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0); - proto.write( - PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM, - (mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0); - proto.write( - PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM, - (mUserActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0); - proto.end(userActivityToken); + for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { + final long userActivityToken = proto.start( + PowerManagerServiceDumpProto.USER_ACTIVITY); + proto.write(PowerManagerServiceDumpProto.UserActivityProto.DISPLAY_GROUP_ID, id); + final long userActivitySummary = + mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(id); + proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT, + (userActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0); + proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM, + (userActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0); + proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM, + (userActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0); + proto.write( + PowerManagerServiceDumpProto.UserActivityProto.LAST_USER_ACTIVITY_TIME_MS, + mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(id)); + proto.write( + PowerManagerServiceDumpProto.UserActivityProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS, + mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked( + id)); + proto.end(userActivityToken); + } proto.write( PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY, @@ -4295,10 +4374,6 @@ public final class PowerManagerService extends SystemService proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime); proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime); - proto.write(PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime); - proto.write( - PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS, - mLastUserActivityTimeNoChangeLights); proto.write( PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS, mLastInteractivePowerHintTime); @@ -5102,7 +5177,7 @@ public final class PowerManagerService extends SystemService final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - userActivityInternal(eventTime, event, flags, uid); + userActivityInternal(displayId, eventTime, event, flags, uid); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java index beebb3145018..fe21201f5cb7 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java @@ -21,11 +21,13 @@ import static android.os.UserHandle.USER_SYSTEM; import android.annotation.IntDef; import android.content.Context; import android.content.IntentSender; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.boot.V1_0.IBootControl; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Binder; +import android.os.Environment; import android.os.IRecoverySystem; import android.os.IRecoverySystemProgressListener; import android.os.PowerManager; @@ -52,6 +54,7 @@ import libcore.io.IoUtils; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; import java.io.FileDescriptor; import java.io.FileWriter; import java.io.IOException; @@ -87,6 +90,12 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo private static final int SOCKET_CONNECTION_MAX_RETRY = 30; + static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp"; + static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count"; + + static final String LSKF_CAPTURED_TIMESTAMP_PREF = "lskf_captured_timestamp"; + static final String LSKF_CAPTURED_COUNT_PREF = "lskf_captured_count"; + private final Injector mInjector; private final Context mContext; @@ -127,7 +136,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo */ @IntDef({ ROR_NEED_PREPARATION, ROR_SKIP_PREPARATION_AND_NOTIFY, - ROR_SKIP_PREPARATION_NOT_NOTIFY }) + ROR_SKIP_PREPARATION_NOT_NOTIFY}) private @interface ResumeOnRebootActionsOnRequest {} /** @@ -139,7 +148,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo private @interface ResumeOnRebootActionsOnClear {} /** - * The error code for reboots initiated by resume on reboot clients. + * The error codes for reboots initiated by resume on reboot clients. */ private static final int REBOOT_ERROR_NONE = 0; private static final int REBOOT_ERROR_UNKNOWN = 1; @@ -156,11 +165,64 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE}) private @interface ResumeOnRebootRebootErrorCode {} + /** + * Manages shared preference, i.e. the storage used for metrics reporting. + */ + public static class PreferencesManager { + private static final String METRICS_DIR = "recovery_system"; + private static final String METRICS_PREFS_FILE = "RecoverySystemMetricsPrefs.xml"; + + protected final SharedPreferences mSharedPreferences; + private final File mMetricsPrefsFile; + + PreferencesManager(Context context) { + File prefsDir = new File(Environment.getDataSystemCeDirectory(USER_SYSTEM), + METRICS_DIR); + mMetricsPrefsFile = new File(prefsDir, METRICS_PREFS_FILE); + mSharedPreferences = context.getSharedPreferences(mMetricsPrefsFile, 0); + } + + /** Reads the value of a given key with type long. **/ + public long getLong(String key, long defaultValue) { + return mSharedPreferences.getLong(key, defaultValue); + } + + /** Reads the value of a given key with type int. **/ + public int getInt(String key, int defaultValue) { + return mSharedPreferences.getInt(key, defaultValue); + } + + /** Stores the value of a given key with type long. **/ + public void putLong(String key, long value) { + mSharedPreferences.edit().putLong(key, value).commit(); + } + + /** Stores the value of a given key with type int. **/ + public void putInt(String key, int value) { + mSharedPreferences.edit().putInt(key, value).commit(); + } + + /** Increments the value of a given key with type int. **/ + public synchronized void incrementIntKey(String key, int defaultInitialValue) { + int oldValue = getInt(key, defaultInitialValue); + putInt(key, oldValue + 1); + } + + /** Delete the preference file and cleanup all metrics storage. **/ + public void deletePrefsFile() { + if (!mMetricsPrefsFile.delete()) { + Slog.w(TAG, "Failed to delete metrics prefs"); + } + } + } + static class Injector { protected final Context mContext; + protected final PreferencesManager mPrefs; Injector(Context context) { mContext = context; + mPrefs = new PreferencesManager(context); } public Context getContext() { @@ -236,6 +298,14 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return -1; } + public PreferencesManager getMetricsPrefs() { + return mPrefs; + } + + public long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + public void reportRebootEscrowPreparationMetrics(int uid, @ResumeOnRebootActionsOnRequest int requestResult, int requestedClientCount) { FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_PREPARATION_REPORTED, uid, @@ -414,7 +484,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY) != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(android.Manifest.permission.REBOOT) - != PackageManager.PERMISSION_GRANTED) { + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Caller must have " + android.Manifest.permission.RECOVERY + " or " + android.Manifest.permission.REBOOT + " for resume on reboot."); } @@ -427,6 +497,12 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo pendingRequestCount = mCallerPendingRequest.size(); } + // Save the timestamp and request count for new ror request + PreferencesManager prefs = mInjector.getMetricsPrefs(); + prefs.putLong(packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX, + mInjector.getCurrentTimeMillis()); + prefs.incrementIntKey(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, 0); + mInjector.reportRebootEscrowPreparationMetrics(uid, requestResult, pendingRequestCount); } @@ -486,15 +562,31 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo } private void reportMetricsOnPreparedForReboot() { + long currentTimestamp = mInjector.getCurrentTimeMillis(); + List<String> preparedClients; synchronized (this) { preparedClients = new ArrayList<>(mCallerPreparedForReboot); } + // Save the timestamp & lskf capture count for lskf capture + PreferencesManager prefs = mInjector.getMetricsPrefs(); + prefs.putLong(LSKF_CAPTURED_TIMESTAMP_PREF, currentTimestamp); + prefs.incrementIntKey(LSKF_CAPTURED_COUNT_PREF, 0); + for (String packageName : preparedClients) { int uid = mInjector.getUidFromPackageName(packageName); + + int durationSeconds = -1; + long requestLskfTimestamp = prefs.getLong( + packageName + REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX, -1); + if (requestLskfTimestamp != -1 && currentTimestamp > requestLskfTimestamp) { + durationSeconds = (int) (currentTimestamp - requestLskfTimestamp) / 1000; + } + Slog.i(TAG, String.format("Reporting lskf captured, lskf capture takes %d seconds for" + + " package %s", durationSeconds, packageName)); mInjector.reportRebootEscrowLskfCapturedMetrics(uid, preparedClients.size(), - -1 /* duration */); + durationSeconds); } } @@ -541,6 +633,7 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo Slog.w(TAG, "Missing packageName when clearing lskf."); return false; } + // TODO(179105110) Clear the RoR metrics for the given packageName. @ResumeOnRebootActionsOnClear int action = updateRoRPreparationStateOnClear(packageName); switch (action) { @@ -641,7 +734,15 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return REBOOT_ERROR_SLOT_MISMATCH; } - if (!mInjector.getLockSettingsService().armRebootEscrow()) { + final long origId = Binder.clearCallingIdentity(); + boolean result; + try { + result = mInjector.getLockSettingsService().armRebootEscrow(); + } finally { + Binder.restoreCallingIdentity(origId); + } + + if (!result) { Slog.w(TAG, "Failure to escrow key for reboot"); return REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE; } @@ -649,20 +750,42 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return REBOOT_ERROR_NONE; } + private boolean useServerBasedRoR() { + final long origId = Binder.clearCallingIdentity(); + try { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, + "server_based_ror_enabled", false); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + private void reportMetricsOnRebootWithLskf(String packageName, boolean slotSwitch, @ResumeOnRebootRebootErrorCode int errorCode) { int uid = mInjector.getUidFromPackageName(packageName); - boolean serverBased = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, - "server_based_ror_enabled", false); + boolean serverBased = useServerBasedRoR(); int preparedClientCount; synchronized (this) { preparedClientCount = mCallerPreparedForReboot.size(); } - // TODO(b/179105110) report the true value of duration and counts + long currentTimestamp = mInjector.getCurrentTimeMillis(); + int durationSeconds = -1; + PreferencesManager prefs = mInjector.getMetricsPrefs(); + long lskfCapturedTimestamp = prefs.getLong(LSKF_CAPTURED_TIMESTAMP_PREF, -1); + if (lskfCapturedTimestamp != -1 && currentTimestamp > lskfCapturedTimestamp) { + durationSeconds = (int) (currentTimestamp - lskfCapturedTimestamp) / 1000; + } + + int requestCount = prefs.getInt(packageName + REQUEST_LSKF_COUNT_PREF_SUFFIX, -1); + int lskfCapturedCount = prefs.getInt(LSKF_CAPTURED_COUNT_PREF, -1); + + Slog.i(TAG, String.format("Reporting reboot with lskf, package name %s, client count %d," + + " request count %d, lskf captured count %d, duration since lskf captured" + + " %d seconds.", packageName, preparedClientCount, requestCount, + lskfCapturedCount, durationSeconds)); mInjector.reportRebootEscrowRebootMetrics(errorCode, uid, preparedClientCount, - 1 /* request count */, slotSwitch, serverBased, - -1 /* duration */, 1 /* lskf capture count */); + requestCount, slotSwitch, serverBased, durationSeconds, lskfCapturedCount); } private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) { @@ -673,6 +796,9 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo return false; } + // Clear the metrics prefs after a successful RoR reboot. + mInjector.getMetricsPrefs().deletePrefsFile(); + PowerManager pm = mInjector.getPowerManager(); pm.reboot(reason); return true; diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java index 23b5d98de59f..030bbd2bc652 100644 --- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java +++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java @@ -176,7 +176,7 @@ public interface ServiceWatcher { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof BoundServiceInfo)) { return false; } diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java index 7757a7a096db..e718ba3b17cf 100644 --- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java +++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java @@ -147,13 +147,23 @@ class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements @Override public String toString() { - return mServiceConnection.getBoundServiceInfo().toString(); + MyServiceConnection serviceConnection; + synchronized (this) { + serviceConnection = mServiceConnection; + } + + return serviceConnection.getBoundServiceInfo().toString(); } @Override public void dump(PrintWriter pw) { - pw.println("target service=" + mServiceConnection.getBoundServiceInfo()); - pw.println("connected=" + mServiceConnection.isConnected()); + MyServiceConnection serviceConnection; + synchronized (this) { + serviceConnection = mServiceConnection; + } + + pw.println("target service=" + serviceConnection.getBoundServiceInfo()); + pw.println("connected=" + serviceConnection.isConnected()); } // runs on the handler thread, and expects most of it's methods to be called from that thread diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java index dedb3ac6397d..ff6c2f551bb3 100644 --- a/services/core/java/com/android/server/storage/StorageSessionController.java +++ b/services/core/java/com/android/server/storage/StorageSessionController.java @@ -27,11 +27,13 @@ import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.os.IVold; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; +import android.os.UserManager; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; @@ -53,6 +55,7 @@ public final class StorageSessionController { private final Object mLock = new Object(); private final Context mContext; + private final UserManager mUserManager; @GuardedBy("mLock") private final SparseArray<StorageUserConnection> mConnections = new SparseArray<>(); @@ -63,6 +66,30 @@ public final class StorageSessionController { public StorageSessionController(Context context) { mContext = Objects.requireNonNull(context); + mUserManager = mContext.getSystemService(UserManager.class); + } + + /** + * Returns userId for the volume to be used in the StorageUserConnection. + * If the user is a clone profile, it will use the same connection + * as the parent user, and hence this method returns the parent's userId. Else, it returns the + * volume's mountUserId + * @param vol for which the storage session has to be started + * @return userId for connection for this volume + */ + public int getConnectionUserIdForVolume(VolumeInfo vol) { + final Context volumeUserContext = mContext.createContextAsUser( + UserHandle.of(vol.mountUserId), 0); + boolean sharesMediaWithParent = volumeUserContext.getSystemService( + UserManager.class).sharesMediaWithParent(); + + UserInfo userInfo = mUserManager.getUserInfo(vol.mountUserId); + if (userInfo != null && sharesMediaWithParent) { + // Clones use the same connection as their parent + return userInfo.profileGroupId; + } else { + return vol.mountUserId; + } } /** @@ -88,7 +115,7 @@ public final class StorageSessionController { Slog.i(TAG, "On volume mount " + vol); String sessionId = vol.getId(); - int userId = vol.getMountUserId(); + int userId = getConnectionUserIdForVolume(vol); StorageUserConnection connection = null; synchronized (mLock) { @@ -120,7 +147,7 @@ public final class StorageSessionController { return; } String sessionId = vol.getId(); - int userId = vol.getMountUserId(); + int userId = getConnectionUserIdForVolume(vol); StorageUserConnection connection = null; synchronized (mLock) { @@ -191,7 +218,7 @@ public final class StorageSessionController { Slog.i(TAG, "On volume remove " + vol); String sessionId = vol.getId(); - int userId = vol.getMountUserId(); + int userId = getConnectionUserIdForVolume(vol); synchronized (mLock) { StorageUserConnection connection = mConnections.get(userId); diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index a0e2286f72e2..0b11b0b6d212 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -265,6 +265,10 @@ public final class StorageUserConnection { synchronized (mSessionsLock) { int ioBlockedCounter = mUidsBlockedOnIo.get(uid, 0); if (ioBlockedCounter == 0) { + Slog.w(TAG, "Unexpected app IO resumption for uid: " + uid); + } + + if (ioBlockedCounter <= 1) { mUidsBlockedOnIo.remove(uid); } else { mUidsBlockedOnIo.put(uid, --ioBlockedCounter); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index eefa045abe1b..27e2ee585a20 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -228,7 +228,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { private void enforceSuggestExternalTimePermission() { // We don't expect a call from system server, so simply enforce calling permission. mContext.enforceCallingPermission( - android.Manifest.permission.SET_TIME, + android.Manifest.permission.SUGGEST_EXTERNAL_TIME, "suggest time from external source"); } diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 3f74938005a7..89ed956b3aef 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -41,6 +41,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -110,6 +111,24 @@ public class Vcn extends Handler { @NonNull private final VcnNetworkRequestListener mRequestListener; @NonNull private final VcnCallback mVcnCallback; + /** + * Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs. + * + * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be created and added + * to this map in {@link #handleNetworkRequested(NetworkRequest, int, int)}, when a VCN receives + * a NetworkRequest that matches a VcnGatewayConnectionConfig for this VCN's VcnConfig. + * + * <p>A VcnGatewayConnection instance MUST NEVER overwrite an existing instance - otherwise + * there is potential for a orphaned VcnGatewayConnection instance that does not get properly + * shut down. + * + * <p>Due to potential for race conditions, VcnGatewayConnections MUST only be removed from this + * map once they have finished tearing down, which is reported to this VCN via {@link + * VcnGatewayStatusCallback#onQuit()}. Once this is done, all NetworkRequests are retrieved from + * the NetworkProvider so that another VcnGatewayConnectionConfig can match the + * previously-matched request. + */ + // TODO(b/182533200): remove the invariant on VcnGatewayConnection lifecycles @NonNull private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections = new HashMap<>(); @@ -191,6 +210,19 @@ public class Vcn extends Handler { return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values())); } + /** Get current Configs and Gateways for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Map<VcnGatewayConnectionConfig, VcnGatewayConnection> + getVcnGatewayConnectionConfigMap() { + return Collections.unmodifiableMap(new HashMap<>(mVcnGatewayConnections)); + } + + /** Set whether this Vcn is active for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public void setIsActive(boolean isActive) { + mIsActive.set(isActive); + } + private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { @@ -202,11 +234,6 @@ public class Vcn extends Handler { @Override public void handleMessage(@NonNull Message msg) { - // Ignore if this Vcn is not active and we're not receiving new configs - if (!isActive() && msg.what != MSG_EVENT_CONFIG_UPDATED) { - return; - } - switch (msg.what) { case MSG_EVENT_CONFIG_UPDATED: handleConfigUpdated((VcnConfig) msg.obj); @@ -237,9 +264,31 @@ public class Vcn extends Handler { mConfig = config; - // TODO(b/181815405): Reevaluate active VcnGatewayConnection(s) + if (mIsActive.getAndSet(true)) { + // VCN is already active - teardown any GatewayConnections whose configs have been + // removed and get all current requests + for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : + mVcnGatewayConnections.entrySet()) { + final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey(); + final VcnGatewayConnection gatewayConnection = entry.getValue(); + + // GatewayConnectionConfigs must match exactly (otherwise authentication or + // connection details may have changed). + if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) { + if (gatewayConnection == null) { + Slog.wtf( + getLogTag(), + "Found gatewayConnectionConfig without GatewayConnection"); + } else { + gatewayConnection.teardownAsynchronously(); + } + } + } - if (!mIsActive.getAndSet(true)) { + // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be + // satisfied start a new GatewayConnection) + mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); + } else { // If this VCN was not previously active, it is exiting Safe Mode. Re-register the // request listener to get NetworkRequests again (and all cached requests). mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); @@ -259,13 +308,16 @@ public class Vcn extends Handler { private void handleEnterSafeMode() { handleTeardown(); - mVcnGatewayConnections.clear(); - mVcnCallback.onEnteredSafeMode(); } private void handleNetworkRequested( @NonNull NetworkRequest request, int score, int providerId) { + if (!isActive()) { + Slog.v(getLogTag(), "Received NetworkRequest while inactive. Ignore for now"); + return; + } + if (score > getNetworkScore()) { if (VDBG) { Slog.v( @@ -318,8 +370,10 @@ public class Vcn extends Handler { mVcnGatewayConnections.remove(config); // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied - // start a new GatewayConnection) - mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); + // start a new GatewayConnection), but only if the Vcn is still active + if (isActive()) { + mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); + } } private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { diff --git a/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java new file mode 100644 index 000000000000..953837a56698 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/DeviceVibrationEffectAdapter.java @@ -0,0 +1,120 @@ +/* + * 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.vibrator; + +import android.os.VibrationEffect; +import android.os.VibratorInfo; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.MathUtils; +import android.util.Range; + +import java.util.ArrayList; +import java.util.List; + +/** Adapts a {@link VibrationEffect} to a specific device, taking into account its capabilities. */ +final class DeviceVibrationEffectAdapter implements VibrationEffectModifier<VibratorInfo> { + + /** + * Adapts a sequence of {@link VibrationEffectSegment} to device's absolute frequency values + * and respective supported amplitudes. + * + * <p>This adapter preserves the segment count. + */ + interface AmplitudeFrequencyAdapter { + List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments, + VibratorInfo info); + } + + private final AmplitudeFrequencyAdapter mAmplitudeFrequencyAdapter; + + DeviceVibrationEffectAdapter() { + this(new ClippingAmplitudeFrequencyAdapter()); + } + + DeviceVibrationEffectAdapter(AmplitudeFrequencyAdapter amplitudeFrequencyAdapter) { + mAmplitudeFrequencyAdapter = amplitudeFrequencyAdapter; + } + + @Override + public VibrationEffect apply(VibrationEffect effect, VibratorInfo info) { + if (!(effect instanceof VibrationEffect.Composed)) { + return effect; + } + + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + List<VibrationEffectSegment> mappedSegments = mAmplitudeFrequencyAdapter.apply( + composed.getSegments(), info); + + // TODO(b/167947076): add ramp to step adapter once PWLE capability is introduced + // TODO(b/167947076): add filter that removes unsupported primitives + // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback + + return new VibrationEffect.Composed(mappedSegments, composed.getRepeatIndex()); + } + + /** + * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRange()} and + * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}. + * + * <p>Devices with no frequency control will collapse all frequencies to zero and leave + * amplitudes unchanged. + */ + private static final class ClippingAmplitudeFrequencyAdapter + implements AmplitudeFrequencyAdapter { + @Override + public List<VibrationEffectSegment> apply(List<VibrationEffectSegment> segments, + VibratorInfo info) { + List<VibrationEffectSegment> result = new ArrayList<>(); + int segmentCount = segments.size(); + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = segments.get(i); + if (segment instanceof StepSegment) { + result.add(apply((StepSegment) segment, info)); + } else if (segment instanceof RampSegment) { + result.add(apply((RampSegment) segment, info)); + } else { + result.add(segment); + } + } + return result; + } + + private StepSegment apply(StepSegment segment, VibratorInfo info) { + float clampedFrequency = info.getFrequencyRange().clamp(segment.getFrequency()); + return new StepSegment( + MathUtils.min(segment.getAmplitude(), info.getMaxAmplitude(clampedFrequency)), + info.getAbsoluteFrequency(clampedFrequency), + (int) segment.getDuration()); + } + + private RampSegment apply(RampSegment segment, VibratorInfo info) { + Range<Float> frequencyRange = info.getFrequencyRange(); + float clampedStartFrequency = frequencyRange.clamp(segment.getStartFrequency()); + float clampedEndFrequency = frequencyRange.clamp(segment.getEndFrequency()); + return new RampSegment( + MathUtils.min(segment.getStartAmplitude(), + info.getMaxAmplitude(clampedStartFrequency)), + MathUtils.min(segment.getEndAmplitude(), + info.getMaxAmplitude(clampedEndFrequency)), + info.getAbsoluteFrequency(clampedStartFrequency), + info.getAbsoluteFrequency(clampedEndFrequency), + (int) segment.getDuration()); + } + } +} diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index e84ee672bf0f..cd840589475a 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -16,17 +16,24 @@ package com.android.server.vibrator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.CombinedVibrationEffect; import android.os.IBinder; import android.os.SystemClock; import android.os.VibrationAttributes; import android.os.VibrationEffect; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; +import java.util.function.Function; /** Represents a vibration request to the vibrator service. */ final class Vibration { @@ -61,6 +68,7 @@ final class Vibration { public final String opPkg; public final String reason; public final IBinder token; + public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); /** The actual effect to be played. */ @Nullable @@ -113,17 +121,70 @@ final class Vibration { } /** - * Replace this vibration effect if given {@code scaledEffect} is different, preserving the - * original one for debug purposes. + * Return the effect to be played when given prebaked effect id is not supported by the + * vibrator. */ - public void updateEffect(@NonNull CombinedVibrationEffect newEffect) { - if (newEffect.equals(mEffect)) { - return; + @Nullable + public VibrationEffect getFallback(int effectId) { + return mFallbacks.get(effectId); + } + + /** + * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported, + * which might be necessary for replacement in realtime. + */ + public void addFallback(int effectId, VibrationEffect effect) { + mFallbacks.put(effectId, effect); + } + + /** + * Applied update function to the current effect held by this vibration, and to each fallback + * effect added. + */ + public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) { + CombinedVibrationEffect newEffect = transformCombinedEffect(mEffect, updateFn); + if (!newEffect.equals(mEffect)) { + if (mOriginalEffect == null) { + mOriginalEffect = mEffect; + } + mEffect = newEffect; } - if (mOriginalEffect == null) { - mOriginalEffect = mEffect; + for (int i = 0; i < mFallbacks.size(); i++) { + mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i))); + } + } + + /** + * Creates a new {@link CombinedVibrationEffect} by applying the given transformation function + * to each {@link VibrationEffect}. + */ + private static CombinedVibrationEffect transformCombinedEffect( + CombinedVibrationEffect combinedEffect, Function<VibrationEffect, VibrationEffect> fn) { + if (combinedEffect instanceof CombinedVibrationEffect.Mono) { + VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect(); + return CombinedVibrationEffect.createSynced(fn.apply(effect)); + } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) { + SparseArray<VibrationEffect> effects = + ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects(); + CombinedVibrationEffect.SyncedCombination combination = + CombinedVibrationEffect.startSynced(); + for (int i = 0; i < effects.size(); i++) { + combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i))); + } + return combination.combine(); + } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) { + List<CombinedVibrationEffect> effects = + ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects(); + CombinedVibrationEffect.SequentialCombination combination = + CombinedVibrationEffect.startSequential(); + for (CombinedVibrationEffect effect : effects) { + combination.addNext(transformCombinedEffect(effect, fn)); + } + return combination.combine(); + } else { + // Unknown combination, return same effect. + return combinedEffect; } - mEffect = newEffect; } /** Return true is current status is different from {@link Status#RUNNING}. */ @@ -272,57 +333,62 @@ final class Vibration { private void dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect) { final long token = proto.start(fieldId); - if (effect instanceof VibrationEffect.OneShot) { - dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect); - } else if (effect instanceof VibrationEffect.Waveform) { - dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect); - } else if (effect instanceof VibrationEffect.Prebaked) { - dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect); - } else if (effect instanceof VibrationEffect.Composed) { - dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect); + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + for (VibrationEffectSegment segment : composed.getSegments()) { + dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment); } + proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.OneShot effect) { + VibrationEffectSegment segment) { final long token = proto.start(fieldId); - proto.write(OneShotProto.DURATION, (int) effect.getDuration()); - proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude()); + if (segment instanceof StepSegment) { + dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment); + } else if (segment instanceof RampSegment) { + dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment); + } else if (segment instanceof PrebakedSegment) { + dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment); + } else if (segment instanceof PrimitiveSegment) { + dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment); + } proto.end(token); } - private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Waveform effect) { + private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) { final long token = proto.start(fieldId); - for (long timing : effect.getTimings()) { - proto.write(WaveformProto.TIMINGS, (int) timing); - } - for (int amplitude : effect.getAmplitudes()) { - proto.write(WaveformProto.AMPLITUDES, amplitude); - } - proto.write(WaveformProto.REPEAT, effect.getRepeatIndex() >= 0); + proto.write(StepSegmentProto.DURATION, segment.getDuration()); + proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude()); + proto.write(StepSegmentProto.FREQUENCY, segment.getFrequency()); + proto.end(token); + } + + private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) { + final long token = proto.start(fieldId); + proto.write(RampSegmentProto.DURATION, segment.getDuration()); + proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude()); + proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude()); + proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequency()); + proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequency()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Prebaked effect) { + PrebakedSegment segment) { final long token = proto.start(fieldId); - proto.write(PrebakedProto.EFFECT_ID, effect.getId()); - proto.write(PrebakedProto.EFFECT_STRENGTH, effect.getEffectStrength()); - proto.write(PrebakedProto.FALLBACK, effect.shouldFallback()); + proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId()); + proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength()); + proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback()); proto.end(token); } private void dumpEffect(ProtoOutputStream proto, long fieldId, - VibrationEffect.Composed effect) { + PrimitiveSegment segment) { final long token = proto.start(fieldId); - for (VibrationEffect.Composition.PrimitiveEffect primitive : - effect.getPrimitiveEffects()) { - proto.write(ComposedProto.EFFECT_IDS, primitive.id); - proto.write(ComposedProto.EFFECT_SCALES, primitive.scale); - proto.write(ComposedProto.DELAYS, primitive.delay); - } + proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId()); + proto.write(PrimitiveSegmentProto.SCALE, segment.getScale()); + proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay()); proto.end(token); } } diff --git a/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java new file mode 100644 index 000000000000..d287c8faa34d --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.os.VibrationEffect; + +/** Function that applies a generic modifier to a {@link VibrationEffect}. */ +interface VibrationEffectModifier<T> { + + /** Applies the modifier to given {@link VibrationEffect}. */ + VibrationEffect apply(VibrationEffect effect, T modifier); +} diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index 10393f682279..f481772d7a7f 100644 --- a/services/core/java/com/android/server/vibrator/VibrationScaler.java +++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java @@ -18,16 +18,13 @@ package com.android.server.vibrator; import android.content.Context; import android.hardware.vibrator.V1_0.EffectStrength; -import android.os.CombinedVibrationEffect; import android.os.IExternalVibratorService; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.vibrator.PrebakedSegment; import android.util.Slog; import android.util.SparseArray; -import java.util.List; -import java.util.Objects; - /** Controls vibration scaling. */ final class VibrationScaler { private static final String TAG = "VibrationScaler"; @@ -90,43 +87,6 @@ final class VibrationScaler { } /** - * Scale a {@link CombinedVibrationEffect} based on the given usage hint for this vibration. - * - * @param combinedEffect the effect to be scaled - * @param usageHint one of VibrationAttributes.USAGE_* - * @return The same given effect, if no changes were made, or a new - * {@link CombinedVibrationEffect} with resolved and scaled amplitude - */ - public <T extends CombinedVibrationEffect> T scale(CombinedVibrationEffect combinedEffect, - int usageHint) { - if (combinedEffect instanceof CombinedVibrationEffect.Mono) { - VibrationEffect effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect(); - return (T) CombinedVibrationEffect.createSynced(scale(effect, usageHint)); - } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) { - SparseArray<VibrationEffect> effects = - ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects(); - CombinedVibrationEffect.SyncedCombination combination = - CombinedVibrationEffect.startSynced(); - for (int i = 0; i < effects.size(); i++) { - combination.addVibrator(effects.keyAt(i), scale(effects.valueAt(i), usageHint)); - } - return (T) combination.combine(); - } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) { - List<CombinedVibrationEffect> effects = - ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects(); - CombinedVibrationEffect.SequentialCombination combination = - CombinedVibrationEffect.startSequential(); - for (CombinedVibrationEffect effect : effects) { - combination.addNext(scale(effect, usageHint)); - } - return (T) combination.combine(); - } else { - // Unknown combination, return same effect. - return (T) combinedEffect; - } - } - - /** * Scale a {@link VibrationEffect} based on the given usage hint for this vibration. * * @param effect the effect to be scaled @@ -135,33 +95,10 @@ final class VibrationScaler { * resolved and scaled amplitude */ public <T extends VibrationEffect> T scale(VibrationEffect effect, int usageHint) { - if (effect instanceof VibrationEffect.Prebaked) { - // Prebaked effects are always just a direct translation to EffectStrength. - int intensity = mSettingsController.getCurrentIntensity(usageHint); - int newStrength = intensityToEffectStrength(intensity); - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - int strength = prebaked.getEffectStrength(); - VibrationEffect fallback = prebaked.getFallbackEffect(); - - if (fallback != null) { - VibrationEffect scaledFallback = scale(fallback, usageHint); - if (strength == newStrength && Objects.equals(fallback, scaledFallback)) { - return (T) prebaked; - } - - return (T) new VibrationEffect.Prebaked(prebaked.getId(), newStrength, - scaledFallback); - } else if (strength == newStrength) { - return (T) prebaked; - } else { - return (T) new VibrationEffect.Prebaked(prebaked.getId(), prebaked.shouldFallback(), - newStrength); - } - } - - effect = effect.resolve(mDefaultVibrationAmplitude); int defaultIntensity = mSettingsController.getDefaultIntensity(usageHint); int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + int newEffectStrength = intensityToEffectStrength(currentIntensity); + effect = effect.applyEffectStrength(newEffectStrength).resolve(mDefaultVibrationAmplitude); ScaleLevel scale = mScaleLevels.get(currentIntensity - defaultIntensity); if (scale == null) { @@ -171,7 +108,21 @@ final class VibrationScaler { return (T) effect; } - return effect.scale(scale.factor); + return (T) effect.scale(scale.factor); + } + + /** + * Scale a {@link PrebakedSegment} based on the given usage hint for this vibration. + * + * @param prebaked the prebaked segment to be scaled + * @param usageHint one of VibrationAttributes.USAGE_* + * @return The same segment if no changes were made, or a new {@link PrebakedSegment} with + * updated effect strength + */ + public PrebakedSegment scale(PrebakedSegment prebaked, int usageHint) { + int currentIntensity = mSettingsController.getCurrentIntensity(usageHint); + int newEffectStrength = intensityToEffectStrength(currentIntensity); + return prebaked.applyEffectStrength(newEffectStrength); } /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */ diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index b90408fe5371..3090e6d8c622 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -27,7 +27,13 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.VibrationEffect; +import android.os.VibratorInfo; import android.os.WorkSource; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; import android.util.SparseArray; @@ -87,6 +93,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private final WorkSource mWorkSource = new WorkSource(); private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; + private final VibrationEffectModifier<VibratorInfo> mDeviceEffectAdapter = + new DeviceVibrationEffectAdapter(); private final Vibration mVibration; private final VibrationCallbacks mCallbacks; private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); @@ -248,22 +256,26 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code * startIndex} until the next time it's vibrating amplitude is zero. */ - private static long getVibratorOnDuration(VibrationEffect.Waveform waveform, int startIndex) { - long[] timings = waveform.getTimings(); - int[] amplitudes = waveform.getAmplitudes(); - int repeatIndex = waveform.getRepeatIndex(); + private static long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) { + List<VibrationEffectSegment> segments = effect.getSegments(); + int segmentCount = segments.size(); + int repeatIndex = effect.getRepeatIndex(); int i = startIndex; long timing = 0; - while (timings[i] == 0 || amplitudes[i] != 0) { - timing += timings[i++]; - if (i >= timings.length) { - if (repeatIndex >= 0) { - i = repeatIndex; - // prevent infinite loop - repeatIndex = -1; - } else { - break; - } + while (i < segmentCount) { + if (!(segments.get(i) instanceof StepSegment)) { + break; + } + StepSegment stepSegment = (StepSegment) segments.get(i); + if (stepSegment.getAmplitude() == 0) { + break; + } + timing += stepSegment.getDuration(); + i++; + if (i == segmentCount && repeatIndex >= 0) { + i = repeatIndex; + // prevent infinite loop + repeatIndex = -1; } if (i == startIndex) { return 1000; @@ -620,22 +632,19 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } private long startVibrating(VibrationEffect effect, List<Step> nextSteps) { + // TODO(b/167947076): split this into 4 different step implementations: + // VibratorPerformStep, VibratorComposePrimitiveStep, VibratorComposePwleStep and + // VibratorAmplitudeStep. + // Make sure each step carries over the full VibrationEffect and an incremental segment + // index, and triggers a final VibratorOffStep once all segments are done. + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + VibrationEffectSegment firstSegment = composed.getSegments().get(0); final long duration; final long now = SystemClock.uptimeMillis(); - if (effect instanceof VibrationEffect.OneShot) { - VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect; - duration = oneShot.getDuration(); - // Do NOT set amplitude here. This might be called between prepareSynced and - // triggerSynced, so the vibrator is not actually turned on here. - // The next steps will handle the amplitude after the vibrator has turned on. - controller.on(duration, mVibration.id); - nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot, - now + duration + CALLBACKS_EXTRA_TIMEOUT)); - } else if (effect instanceof VibrationEffect.Waveform) { - VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect; + if (firstSegment instanceof StepSegment) { // Return the full duration of this waveform effect. - duration = waveform.getDuration(); - long onDuration = getVibratorOnDuration(waveform, 0); + duration = effect.getDuration(); + long onDuration = getVibratorOnDuration(composed, 0); if (onDuration > 0) { // Do NOT set amplitude here. This might be called between prepareSynced and // triggerSynced, so the vibrator is not actually turned on here. @@ -643,19 +652,53 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { controller.on(onDuration, mVibration.id); } long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now; - nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime)); - } else if (effect instanceof VibrationEffect.Prebaked) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; + nextSteps.add(new VibratorAmplitudeStep(now, controller, composed, offTime)); + } else if (firstSegment instanceof PrebakedSegment) { + PrebakedSegment prebaked = (PrebakedSegment) firstSegment; + VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId()); duration = controller.on(prebaked, mVibration.id); if (duration > 0) { nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, controller)); - } else if (prebaked.getFallbackEffect() != null) { - return startVibrating(prebaked.getFallbackEffect(), nextSteps); + } else if (prebaked.shouldFallback() && fallback != null) { + return startVibrating(fallback, nextSteps); } - } else if (effect instanceof VibrationEffect.Composed) { - VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; - duration = controller.on(composed, mVibration.id); + } else if (firstSegment instanceof PrimitiveSegment) { + int segmentCount = composed.getSegments().size(); + PrimitiveSegment[] primitives = new PrimitiveSegment[segmentCount]; + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if (segment instanceof PrimitiveSegment) { + primitives[i] = (PrimitiveSegment) segment; + } else { + primitives[i] = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_NOOP, + /* scale= */ 1, /* delay= */ 0); + } + } + duration = controller.on(primitives, mVibration.id); + if (duration > 0) { + nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, + controller)); + } + } else if (firstSegment instanceof RampSegment) { + int segmentCount = composed.getSegments().size(); + RampSegment[] primitives = new RampSegment[segmentCount]; + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if (segment instanceof RampSegment) { + primitives[i] = (RampSegment) segment; + } else if (segment instanceof StepSegment) { + StepSegment stepSegment = (StepSegment) segment; + primitives[i] = new RampSegment( + stepSegment.getAmplitude(), stepSegment.getAmplitude(), + stepSegment.getFrequency(), stepSegment.getFrequency(), + (int) stepSegment.getDuration()); + } else { + primitives[i] = new RampSegment(0, 0, 0, 0, 0); + } + } + duration = controller.on(primitives, mVibration.id); if (duration > 0) { nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT, controller)); @@ -713,33 +756,22 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { /** Represents a step to change the amplitude of the vibrator. */ private final class VibratorAmplitudeStep extends Step { public final VibratorController controller; - public final VibrationEffect.Waveform waveform; + public final VibrationEffect.Composed effect; public final int currentIndex; - public final long expectedVibratorStopTime; private long mNextVibratorStopTime; VibratorAmplitudeStep(long startTime, VibratorController controller, - VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) { - this(startTime, controller, - (VibrationEffect.Waveform) VibrationEffect.createWaveform( - new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()}, - /* repeat= */ -1), - expectedVibratorStopTime); - } - - VibratorAmplitudeStep(long startTime, VibratorController controller, - VibrationEffect.Waveform waveform, long expectedVibratorStopTime) { - this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime); + VibrationEffect.Composed effect, long expectedVibratorStopTime) { + this(startTime, controller, effect, /* index= */ 0, expectedVibratorStopTime); } VibratorAmplitudeStep(long startTime, VibratorController controller, - VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) { + VibrationEffect.Composed effect, int index, long expectedVibratorStopTime) { super(startTime); this.controller = controller; - this.waveform = waveform; + this.effect = effect; this.currentIndex = index; - this.expectedVibratorStopTime = expectedVibratorStopTime; mNextVibratorStopTime = expectedVibratorStopTime; } @@ -759,11 +791,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { long latency = SystemClock.uptimeMillis() - startTime; Slog.d(TAG, "Running amplitude step with " + latency + "ms latency."); } - if (waveform.getTimings()[currentIndex] == 0) { + VibrationEffectSegment segment = effect.getSegments().get(currentIndex); + if (!(segment instanceof StepSegment)) { + return nextSteps(); + } + StepSegment stepSegment = (StepSegment) segment; + if (stepSegment.getDuration() == 0) { // Skip waveform entries with zero timing. return nextSteps(); } - int amplitude = waveform.getAmplitudes()[currentIndex]; + float amplitude = stepSegment.getAmplitude(); if (amplitude == 0) { stopVibrating(); return nextSteps(); @@ -771,7 +808,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { if (startTime >= mNextVibratorStopTime) { // Vibrator has stopped. Turn vibrator back on for the duration of another // cycle before setting the amplitude. - long onDuration = getVibratorOnDuration(waveform, currentIndex); + long onDuration = getVibratorOnDuration(effect, currentIndex); if (onDuration > 0) { startVibrating(onDuration); mNextVibratorStopTime = @@ -806,7 +843,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { controller.on(duration, mVibration.id); } - private void changeAmplitude(int amplitude) { + private void changeAmplitude(float amplitude) { if (DEBUG) { Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId() + " to " + amplitude); @@ -816,16 +853,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { @NonNull private List<Step> nextSteps() { - long nextStartTime = startTime + waveform.getTimings()[currentIndex]; + long nextStartTime = startTime + effect.getSegments().get(currentIndex).getDuration(); int nextIndex = currentIndex + 1; - if (nextIndex >= waveform.getTimings().length) { - nextIndex = waveform.getRepeatIndex(); - } - if (nextIndex < 0) { - return Arrays.asList(new VibratorOffStep(nextStartTime, controller)); + if (nextIndex >= effect.getSegments().size()) { + nextIndex = effect.getRepeatIndex(); } - return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform, - nextIndex, mNextVibratorStopTime)); + Step nextStep = nextIndex < 0 + ? new VibratorOffStep(nextStartTime, controller) + : new VibratorAmplitudeStep(nextStartTime, controller, effect, nextIndex, + mNextVibratorStopTime); + return Arrays.asList(nextStep); } } @@ -845,7 +882,9 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { mVibratorIds = new int[mVibrators.size()]; for (int i = 0; i < mVibrators.size(); i++) { int vibratorId = mVibrators.keyAt(i); - mVibratorEffects.put(vibratorId, mono.getEffect()); + VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo(); + VibrationEffect effect = mDeviceEffectAdapter.apply(mono.getEffect(), vibratorInfo); + mVibratorEffects.put(vibratorId, effect); mVibratorIds[i] = vibratorId; } mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects); @@ -857,7 +896,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { for (int i = 0; i < stereoEffects.size(); i++) { int vibratorId = stereoEffects.keyAt(i); if (mVibrators.contains(vibratorId)) { - mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i)); + VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo(); + VibrationEffect effect = mDeviceEffectAdapter.apply( + stereoEffects.valueAt(i), vibratorInfo); + mVibratorEffects.put(vibratorId, effect); } } mVibratorIds = new int[mVibratorEffects.size()]; @@ -909,13 +951,13 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private long calculateRequiredSyncCapabilities(SparseArray<VibrationEffect> effects) { long prepareCap = 0; for (int i = 0; i < effects.size(); i++) { - VibrationEffect effect = effects.valueAt(i); - if (effect instanceof VibrationEffect.OneShot - || effect instanceof VibrationEffect.Waveform) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effects.valueAt(i); + VibrationEffectSegment firstSegment = composed.getSegments().get(0); + if (firstSegment instanceof StepSegment) { prepareCap |= IVibratorManager.CAP_PREPARE_ON; - } else if (effect instanceof VibrationEffect.Prebaked) { + } else if (firstSegment instanceof PrebakedSegment) { prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM; - } else if (effect instanceof VibrationEffect.Composed) { + } else if (firstSegment instanceof PrimitiveSegment) { prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE; } } diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java index e3dc70b41c0d..c8977025b846 100644 --- a/services/core/java/com/android/server/vibrator/VibratorController.java +++ b/services/core/java/com/android/server/vibrator/VibratorController.java @@ -22,8 +22,10 @@ import android.os.Binder; import android.os.IVibratorStateListener; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.VibrationEffect; import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -64,9 +66,12 @@ final class VibratorController { mNativeWrapper = nativeWrapper; mNativeWrapper.init(vibratorId, listener); + // TODO(b/167947076): load supported ones from HAL once API introduced + VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( + Float.NaN, nativeWrapper.getResonantFrequency(), Float.NaN, Float.NaN, null); mVibratorInfo = new VibratorInfo(vibratorId, nativeWrapper.getCapabilities(), nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives(), - nativeWrapper.getResonantFrequency(), nativeWrapper.getQFactor()); + nativeWrapper.getQFactor(), frequencyMapping); } /** Register state listener for this vibrator. */ @@ -156,21 +161,22 @@ final class VibratorController { * Update the predefined vibration effect saved with given id. This will remove the saved effect * if given {@code effect} is {@code null}. */ - public void updateAlwaysOn(int id, @Nullable VibrationEffect.Prebaked effect) { + public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) { if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { return; } synchronized (mLock) { - if (effect == null) { + if (prebaked == null) { mNativeWrapper.alwaysOnDisable(id); } else { - mNativeWrapper.alwaysOnEnable(id, effect.getId(), effect.getEffectStrength()); + mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(), + prebaked.getEffectStrength()); } } } /** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */ - public void setAmplitude(int amplitude) { + public void setAmplitude(float amplitude) { synchronized (mLock) { if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { mNativeWrapper.setAmplitude(amplitude); @@ -199,10 +205,10 @@ final class VibratorController { * * @return The duration of the effect playing, or 0 if unsupported. */ - public long on(VibrationEffect.Prebaked effect, long vibrationId) { + public long on(PrebakedSegment prebaked, long vibrationId) { synchronized (mLock) { - long duration = mNativeWrapper.perform(effect.getId(), effect.getEffectStrength(), - vibrationId); + long duration = mNativeWrapper.perform(prebaked.getEffectId(), + prebaked.getEffectStrength(), vibrationId); if (duration > 0) { notifyVibratorOnLocked(); } @@ -211,21 +217,18 @@ final class VibratorController { } /** - * Plays composited vibration effect, using {@code vibrationId} or completion callback to - * {@link OnVibrationCompleteListener}. + * Plays a composition of vibration primitives, using {@code vibrationId} or completion callback + * to {@link OnVibrationCompleteListener}. * * <p>This will affect the state of {@link #isVibrating()}. * * @return The duration of the effect playing, or 0 if unsupported. */ - public long on(VibrationEffect.Composed effect, long vibrationId) { + public long on(PrimitiveSegment[] primitives, long vibrationId) { if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { return 0; } synchronized (mLock) { - VibrationEffect.Composition.PrimitiveEffect[] primitives = - effect.getPrimitiveEffects().toArray( - new VibrationEffect.Composition.PrimitiveEffect[0]); long duration = mNativeWrapper.compose(primitives, vibrationId); if (duration > 0) { notifyVibratorOnLocked(); @@ -234,6 +237,19 @@ final class VibratorController { } } + /** + * Plays a composition of pwle primitives, using {@code vibrationId} or completion callback + * to {@link OnVibrationCompleteListener}. + * + * <p>This will affect the state of {@link #isVibrating()}. + * + * @return The duration of the effect playing, or 0 if unsupported. + */ + public long on(RampSegment[] primitives, long vibrationId) { + // TODO(b/167947076): forward to the HAL once APIs are introduced + return 0; + } + /** Turns off the vibrator.This will affect the state of {@link #isVibrating()}. */ public void off() { synchronized (mLock) { @@ -313,13 +329,13 @@ final class VibratorController { private static native boolean isAvailable(long nativePtr); private static native void on(long nativePtr, long milliseconds, long vibrationId); private static native void off(long nativePtr); - private static native void setAmplitude(long nativePtr, int amplitude); + private static native void setAmplitude(long nativePtr, float amplitude); private static native int[] getSupportedEffects(long nativePtr); private static native int[] getSupportedPrimitives(long nativePtr); - private static native long performEffect( - long nativePtr, long effect, long strength, long vibrationId); - private static native long performComposedEffect(long nativePtr, - VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId); + private static native long performEffect(long nativePtr, long effect, long strength, + long vibrationId); + private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect, + long vibrationId); private static native void setExternalControl(long nativePtr, boolean enabled); private static native long getCapabilities(long nativePtr); private static native void alwaysOnEnable(long nativePtr, long id, long effect, @@ -359,7 +375,7 @@ final class VibratorController { } /** Sets the amplitude for the vibrator to run. */ - public void setAmplitude(int amplitude) { + public void setAmplitude(float amplitude) { setAmplitude(mNativePtr, amplitude); } @@ -379,9 +395,8 @@ final class VibratorController { } /** Turns vibrator on to perform one of the supported composed effects. */ - public long compose( - VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) { - return performComposedEffect(mNativePtr, effect, vibrationId); + public long compose(PrimitiveSegment[] primitives, long vibrationId) { + return performComposedEffect(mNativePtr, primitives, vibrationId); } /** Enabled the device vibrator to be controlled by another service. */ diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index c9751bb7abe4..5fd1d7a6e1dc 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -48,6 +48,8 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; @@ -312,7 +314,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } attrs = fixupVibrationAttributes(attrs); synchronized (mLock) { - SparseArray<VibrationEffect.Prebaked> effects = fixupAlwaysOnEffectsLocked(effect); + SparseArray<PrebakedSegment> effects = fixupAlwaysOnEffectsLocked(effect); if (effects == null) { // Invalid effects set in CombinedVibrationEffect, or always-on capability is // missing on individual vibrators. @@ -347,8 +349,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { attrs = fixupVibrationAttributes(attrs); Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs, uid, opPkg, reason); - // Update with fixed up effect to keep the original effect in Vibration for debugging. - vib.updateEffect(fixupVibrationEffect(effect)); + fillVibrationFallbacks(vib, effect); synchronized (mLock) { Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib); @@ -476,7 +477,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private void updateAlwaysOnLocked(AlwaysOnVibration vib) { for (int i = 0; i < vib.effects.size(); i++) { VibratorController vibrator = mVibrators.get(vib.effects.keyAt(i)); - VibrationEffect.Prebaked effect = vib.effects.valueAt(i); + PrebakedSegment effect = vib.effects.valueAt(i); if (vibrator == null) { continue; } @@ -496,7 +497,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private Vibration.Status startVibrationLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { - vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage())); + vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage())); boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); if (inputDevicesAvailable) { @@ -757,43 +758,38 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link * VibrationSettings#getFallbackEffect}. */ - private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) { + private void fillVibrationFallbacks(Vibration vib, CombinedVibrationEffect effect) { if (effect instanceof CombinedVibrationEffect.Mono) { - return CombinedVibrationEffect.createSynced( - fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect())); + fillVibrationFallbacks(vib, ((CombinedVibrationEffect.Mono) effect).getEffect()); } else if (effect instanceof CombinedVibrationEffect.Stereo) { - CombinedVibrationEffect.SyncedCombination combination = - CombinedVibrationEffect.startSynced(); SparseArray<VibrationEffect> effects = ((CombinedVibrationEffect.Stereo) effect).getEffects(); for (int i = 0; i < effects.size(); i++) { - combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i))); + fillVibrationFallbacks(vib, effects.valueAt(i)); } - return combination.combine(); } else if (effect instanceof CombinedVibrationEffect.Sequential) { - CombinedVibrationEffect.SequentialCombination combination = - CombinedVibrationEffect.startSequential(); List<CombinedVibrationEffect> effects = ((CombinedVibrationEffect.Sequential) effect).getEffects(); - for (CombinedVibrationEffect e : effects) { - combination.addNext(fixupVibrationEffect(e)); + for (int i = 0; i < effects.size(); i++) { + fillVibrationFallbacks(vib, effects.get(i)); } - return combination.combine(); } - return effect; } - private VibrationEffect fixupVibrationEffect(VibrationEffect effect) { - if (effect instanceof VibrationEffect.Prebaked - && ((VibrationEffect.Prebaked) effect).shouldFallback()) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId()); - if (fallback != null) { - return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(), - fallback); + private void fillVibrationFallbacks(Vibration vib, VibrationEffect effect) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + int segmentCount = composed.getSegments().size(); + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if (segment instanceof PrebakedSegment) { + PrebakedSegment prebaked = (PrebakedSegment) segment; + VibrationEffect fallback = mVibrationSettings.getFallbackEffect( + prebaked.getEffectId()); + if (prebaked.shouldFallback() && fallback != null) { + vib.addFallback(prebaked.getEffectId(), fallback); + } } } - return effect; } /** @@ -819,7 +815,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") @Nullable - private SparseArray<VibrationEffect.Prebaked> fixupAlwaysOnEffectsLocked( + private SparseArray<PrebakedSegment> fixupAlwaysOnEffectsLocked( CombinedVibrationEffect effect) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "fixupAlwaysOnEffectsLocked"); try { @@ -833,17 +829,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // Only synced combinations can be used for always-on effects. return null; } - SparseArray<VibrationEffect.Prebaked> result = new SparseArray<>(); + SparseArray<PrebakedSegment> result = new SparseArray<>(); for (int i = 0; i < effects.size(); i++) { - VibrationEffect prebaked = effects.valueAt(i); - if (!(prebaked instanceof VibrationEffect.Prebaked)) { + PrebakedSegment prebaked = extractPrebakedSegment(effects.valueAt(i)); + if (prebaked == null) { Slog.e(TAG, "Only prebaked effects supported for always-on."); return null; } int vibratorId = effects.keyAt(i); VibratorController vibrator = mVibrators.get(vibratorId); if (vibrator != null && vibrator.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { - result.put(vibratorId, (VibrationEffect.Prebaked) prebaked); + result.put(vibratorId, prebaked); } } if (result.size() == 0) { @@ -855,6 +851,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + @Nullable + private static PrebakedSegment extractPrebakedSegment(VibrationEffect effect) { + if (effect instanceof VibrationEffect.Composed) { + VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; + if (composed.getSegments().size() == 1) { + VibrationEffectSegment segment = composed.getSegments().get(0); + if (segment instanceof PrebakedSegment) { + return (PrebakedSegment) segment; + } + } + } + return null; + } + /** * Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to * allow bypassing {@link AppOpsManager} checks. @@ -1008,10 +1018,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { public final int uid; public final String opPkg; public final VibrationAttributes attrs; - public final SparseArray<VibrationEffect.Prebaked> effects; + public final SparseArray<PrebakedSegment> effects; AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs, - SparseArray<VibrationEffect.Prebaked> effects) { + SparseArray<PrebakedSegment> effects) { this.alwaysOnId = alwaysOnId; this.uid = uid; this.opPkg = opPkg; 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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6072a0678d4d..a9d33dc29467 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3724,10 +3724,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // config. (Only happens when the target window is in a different root DA) if (target != null) { RootDisplayArea targetRoot = target.getRootDisplayArea(); - if (targetRoot != null) { + if (targetRoot != null && targetRoot != mImeWindowsContainer.getRootDisplayArea()) { // Reposition the IME container to the target root to get the correct bounds and // config. targetRoot.placeImeContainer(mImeWindowsContainer); + // Directly hide the IME window so it doesn't flash immediately after reparenting. + // InsetsController will make IME visible again before animating it. + if (mInputMethodWindow != null) { + mInputMethodWindow.hide(false /* doAnimation */, false /* requestAnim */); + } } } // 2. Assign window layers based on the IME surface parent to make sure it is on top of the diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 38ad4f08b35d..7b0fb0146bc2 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -30,7 +30,6 @@ import static com.android.server.wm.InsetsSourceProviderProto.CONTROLLABLE; import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET; import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL; import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET; -import static com.android.server.wm.InsetsSourceProviderProto.FINISH_SEAMLESS_ROTATE_FRAME_NUMBER; import static com.android.server.wm.InsetsSourceProviderProto.FRAME; import static com.android.server.wm.InsetsSourceProviderProto.IME_OVERRIDDEN_FRAME; import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING; @@ -60,6 +59,7 @@ import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; +import java.util.function.Consumer; /** * Controller for a specific inset source on the server. It's called provider as it provides the @@ -85,6 +85,16 @@ class InsetsSourceProvider { private final Rect mImeOverrideFrame = new Rect(); private boolean mIsLeashReadyForDispatching; + private final Consumer<Transaction> mSetLeashPositionConsumer = t -> { + if (mControl != null) { + final SurfaceControl leash = mControl.getLeash(); + if (leash != null) { + final Point position = mControl.getSurfacePosition(); + t.setPosition(leash, position.x, position.y); + } + } + }; + /** The visibility override from the current controlling window. */ private boolean mClientVisible; @@ -151,7 +161,6 @@ class InsetsSourceProvider { // TODO: Ideally, we should wait for the animation to finish so previous window can // animate-out as new one animates-in. mWin.cancelAnimation(); - mWin.mPendingPositionChanged = null; mWin.mProvidedInsetsSources.remove(mSource.getType()); } ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win); @@ -252,12 +261,11 @@ class InsetsSourceProvider { final Point position = getWindowFrameSurfacePosition(); if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) { changed = true; - if (!mWin.getWindowFrames().didFrameSizeChange()) { - updateLeashPosition(-1 /* frameNumber */); - } else if (mWin.mInRelayout) { - updateLeashPosition(mWin.getFrameNumber()); + if (mWin.getWindowFrames().didFrameSizeChange() && mWin.mWinAnimator.getShown() + && mWin.okToDisplay()) { + mWin.applyWithNextDraw(mSetLeashPositionConsumer); } else { - mWin.mPendingPositionChanged = this; + mSetLeashPositionConsumer.accept(mWin.getPendingTransaction()); } } final Insets insetsHint = mSource.calculateInsets( @@ -272,19 +280,6 @@ class InsetsSourceProvider { } } - void updateLeashPosition(long frameNumber) { - if (mControl == null) { - return; - } - final SurfaceControl leash = mControl.getLeash(); - if (leash != null) { - final Transaction t = mDisplayContent.getPendingTransaction(); - final Point position = mControl.getSurfacePosition(); - t.setPosition(leash, position.x, position.y); - deferTransactionUntil(t, leash, frameNumber); - } - } - private Point getWindowFrameSurfacePosition() { final Rect frame = mWin.getFrame(); final Point position = new Point(); @@ -292,14 +287,6 @@ class InsetsSourceProvider { return position; } - private void deferTransactionUntil(Transaction t, SurfaceControl leash, long frameNumber) { - if (frameNumber >= 0) { - final SurfaceControl barrier = mWin.getClientViewRootSurface(); - t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber); - t.deferTransactionUntil(leash, barrier, frameNumber); - } - } - /** * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget) */ diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index b31c2e462766..20216c3afcd4 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -167,6 +167,11 @@ class KeyguardController { if (aodChanged) { // Ensure the new state takes effect. mWindowManager.mWindowPlacerLocked.performSurfacePlacement(); + // If the device can enter AOD and keyguard at the same time, the screen will not be + // turned off, so the snapshot needs to be refreshed when these states are changed. + if (aodShowing && keyguardShowing && keyguardChanged) { + mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY); + } } if (keyguardChanged) { diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index d81181d46417..20c0d4189ad8 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1573,6 +1573,10 @@ class RecentTasks { } else if (document || trIsDocument) { // Only one of these is a document. Not the droid we're looking for. continue; + } else if (multiTasksAllowed) { + // Neither is a document, but the new task supports multiple tasks so keep the + // existing task + continue; } } return i; diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java index 505af05ed642..cd20c8242d81 100644 --- a/services/core/java/com/android/server/wm/RootDisplayArea.java +++ b/services/core/java/com/android/server/wm/RootDisplayArea.java @@ -79,10 +79,6 @@ class RootDisplayArea extends DisplayArea<DisplayArea> { */ void placeImeContainer(DisplayArea.Tokens imeContainer) { final RootDisplayArea previousRoot = imeContainer.getRootDisplayArea(); - if (previousRoot == this) { - // No need to reparent if IME container is below the same root. - return; - } List<Feature> features = mFeatures; for (int i = 0; i < features.size(); i++) { 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/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 8915eba3d509..5af44317b8c1 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -635,20 +635,7 @@ class TaskSnapshotController { mHandler.post(() -> { try { synchronized (mService.mGlobalLock) { - mTmpTasks.clear(); - mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> { - // Since RecentsAnimation will handle task snapshot while switching apps - // with the best capture timing (e.g. IME window capture), No need - // additional task capture while task is controlled by RecentsAnimation. - if (task.isVisible() && !task.isAnimatingByRecents()) { - mTmpTasks.add(task); - } - }); - // Allow taking snapshot of home when turning screen off to reduce the delay of - // waking from secure lock to home. - final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY && - mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId); - snapshotTasks(mTmpTasks, allowSnapshotHome); + snapshotForSleeping(displayId); } } finally { listener.onScreenOff(); @@ -656,6 +643,27 @@ class TaskSnapshotController { }); } + /** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */ + void snapshotForSleeping(int displayId) { + if (shouldDisableSnapshots()) { + return; + } + mTmpTasks.clear(); + mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> { + // Since RecentsAnimation will handle task snapshot while switching apps with the best + // capture timing (e.g. IME window capture), No need additional task capture while task + // is controlled by RecentsAnimation. + if (task.isVisible() && !task.isAnimatingByRecents()) { + mTmpTasks.add(task); + } + }); + // Allow taking snapshot of home when turning screen off to reduce the delay of waking from + // secure lock to home. + final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY + && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId); + snapshotTasks(mTmpTasks, allowSnapshotHome); + } + /** * @return The {@link Appearance} flags for the top fullscreen opaque window in the given * {@param task}. diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 7c5afa8282ee..7644cf2fa592 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -43,7 +43,6 @@ import android.os.SystemClock; import android.util.ArraySet; import android.util.MathUtils; import android.util.Slog; -import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; @@ -296,9 +295,9 @@ class WallpaperController { } boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) { - final DisplayInfo displayInfo = wallpaperWin.getDisplayInfo(); - final int dw = displayInfo.logicalWidth; - final int dh = displayInfo.logicalHeight; + final Rect parentFrame = wallpaperWin.getParentFrame(); + final int dw = parentFrame.width(); + final int dh = parentFrame.height(); int xOffset = 0; int yOffset = 0; 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/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java index 9245f8c3efe5..ffd6d21c1026 100644 --- a/services/core/java/com/android/server/wm/WindowFrames.java +++ b/services/core/java/com/android/server/wm/WindowFrames.java @@ -113,7 +113,7 @@ public class WindowFrames { } /** - * @return true if the width or height has changed since last reported to the client. + * @return true if the width or height has changed since last updating resizing window. */ boolean didFrameSizeChange() { return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height()); @@ -135,6 +135,13 @@ public class WindowFrames { } /** + * @return true if the width or height has changed since last reported to the client. + */ + boolean isFrameSizeChangeReported() { + return mFrameSizeChanged || didFrameSizeChange(); + } + + /** * Resets the size changed flags so they're all set to false again. This should be called * after the frames are reported to client. */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6b1071c9e84d..d494b7573359 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2228,18 +2228,6 @@ public class WindowManagerService extends IWindowManager.Stub win.setFrameNumber(frameNumber); - final DisplayContent dc = win.getDisplayContent(); - - if (win.mPendingPositionChanged != null) { - win.mPendingPositionChanged.updateLeashPosition(frameNumber); - win.mPendingPositionChanged = null; - } - - if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) { - win.prepareDrawHandlers(); - result |= RELAYOUT_RES_BLAST_SYNC; - } - int attrChanges = 0; int flagChanges = 0; int privateFlagChanges = 0; @@ -2512,6 +2500,12 @@ public class WindowManagerService extends IWindowManager.Stub } win.mInRelayout = false; + if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) { + win.prepareDrawHandlers(); + win.markRedrawForSyncReported(); + result |= RELAYOUT_RES_BLAST_SYNC; + } + if (configChanged) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: postNewConfigurationToHandler"); @@ -2707,7 +2701,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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6da350bbedf2..eb8315248db2 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -726,8 +726,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private InsetsState mFrozenInsetsState; - @Nullable InsetsSourceProvider mPendingPositionChanged; - private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; private KeyInterceptionInfo mKeyInterceptionInfo; @@ -763,6 +761,56 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * into mReadyDrawHandlers. Finally once we get to finishDrawing we know everything in * mReadyDrawHandlers corresponds to state which was observed by the client and we can * invoke the consumers. + * + * To see in more detail that this works, we can look at it like this: + * + * The client is in one of these states: + * + * 1. Asleep + * 2. Traversal scheduled + * 3. Starting traversal + * 4. In relayout + * 5. Already drawing + * + * The property we want to implement with the draw handlers is: + * If WM code makes a change to client observable state (e.g. configuration), + * and registers a draw handler (without releasing the WM lock in between), + * the FIRST frame reflecting that change, will be in the Transaction passed + * to the draw handler. + * + * We describe the expected sequencing in each of the possible client states. + * We aim to "prove" that the WM can call applyWithNextDraw() with the client + * starting in any state, and achieve the desired result. + * + * 1. Asleep: The client will wake up in response to MSG_RESIZED, call relayout, + * observe the changes. Relayout will return BLAST_SYNC, and the client will + * send the transaction to finishDrawing. Since the client was asleep. This + * will be the first finishDrawing reflecting the change. + * 2, 3: traversal scheduled/starting traversal: These two states can be considered + * together. Each has two sub-states: + * a) Traversal will call relayout. In this case we proceed like the starting + * from asleep case. + * b) Traversal will not call relayout. In this case, the client produced + * frame will not include the change. Because there is no call to relayout + * there is no call to prepareDrawHandlers() and even if the client calls + * finish drawing the draw handler will not be invoked. We have to wait + * on the client to receive MSG_RESIZED, and will sync on the next frame + * 4. In relayout. In this case we are careful to prepare draw handlers and check + * whether to return the BLAST flag at the end of relayoutWindow. This means if you + * add a draw handler while the client is in relayout, BLAST_SYNC will be + * immediately returned, and the client will submit the frame corresponding + * to what returns from layout. When we prepare the draw handlers we clear the + * flag which would later cause us to report draw for sync. Since we reported + * sync through relayout (by luck the client was calling relayout perhaps) + * there is no need for a MSG_RESIZED. + * 5. Already drawing. This works much like cases 2 and 3. If there is no call to + * finishDrawing then of course the draw handlers will not be invoked and we just + * wait on the next frame for sync. If there is a call to finishDrawing, + * the draw handler will not have been prepared (since we did not call relayout) + * and we will have to wait on the next frame. + * + * By this logic we can see no matter which of the client states we are in when the + * draw handler is added, it will always execute on the expected frame. */ private final List<Consumer<SurfaceControl.Transaction>> mPendingDrawHandlers = new ArrayList<>(); @@ -774,6 +822,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateSurfacePosition(t); }; + private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> { + if (mSurfaceControl != null && mSurfaceControl.isValid()) { + t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y); + } + }; + /** * @see #setSurfaceTranslationY(int) */ @@ -2130,18 +2184,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int left = mWindowFrames.mFrame.left; final int top = mWindowFrames.mFrame.top; - // During the transition from pip to fullscreen, the activity windowing mode is set to - // fullscreen at the beginning while the task is kept in pinned mode. Skip the move - // animation in such case since the transition is handled in SysUI. - final boolean hasMovementAnimation = getTask() == null - ? getWindowConfiguration().hasMovementAnimations() - : getTask().getWindowConfiguration().hasMovementAnimations(); - if (mToken.okToAnimate() - && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0 - && !isDragResizing() - && hasMovementAnimation - && !mWinAnimator.mLastHidden - && !mSeamlesslyRotated) { + if (canPlayMoveAnimation()) { startMoveAnimation(left, top); } @@ -2157,6 +2200,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mMovedByResize = false; } + private boolean canPlayMoveAnimation() { + + // During the transition from pip to fullscreen, the activity windowing mode is set to + // fullscreen at the beginning while the task is kept in pinned mode. Skip the move + // animation in such case since the transition is handled in SysUI. + final boolean hasMovementAnimation = getTask() == null + ? getWindowConfiguration().hasMovementAnimations() + : getTask().getWindowConfiguration().hasMovementAnimations(); + return mToken.okToAnimate() + && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0 + && !isDragResizing() + && hasMovementAnimation + && !mWinAnimator.mLastHidden + && !mSeamlesslyRotated; + } + /** * Return whether this window has moved. (Only makes * sense to call from performLayoutAndPlaceSurfacesLockedInner().) @@ -3706,7 +3765,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int displayId = getDisplayId(); fillClientWindowFrames(mClientWindowFrames); - mRedrawForSyncReported = true; + markRedrawForSyncReported(); try { mClient.resized(mClientWindowFrames, reportDraw, mergedConfiguration, forceRelayout, @@ -5327,13 +5386,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // prior to the rotation. if (!mSurfaceAnimator.hasLeash() && mPendingSeamlessRotate == null && !mLastSurfacePosition.equals(mSurfacePosition)) { - t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y); + final boolean frameSizeChanged = mWindowFrames.isFrameSizeChangeReported(); + final boolean surfaceInsetsChanged = surfaceInsetsChanging(); + final boolean surfaceSizeChanged = frameSizeChanged || surfaceInsetsChanged; mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y); - if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) { + if (surfaceInsetsChanged) { mLastSurfaceInsets.set(mAttrs.surfaceInsets); - t.deferTransactionUntil(mSurfaceControl, - mWinAnimator.mSurfaceController.mSurfaceControl, - getFrameNumber()); + } + if (surfaceSizeChanged && mWinAnimator.getShown() && !canPlayMoveAnimation() + && okToDisplay()) { + applyWithNextDraw(mSetSurfacePositionConsumer); + } else { + mSetSurfacePositionConsumer.accept(t); } } } @@ -5850,7 +5914,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * "in relayout", the results may be undefined but at all other times the function * should sort of transparently work like this: * 1. Make changes to WM hierarchy (say change app configuration) - * 2. Call apply with next draw. + * 2. Call applyWithNextDraw * 3. After finishDrawing, our consumer will be passed the Transaction * containing the buffer, and we can merge in additional operations. * See {@link WindowState#mPendingDrawHandlers} @@ -5879,16 +5943,26 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * See {@link WindowState#mPendingDrawHandlers} */ boolean executeDrawHandlers(SurfaceControl.Transaction t) { - if (t == null) t = mTmpTransaction; boolean hadHandlers = false; + boolean applyHere = false; + if (t == null) { + t = mTmpTransaction; + applyHere = true; + } + for (int i = 0; i < mReadyDrawHandlers.size(); i++) { mReadyDrawHandlers.get(i).accept(t); hadHandlers = true; } - mReadyDrawHandlers.clear(); - mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); - t.apply(); + if (hadHandlers) { + mReadyDrawHandlers.clear(); + mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); + } + + if (applyHere) { + t.apply(); + } return hadHandlers; } @@ -5906,4 +5980,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @WindowManager.LayoutParams.WindowType int getWindowType() { return mAttrs.type; } + + void markRedrawForSyncReported() { + mRedrawForSyncReported = true; + } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 0c80f866ba7f..3ef7ccd6bfab 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -272,14 +272,15 @@ class WindowStateAnimator { } mDrawState = COMMIT_DRAW_PENDING; layoutNeeded = true; + } - if (postDrawTransaction != null) { + if (postDrawTransaction != null) { + if (mLastHidden) { mPostDrawTransaction.merge(postDrawTransaction); + layoutNeeded = true; + } else { + postDrawTransaction.apply(); } - } else if (postDrawTransaction != null) { - // If draw state is not pending we may delay applying this transaction from the client, - // so apply it now. - postDrawTransaction.apply(); } return layoutNeeded; 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/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index f60b35499013..7f8168af944a 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -170,13 +170,13 @@ static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong ptr) { wrapper->hal()->off(); } -static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jint amplitude) { +static void vibratorSetAmplitude(JNIEnv* env, jclass /* clazz */, jlong ptr, jfloat amplitude) { VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr); if (wrapper == nullptr) { ALOGE("vibratorSetAmplitude failed because native wrapper was not initialized"); return; } - wrapper->hal()->setAmplitude(static_cast<int32_t>(amplitude)); + wrapper->hal()->setAmplitude(static_cast<float>(amplitude)); } static void vibratorSetExternalControl(JNIEnv* env, jclass /* clazz */, jlong ptr, @@ -313,9 +313,9 @@ static const JNINativeMethod method_table[] = { {"isAvailable", "(J)Z", (void*)vibratorIsAvailable}, {"on", "(JJJ)V", (void*)vibratorOn}, {"off", "(J)V", (void*)vibratorOff}, - {"setAmplitude", "(JI)V", (void*)vibratorSetAmplitude}, + {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude}, {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect}, - {"performComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J", + {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J", (void*)vibratorPerformComposedEffect}, {"getSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects}, {"getSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives}, @@ -334,11 +334,10 @@ int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env jclass listenerClass = FindClassOrDie(env, listenerClassName); sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(IJ)V"); - jclass primitiveClass = - FindClassOrDie(env, "android/os/VibrationEffect$Composition$PrimitiveEffect"); - sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I"); - sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F"); - sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I"); + jclass primitiveClass = FindClassOrDie(env, "android/os/vibrator/PrimitiveSegment"); + sPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "mPrimitiveId", "I"); + sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "mScale", "F"); + sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "mDelay", "I"); return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorController$NativeWrapper", diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index aed13b263a7f..56e2385ca548 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -139,12 +139,13 @@ class ActiveAdmin { private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id"; private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS = "admin-can-grant-sensors-permissions"; - private static final String TAG_NETWORK_SLICING_ENABLED = "network-slicing-enabled"; + private static final String TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED = + "enterprise-network-preference-enabled"; private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling"; private static final String ATTR_VALUE = "value"; private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; - private static final boolean NETWORK_SLICING_ENABLED_DEFAULT = true; + private static final boolean ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT = true; DeviceAdminInfo info; @@ -284,7 +285,8 @@ class ActiveAdmin { public String mOrganizationId; public String mEnrollmentSpecificId; public boolean mAdminCanGrantSensorsPermissions; - public boolean mNetworkSlicingEnabled = NETWORK_SLICING_ENABLED_DEFAULT; + public boolean mEnterpriseNetworkPreferenceEnabled = + ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT; private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true; boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT; @@ -555,8 +557,9 @@ class ActiveAdmin { } writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS, mAdminCanGrantSensorsPermissions); - if (mNetworkSlicingEnabled != NETWORK_SLICING_ENABLED_DEFAULT) { - writeAttributeValueToXml(out, TAG_NETWORK_SLICING_ENABLED, mNetworkSlicingEnabled); + if (mEnterpriseNetworkPreferenceEnabled != ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT) { + writeAttributeValueToXml(out, TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED, + mEnterpriseNetworkPreferenceEnabled); } if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) { writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled); @@ -784,9 +787,9 @@ class ActiveAdmin { mAlwaysOnVpnPackage = parser.getAttributeValue(null, ATTR_VALUE); } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) { mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false); - } else if (TAG_NETWORK_SLICING_ENABLED.equals(tag)) { - mNetworkSlicingEnabled = parser.getAttributeBoolean( - null, ATTR_VALUE, NETWORK_SLICING_ENABLED_DEFAULT); + } else if (TAG_ENTERPRISE_NETWORK_PREFERENCE_ENABLED.equals(tag)) { + mEnterpriseNetworkPreferenceEnabled = parser.getAttributeBoolean( + null, ATTR_VALUE, ENTERPRISE_NETWORK_PREFERENCE_ENABLED_DEFAULT); } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) { mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false); } else if (TAG_PASSWORD_COMPLEXITY.equals(tag)) { @@ -1142,8 +1145,8 @@ class ActiveAdmin { pw.print("mAlwaysOnVpnLockdown="); pw.println(mAlwaysOnVpnLockdown); - pw.print("mNetworkSlicingEnabled="); - pw.println(mNetworkSlicingEnabled); + pw.print("mEnterpriseNetworkPreferenceEnabled="); + pw.println(mEnterpriseNetworkPreferenceEnabled); pw.print("mCommonCriteriaMode="); pw.println(mCommonCriteriaMode); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index cdd5a92bec7a..55ab8c3b1af6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -109,6 +109,14 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { return 0; } + @Override + public void acknowledgeDeviceCompliant() {} + + @Override + public boolean isComplianceAcknowledgementRequired() { + return false; + } + public boolean canProfileOwnerResetPasswordWhenLocked(int userId) { return false; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 283895bb53e2..8739a01e00c7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; @@ -102,8 +103,8 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; -// TODO (b/178655595) import static android.net.ConnectivityManager.USER_PREFERENCE_ENTERPRISE; -// TODO (b/178655595) import static android.net.ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; @@ -687,12 +688,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final boolean ENABLE_LOCK_GUARD = true; - /** Profile off deadline is not set or more than MANAGED_PROFILE_OFF_WARNING_PERIOD away. */ - private static final int PROFILE_OFF_DEADLINE_DEFAULT = 0; - /** Profile off deadline is closer than MANAGED_PROFILE_OFF_WARNING_PERIOD. */ - private static final int PROFILE_OFF_DEADLINE_WARNING = 1; - /** Profile off deadline reached, notify the user that personal apps blocked. */ - private static final int PROFILE_OFF_DEADLINE_REACHED = 2; + /** + * Profile off deadline is not set or more than MANAGED_PROFILE_OFF_WARNING_PERIOD away, or the + * user is running unlocked, no need for notification. + */ + private static final int PROFILE_OFF_NOTIFICATION_NONE = 0; + /** + * Profile off deadline is closer than MANAGED_PROFILE_OFF_WARNING_PERIOD. + */ + private static final int PROFILE_OFF_NOTIFICATION_WARNING = 1; + /** + * Profile off deadline reached, notify the user that personal apps blocked. + */ + private static final int PROFILE_OFF_NOTIFICATION_SUSPENDED = 2; interface Stats { int LOCK_GUARD_GUARD = 0; @@ -889,10 +897,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } if (isManagedProfile(userHandle)) { Slog.d(LOG_TAG, "Managed profile became unlocked"); - if (updatePersonalAppsSuspension(userHandle, true /* unlocked */) - == PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT) { - triggerPolicyComplianceCheck(userHandle); - } + final boolean suspended = + updatePersonalAppsSuspension(userHandle, true /* unlocked */); + triggerPolicyComplianceCheckIfNeeded(userHandle, suspended); } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { handlePackagesChanged(null /* check all admins */, userHandle); @@ -3089,12 +3096,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updatePermissionPolicyCache(userId); updateAdminCanGrantSensorsPermissionCache(userId); - boolean enableEnterpriseNetworkSlice = true; + boolean enableEnterpriseNetworkPreferenceEnabled = true; synchronized (getLockObject()) { ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId); - enableEnterpriseNetworkSlice = owner != null ? owner.mNetworkSlicingEnabled : true; + enableEnterpriseNetworkPreferenceEnabled = owner != null + ? owner.mEnterpriseNetworkPreferenceEnabled : true; } - updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkSlice); + updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkPreferenceEnabled); startOwnerService(userId, "start-user"); } @@ -10598,21 +10606,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); @@ -10626,10 +10671,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 @@ -10655,9 +10725,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 @@ -11423,30 +11499,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void setNetworkSlicingEnabled(boolean enabled) { + public void setEnterpriseNetworkPreferenceEnabled(boolean enabled) { if (!mHasFeature) { return; } final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(isProfileOwner(caller), - "Caller is not profile owner; only profile owner may control the network slicing"); + "Caller is not profile owner;" + + " only profile owner may control the enterprise network preference"); synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked( caller.getUserId()); - if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) { - requiredAdmin.mNetworkSlicingEnabled = enabled; + if (requiredAdmin != null + && requiredAdmin.mEnterpriseNetworkPreferenceEnabled != enabled) { + requiredAdmin.mEnterpriseNetworkPreferenceEnabled = enabled; saveSettingsLocked(caller.getUserId()); } } updateNetworkPreferenceForUser(caller.getUserId(), enabled); DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.SET_NETWORK_SLICING_ENABLED) + .createEvent(DevicePolicyEnums.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED) .setBoolean(enabled) .write(); } @Override - public boolean isNetworkSlicingEnabled(int userHandle) { + public boolean isEnterpriseNetworkPreferenceEnabled(int userHandle) { if (!mHasFeature) { return false; } @@ -11457,7 +11535,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle); if (requiredAdmin != null) { - return requiredAdmin.mNetworkSlicingEnabled; + return requiredAdmin.mEnterpriseNetworkPreferenceEnabled; } else { return false; } @@ -16040,7 +16118,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - // DO shouldn't be able to use this method. Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); Preconditions.checkState(canHandleCheckPolicyComplianceIntent(caller)); @@ -16071,18 +16148,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } - /** Starts an activity to check policy compliance in the DPC. */ - private void triggerPolicyComplianceCheck(int profileUserId) { - final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE); + /** Starts an activity to check policy compliance or request compliance acknowledgement. */ + private void triggerPolicyComplianceCheckIfNeeded(int profileUserId, boolean suspended) { synchronized (getLockObject()) { final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId); if (profileOwner == null) { Slog.wtf(LOG_TAG, "Profile owner not found for compliance check"); return; } - intent.setPackage(profileOwner.info.getPackageName()); + if (suspended) { + // If suspended, DPC will need to show an activity. + final Intent intent = new Intent(ACTION_CHECK_POLICY_COMPLIANCE); + intent.setPackage(profileOwner.info.getPackageName()); + mContext.startActivityAsUser(intent, UserHandle.of(profileUserId)); + } else if (profileOwner.mProfileOffDeadline > 0) { + // If not suspended, but deadline set, DPC needs to acknowledge compliance so that + // the deadline can be reset. + sendAdminCommandLocked(profileOwner, ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED, + /* adminExtras= */ null, /* receiver= */ null, /* inForeground = */ true); + } } - mContext.startActivityAsUser(intent, UserHandle.of(profileUserId)); } /** @@ -16091,39 +16176,35 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * * @param unlocked whether the profile is currently running unlocked. */ - private @PersonalAppsSuspensionReason int updatePersonalAppsSuspension( - int profileUserId, boolean unlocked) { - final boolean suspendedExplicitly; - final boolean suspendedByTimeout; + private boolean updatePersonalAppsSuspension(int profileUserId, boolean unlocked) { + final boolean shouldSuspend; synchronized (getLockObject()) { final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId); if (profileOwner != null) { - final int deadlineState = - updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked); - suspendedExplicitly = profileOwner.mSuspendPersonalApps; - suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED; - Slog.d(LOG_TAG, "Personal apps suspended explicitly: %b, deadline state: %d", - suspendedExplicitly, deadlineState); final int notificationState = - unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState; + updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked); + final boolean suspendedExplicitly = profileOwner.mSuspendPersonalApps; + final boolean suspendedByTimeout = profileOwner.mProfileOffDeadline == -1; + Slog.d(LOG_TAG, + "Personal apps suspended explicitly: %b, by timeout: %b, notification: %d", + suspendedExplicitly, suspendedByTimeout, notificationState); updateProfileOffDeadlineNotificationLocked( profileUserId, profileOwner, notificationState); + shouldSuspend = suspendedExplicitly || suspendedByTimeout; } else { - suspendedExplicitly = false; - suspendedByTimeout = false; + shouldSuspend = false; } } final int parentUserId = getProfileParentId(profileUserId); - suspendPersonalAppsInternal(parentUserId, suspendedExplicitly || suspendedByTimeout); - - return makeSuspensionReasons(suspendedExplicitly, suspendedByTimeout); + suspendPersonalAppsInternal(parentUserId, shouldSuspend); + return shouldSuspend; } /** * Checks work profile time off policy, scheduling personal apps suspension via alarm if * necessary. - * @return profile deadline state + * @return notification state */ private int updateProfileOffDeadlineLocked( int profileUserId, ActiveAdmin profileOwner, boolean unlocked) { @@ -16135,7 +16216,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { profileOwner.mProfileOffDeadline = -1; saveSettingsLocked(profileUserId); } - return PROFILE_OFF_DEADLINE_REACHED; + return unlocked ? PROFILE_OFF_NOTIFICATION_NONE : PROFILE_OFF_NOTIFICATION_SUSPENDED; } boolean shouldSaveSettings = false; if (profileOwner.mSuspendPersonalApps) { @@ -16145,8 +16226,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { shouldSaveSettings = true; } } else if (profileOwner.mProfileOffDeadline != 0 - && (profileOwner.mProfileMaximumTimeOffMillis == 0 || unlocked)) { - // There is a deadline but either there is no policy or the profile is unlocked -> clear + && (profileOwner.mProfileMaximumTimeOffMillis == 0)) { + // There is a deadline but either there is no policy -> clear // the deadline. Slog.i(LOG_TAG, "Profile off deadline is reset to zero"); profileOwner.mProfileOffDeadline = 0; @@ -16165,19 +16246,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final long alarmTime; - final int deadlineState; - if (profileOwner.mProfileOffDeadline == 0) { + final int notificationState; + if (unlocked || profileOwner.mProfileOffDeadline == 0) { alarmTime = 0; - deadlineState = PROFILE_OFF_DEADLINE_DEFAULT; + notificationState = PROFILE_OFF_NOTIFICATION_NONE; } else if (profileOwner.mProfileOffDeadline - now < MANAGED_PROFILE_OFF_WARNING_PERIOD) { // The deadline is close, upon the alarm personal apps should be suspended. alarmTime = profileOwner.mProfileOffDeadline; - deadlineState = PROFILE_OFF_DEADLINE_WARNING; + notificationState = PROFILE_OFF_NOTIFICATION_WARNING; } else { // The deadline is quite far, upon the alarm we should warn the user first, so the // alarm is scheduled earlier than the actual deadline. alarmTime = profileOwner.mProfileOffDeadline - MANAGED_PROFILE_OFF_WARNING_PERIOD; - deadlineState = PROFILE_OFF_DEADLINE_DEFAULT; + notificationState = PROFILE_OFF_NOTIFICATION_NONE; } final AlarmManager am = mInjector.getAlarmManager(); @@ -16197,7 +16278,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { am.set(AlarmManager.RTC, alarmTime, pi); } - return deadlineState; + return notificationState; } private void suspendPersonalAppsInternal(int userId, boolean suspended) { @@ -16239,7 +16320,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @GuardedBy("getLockObject()") private void updateProfileOffDeadlineNotificationLocked( int profileUserId, ActiveAdmin profileOwner, int notificationState) { - if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) { + if (notificationState == PROFILE_OFF_NOTIFICATION_NONE) { mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED); return; } @@ -16260,7 +16341,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final String text; final boolean ongoing; - if (notificationState == PROFILE_OFF_DEADLINE_WARNING) { + if (notificationState == PROFILE_OFF_NOTIFICATION_WARNING) { // Round to the closest integer number of days. final int maxDays = (int) ((profileOwner.mProfileMaximumTimeOffMillis + MS_PER_DAY / 2) / MS_PER_DAY); @@ -16351,7 +16432,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - // DO shouldn't be able to use this method. Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); synchronized (getLockObject()) { @@ -16361,6 +16441,33 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void acknowledgeDeviceCompliant() { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); + enforceUserUnlocked(caller.getUserId()); + + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerLocked(caller); + if (admin.mProfileOffDeadline > 0) { + admin.mProfileOffDeadline = 0; + saveSettingsLocked(caller.getUserId()); + } + } + } + + @Override + public boolean isComplianceAcknowledgementRequired() { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); + enforceUserUnlocked(caller.getUserId()); + + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerLocked(caller); + return admin.mProfileOffDeadline != 0; + } + } + + @Override public boolean canProfileOwnerResetPasswordWhenLocked(int userId) { enforceSystemCaller("call canProfileOwnerResetPasswordWhenLocked"); synchronized (getLockObject()) { @@ -17006,18 +17113,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void updateNetworkPreferenceForUser(int userId, boolean enableEnterprise) { + private void updateNetworkPreferenceForUser(int userId, + boolean enableEnterpriseNetworkPreferenceEnabled) { if (!isManagedProfile(userId)) { return; } - // TODO(b/178655595) - // int networkPreference = enable ? ConnectivityManager.USER_PREFERENCE_ENTERPRISE : - // ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT; - // mInjector.binderWithCleanCallingIdentity(() -> - // mInjector.getConnectivityManager().setNetworkPreferenceForUser( - // UserHandle.of(userId), - // networkPreference, - // null /* executor */, null /* listener */)); + int networkPreference = enableEnterpriseNetworkPreferenceEnabled + ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT; + mInjector.binderWithCleanCallingIdentity(() -> + mInjector.getConnectivityManager().setProfileNetworkPreference( + UserHandle.of(userId), + networkPreference, + null /* executor */, null /* listener */)); } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java index fa6ef006c61d..5f35a26feefb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java @@ -149,7 +149,8 @@ public class RemoteBugreportManager { .setLocalOnly(true) .setContentIntent(pendingDialogIntent) .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); + com.android.internal.R.color.system_notification_accent_color)) + .extend(new Notification.TvExtender()); if (type == NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) { builder.setContentTitle(mContext.getString( diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 932e99783c83..94f8e59c6057 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -81,8 +81,8 @@ struct Constants { static constexpr auto bindingTimeout = 1min; - // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs) - static constexpr auto minBindDelay = 10s; + // 1s, 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs) + static constexpr auto minBindDelay = 1s; static constexpr auto maxBindDelay = 10000s; static constexpr auto bindDelayMultiplier = 10; static constexpr auto bindDelayJitterDivider = 10; @@ -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/path.cpp b/services/incremental/path.cpp index 338659d40b46..bf4e9616057c 100644 --- a/services/incremental/path.cpp +++ b/services/incremental/path.cpp @@ -44,19 +44,20 @@ bool PathLess::operator()(std::string_view l, std::string_view r) const { PathCharsLess()); } -static void preparePathComponent(std::string_view& path, bool trimFront) { - if (trimFront) { - while (!path.empty() && path.front() == '/') { - path.remove_prefix(1); - } +static void preparePathComponent(std::string_view& path, bool trimAll) { + // need to check for double front slash as a single one has a separate meaning in front + while (!path.empty() && path.front() == '/' && + (trimAll || (path.size() > 1 && path[1] == '/'))) { + path.remove_prefix(1); } - while (!path.empty() && path.back() == '/') { + // for the back we don't care about double-vs-single slash difference + while (path.size() > !trimAll && path.back() == '/') { path.remove_suffix(1); } } void details::append_next_path(std::string& target, std::string_view path) { - preparePathComponent(path, true); + preparePathComponent(path, !target.empty()); if (path.empty()) { return; } diff --git a/services/incremental/path.h b/services/incremental/path.h index 3e5fd21b6535..e12e1d027055 100644 --- a/services/incremental/path.h +++ b/services/incremental/path.h @@ -89,15 +89,25 @@ std::optional<bool> isEmptyDir(std::string_view dir); bool startsWith(std::string_view path, std::string_view prefix); template <class... Paths> -std::string join(std::string_view first, std::string_view second, Paths&&... paths) { - std::string result; +std::string join(std::string&& first, std::string_view second, Paths&&... paths) { + std::string& result = first; { using std::size; result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths))); } - result.assign(first); - (details::append_next_path(result, second), ..., details::append_next_path(result, paths)); - return result; + (details::append_next_path(result, second), ..., + details::append_next_path(result, std::forward<Paths>(paths))); + return std::move(result); +} + +template <class... Paths> +std::string join(std::string_view first, std::string_view second, Paths&&... paths) { + return path::join(std::string(), first, second, std::forward<Paths>(paths)...); +} + +template <class... Paths> +std::string join(const char* first, std::string_view second, Paths&&... paths) { + return path::join(std::string_view(first), second, std::forward<Paths>(paths)...); } } // namespace android::incremental::path diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index de8822dbf105..ddb778462df5 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -272,12 +272,20 @@ public: } return binder::Status::ok(); } + binder::Status bindToDataLoaderOkWith1sDelay(int32_t mountId, + const DataLoaderParamsParcel& params, + int bindDelayMs, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) { + CHECK(100 * 9 <= bindDelayMs && bindDelayMs <= 100 * 11) << bindDelayMs; + return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); + } binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId, const DataLoaderParamsParcel& params, int bindDelayMs, const sp<IDataLoaderStatusListener>& listener, bool* _aidl_return) { - CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs; + CHECK(100 * 9 * 9 <= bindDelayMs && bindDelayMs <= 100 * 11 * 11) << bindDelayMs; return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); } binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId, @@ -285,7 +293,7 @@ public: int bindDelayMs, const sp<IDataLoaderStatusListener>& listener, bool* _aidl_return) { - CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs; + CHECK(100 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 100 * 11 * 11 * 11) << bindDelayMs; return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); } binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId, @@ -293,7 +301,8 @@ public: int bindDelayMs, const sp<IDataLoaderStatusListener>& listener, bool* _aidl_return) { - CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs; + CHECK(100 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 100 * 11 * 11 * 11 * 11) + << bindDelayMs; return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); } binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId, @@ -301,7 +310,7 @@ public: int bindDelayMs, const sp<IDataLoaderStatusListener>& listener, bool* _aidl_return) { - CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11) + CHECK(100 * 9 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 100 * 11 * 11 * 11 * 11 * 11) << bindDelayMs; return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return); } @@ -370,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, @@ -377,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()); @@ -865,10 +876,10 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { } TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) { - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(7); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); - EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6); - EXPECT_CALL(*mDataLoader, start(_)).Times(6); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(7); + EXPECT_CALL(*mDataLoader, start(_)).Times(7); EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; @@ -882,6 +893,11 @@ TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) { ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith1sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay)); mDataLoaderManager->setDataLoaderStatusDestroyed(); @@ -912,10 +928,10 @@ TEST_F(IncrementalServiceTest, testDataLoaderOnRestart) { constexpr auto bindRetryInterval = 5s; - EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(10); + EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(11); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1); - EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6); - EXPECT_CALL(*mDataLoader, start(_)).Times(6); + EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(7); + EXPECT_CALL(*mDataLoader, start(_)).Times(7); EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4); @@ -995,6 +1011,11 @@ TEST_F(IncrementalServiceTest, testDataLoaderOnRestart) { // Simulated crash/other connection breakage. ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) .WillByDefault(Invoke(mDataLoaderManager, + &MockDataLoaderManager::bindToDataLoaderOkWith1sDelay)); + mDataLoaderManager->setDataLoaderStatusDestroyed(); + + ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(mDataLoaderManager, &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay)); mDataLoaderManager->setDataLoaderStatusDestroyed(); @@ -1575,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); @@ -1586,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); @@ -1597,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")); @@ -1716,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(_)) @@ -1732,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)); @@ -1778,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(_)) @@ -1794,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(_)) @@ -1813,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); @@ -1988,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(); @@ -1999,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); @@ -2013,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/incremental/test/path_test.cpp b/services/incremental/test/path_test.cpp index cbe479e10087..4a8ae5b26c49 100644 --- a/services/incremental/test/path_test.cpp +++ b/services/incremental/test/path_test.cpp @@ -40,4 +40,24 @@ TEST(Path, Comparator) { EXPECT_TRUE(!PathLess()("/a/b", "/a")); } +TEST(Path, Join) { + EXPECT_STREQ("", path::join("", "").c_str()); + + EXPECT_STREQ("/", path::join("", "/").c_str()); + EXPECT_STREQ("/", path::join("/", "").c_str()); + EXPECT_STREQ("/", path::join("/", "/").c_str()); + EXPECT_STREQ("/", path::join("/"s, "/").c_str()); + EXPECT_STREQ("/", path::join("/"sv, "/").c_str()); + EXPECT_STREQ("/", path::join("/", "/", "/", "/", "/", "/", "/", "/", "/", "/").c_str()); + + EXPECT_STREQ("/a/b/c/d", path::join("/a/b/"s, "c", "d").c_str()); + EXPECT_STREQ("/a/b/c/d", path::join("/a/b/", "c", "d").c_str()); + EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b/", "c", "d").c_str()); + EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b", "c", "d").c_str()); + EXPECT_STREQ("/a/b/c/d", path::join("/", "//a/b//", "c", "d").c_str()); + EXPECT_STREQ("/a/b/c/d", path::join("", "", "/", "//a/b//", "c", "d").c_str()); + EXPECT_STREQ("/a/b/c/d", path::join(""s, "", "/", "//a/b//", "c", "d").c_str()); + EXPECT_STREQ("/a/b/c/d", path::join("/a/b", "", "", "/", "", "/", "/", "/c", "d").c_str()); +} + } // namespace android::incremental::path diff --git a/services/net/Android.bp b/services/net/Android.bp index b01e42516358..800f7addbd65 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -83,3 +83,15 @@ filegroup { "//packages/modules/Connectivity/Tethering" ], } + +filegroup { + name: "services-connectivity-shared-srcs", + srcs: [ + // TODO: move to networkstack-client + "java/android/net/IpMemoryStore.java", + "java/android/net/NetworkMonitorManager.java", + // TODO: move to libs/net + "java/android/net/util/KeepalivePacketDataUtil.java", + "java/android/net/util/NetworkConstants.java", + ], +} diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index a262939c0ef9..29aedcea0cd2 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -295,10 +295,30 @@ public final class ProfcollectForwardingService extends SystemService { return; } - try { - mIProfcollect.report(); - } catch (RemoteException e) { - Log.e(LOG_TAG, e.getMessage()); - } + final boolean uploadReport = + DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, + "upload_report", false); + + new Thread(() -> { + try { + String reportPath = mIProfcollect.report(); + if (!uploadReport) { + return; + } + Intent uploadIntent = + new Intent("com.google.android.apps.betterbug.intent.action.UPLOAD_PROFILE") + .setPackage("com.google.android.apps.internal.betterbug") + .putExtra("EXTRA_DESTINATION", "PROFCOLLECT") + .putExtra("EXTRA_PACKAGE_NAME", getContext().getPackageName()) + .putExtra("EXTRA_PROFILE_PATH", reportPath) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + Context context = getContext(); + if (context.getPackageManager().queryBroadcastReceivers(uploadIntent, 0) != null) { + context.sendBroadcast(uploadIntent); + } + } catch (RemoteException e) { + Log.e(LOG_TAG, e.getMessage()); + } + }).start(); } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 1b0a305b5bdd..537a49e4adcd 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -417,7 +417,7 @@ class DomainVerificationEnforcerTest { allowQueryAll.set(true) - runMethod(target, NON_VERIFIER_UID) + assertFails { runMethod(target, NON_VERIFIER_UID) } } private fun approvedVerifier() { @@ -816,7 +816,7 @@ class DomainVerificationEnforcerTest { // System/shell only INTERNAL, - // INTERNAL || domain verification agent || user setting permission holder + // INTERNAL || non-legacy domain verification agent QUERENT, // INTERNAL || domain verification agent diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java index 8ae4c5ae96a3..14c02d53efad 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager; +import android.content.pm.verify.domain.DomainVerificationManager; import com.android.server.pm.verify.domain.DomainVerificationService; @@ -45,4 +46,16 @@ class DomainVerificationJavaUtil { throws PackageManager.NameNotFoundException { return service.setDomainVerificationUserSelection(domainSetId, domains, enabled, userId); } + + static int setStatusForceNullable(@NonNull DomainVerificationManager manager, + @Nullable UUID domainSetId, @Nullable Set<String> domains, int state) + throws PackageManager.NameNotFoundException { + return manager.setDomainVerificationStatus(domainSetId, domains, state); + } + + static int setUserSelectionForceNullable(@NonNull DomainVerificationManager manager, + @Nullable UUID domainSetId, @Nullable Set<String> domains, boolean enabled) + throws PackageManager.NameNotFoundException { + return manager.setDomainVerificationUserSelection(domainSetId, domains, enabled); + } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index ef79b08aa1c5..881604ffe6f6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -16,6 +16,7 @@ package com.android.server.pm.test.verify.domain +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.parsing.component.ParsedActivity @@ -23,6 +24,7 @@ import android.content.pm.parsing.component.ParsedIntentInfo import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationUserState +import android.content.pm.verify.domain.IDomainVerificationManager import android.os.Build import android.os.PatternMatcher import android.os.Process @@ -127,15 +129,17 @@ class DomainVerificationManagerApiTest { assertThat(service.setStatus(UUID_INVALID, setOf(DOMAIN_1), 1100)) .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID) - assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, null, - setOf(DOMAIN_1), 1100)) - .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL) + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setStatusForceNullable(service, null, setOf(DOMAIN_1), 1100) + } - assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null, - 1100)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null, 1100) + } - assertThat(service.setStatus(UUID_ONE, emptySet(), 1100)) - .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + assertFailsWith(IllegalArgumentException::class) { + service.setStatus(UUID_ONE, emptySet(), 1100) + } assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100)) .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) @@ -143,8 +147,9 @@ class DomainVerificationManagerApiTest { assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), 1100)) .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) - assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15)) - .isEqualTo(DomainVerificationManager.ERROR_INVALID_STATE_CODE) + assertFailsWith(IllegalArgumentException::class) { + service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15) + } map.clear() assertFailsWith(PackageManager.NameNotFoundException::class){ @@ -198,15 +203,19 @@ class DomainVerificationManagerApiTest { assertThat(service.setUserSelection(UUID_INVALID, setOf(DOMAIN_1), true, 0)) .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID) - assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null, - setOf(DOMAIN_1), true, 0)) - .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL) + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null, + setOf(DOMAIN_1), true, 0) + } - assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null, - true, 0)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null, + true, 0) + } - assertThat(service.setUserSelection(UUID_ONE, emptySet(), true, 0)) - .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY) + assertFailsWith(IllegalArgumentException::class) { + service.setUserSelection(UUID_ONE, emptySet(), true, 0) + } assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0)) .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN) @@ -297,6 +306,48 @@ class DomainVerificationManagerApiTest { assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty() } + @Test + fun appProcessManager() { + // The app side DomainVerificationManager also has to do some argument enforcement since + // the input values are transformed before they are sent across Binder. Verify that here. + + // Mock nothing to ensure no calls are made before failing + val context = mockThrowOnUnmocked<Context>() + val binderInterface = mockThrowOnUnmocked<IDomainVerificationManager>() + + val manager = DomainVerificationManager(context, binderInterface) + + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setStatusForceNullable(manager, null, setOf(DOMAIN_1), 1100) + } + + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setStatusForceNullable(manager, UUID_ONE, null, 1100) + } + + assertFailsWith(IllegalArgumentException::class) { + manager.setDomainVerificationStatus(UUID_ONE, emptySet(), 1100) + } + + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setUserSelectionForceNullable( + manager, null, + setOf(DOMAIN_1), true + ) + } + + assertFailsWith(IllegalArgumentException::class) { + DomainVerificationJavaUtil.setUserSelectionForceNullable( + manager, UUID_ONE, + null, true + ) + } + + assertFailsWith(IllegalArgumentException::class) { + manager.setDomainVerificationUserSelection(UUID_ONE, emptySet(), true) + } + } + private fun makeService(vararg pkgSettings: PackageSetting) = makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 28940b34c82a..84819619ad79 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -21,6 +21,7 @@ import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; import static android.app.AlarmManager.FLAG_IDLE_UNTIL; +import static android.app.AlarmManager.FLAG_PRIORITIZE; import static android.app.AlarmManager.FLAG_STANDALONE; import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; import static android.app.AlarmManager.RTC; @@ -62,6 +63,7 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INT import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW; +import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY; import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX; import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY; import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; @@ -443,6 +445,12 @@ public class AlarmManagerServiceTest { TEST_CALLING_UID); } + private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) { + mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", + FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID, + TEST_CALLING_PACKAGE, null); + } + private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi, boolean unrestricted) { final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE; @@ -579,6 +587,7 @@ public class AlarmManagerServiceTest { setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 40); setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 45); setDeviceConfigLong(KEY_MIN_WINDOW, 50); + setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, 55); assertEquals(5, mService.mConstants.MIN_FUTURITY); assertEquals(10, mService.mConstants.MIN_INTERVAL); assertEquals(15, mService.mConstants.MAX_INTERVAL); @@ -589,6 +598,7 @@ public class AlarmManagerServiceTest { assertEquals(40, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION); assertEquals(45, mService.mConstants.LISTENER_TIMEOUT); assertEquals(50, mService.mConstants.MIN_WINDOW); + assertEquals(55, mService.mConstants.PRIORITY_ALARM_DELAY); } @Test @@ -1586,6 +1596,89 @@ public class AlarmManagerServiceTest { } @Test + public void prioritizedAlarmsInBatterySaver() throws Exception { + when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(true); + when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); + final long minDelay = 5; + setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay); + + final long firstTrigger = mNowElapsedTest + 4; + final AtomicInteger alarmsFired = new AtomicInteger(0); + final int numAlarms = 10; + for (int i = 0; i < numAlarms; i++) { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) + throws RemoteException { + alarmsFired.incrementAndGet(); + } + }); + } + assertEquals(firstTrigger, mTestTimer.getElapsed()); + mNowElapsedTest = firstTrigger; + mTestTimer.expire(); + while (alarmsFired.get() < numAlarms) { + assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + assertEquals(numAlarms, alarmsFired.get()); + } + + @Test + public void prioritizedAlarmsInDeviceIdle() throws Exception { + doReturn(0).when(mService).fuzzForDuration(anyLong()); + + final long minDelay = 5; + setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay); + + final long idleUntil = mNowElapsedTest + 1000; + setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil, getNewMockPendingIntent()); + assertNotNull(mService.mPendingIdleUntil); + + final long firstTrigger = mNowElapsedTest + 4; + final AtomicInteger alarmsFired = new AtomicInteger(0); + final int numAlarms = 10; + for (int i = 0; i < numAlarms; i++) { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) + throws RemoteException { + alarmsFired.incrementAndGet(); + } + }); + } + assertEquals(firstTrigger, mTestTimer.getElapsed()); + mNowElapsedTest = firstTrigger; + mTestTimer.expire(); + while (alarmsFired.get() < numAlarms) { + assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + assertEquals(numAlarms, alarmsFired.get()); + + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }); + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }); + assertEquals(idleUntil - 3, mTestTimer.getElapsed()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + + assertEquals(idleUntil, mTestTimer.getElapsed()); + } + + @Test public void dispatchOrder() throws Exception { doReturn(0).when(mService).fuzzForDuration(anyLong()); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java index 22b2f7e04069..d220444f47de 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java @@ -1015,7 +1015,7 @@ public class ApplicationExitInfoTest { assertNotNull(info); if (timestamp != null) { - final long tolerance = 1000; // ms + final long tolerance = 10000; // ms assertTrue(timestamp - tolerance <= info.getTimestamp()); assertTrue(timestamp + tolerance >= info.getTimestamp()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index ee1a4f4b3578..4bab8e51874c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -5416,9 +5416,8 @@ public class QuotaControllerTest { inOrder.verify(mJobSchedulerService, timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(); - // Top and bg EJs should still be allowed to run since they started before the app ran - // out of quota. - assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); + // Top should still be "in quota" since it started before the app ran on top out of quota. + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse( jobUnstarted.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); @@ -5467,7 +5466,7 @@ public class QuotaControllerTest { // wasn't started. Top should remain in quota since it started when the app was in TOP. assertTrue(jobTop2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); - assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); + assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); trackJobs(jobBg2); assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_EXPEDITED_QUOTA)); assertFalse( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 17c6b6fba861..db0c3aece023 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -36,6 +36,7 @@ import android.os.SystemProperties import android.os.UserHandle import android.os.UserManager import android.os.incremental.IncrementalManager +import android.provider.DeviceConfig import android.util.ArrayMap import android.util.DisplayMetrics import android.util.EventLog @@ -131,6 +132,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { .mockStatic(LockGuard::class.java) .mockStatic(EventLog::class.java) .mockStatic(LocalServices::class.java) + .mockStatic(DeviceConfig::class.java) .apply(withSession) session = apply.startMocking() whenever(mocks.settings.insertPackageSettingLPw( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt index a0e208f662e3..46487ea27959 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt @@ -17,8 +17,13 @@ package com.android.server.pm import android.os.Build +import android.provider.DeviceConfig +import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION import com.android.server.apphibernation.AppHibernationManagerInternal +import com.android.server.extendedtestutils.wheneverStatic import com.android.server.testutils.whenever +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -33,7 +38,10 @@ class PackageManagerServiceHibernationTests { companion object { val TEST_PACKAGE_NAME = "test.package" + val TEST_PACKAGE_2_NAME = "test.package2" val TEST_USER_ID = 0 + + val KEY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled" } @Rule @@ -47,6 +55,8 @@ class PackageManagerServiceHibernationTests { @Throws(Exception::class) fun setup() { MockitoAnnotations.initMocks(this) + wheneverStatic { DeviceConfig.getBoolean( + NAMESPACE_APP_HIBERNATION, KEY_APP_HIBERNATION_ENABLED, false) }.thenReturn(true) rule.system().stageNominalSystemState() whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java)) .thenReturn(appHibernationManager) @@ -68,6 +78,28 @@ class PackageManagerServiceHibernationTests { verify(appHibernationManager).setHibernatingGlobally(TEST_PACKAGE_NAME, false) } + @Test + fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() { + rule.system().stageScanExistingPackage( + TEST_PACKAGE_NAME, + 1L, + rule.system().dataAppDirectory, + withPackage = { it.apply { isHasCode = true } }) + rule.system().stageScanExistingPackage( + TEST_PACKAGE_2_NAME, + 1L, + rule.system().dataAppDirectory, + withPackage = { it.apply { isHasCode = true } }) + val pm = createPackageManagerService() + rule.system().validateFinalState() + whenever(appHibernationManager.isHibernatingGlobally(TEST_PACKAGE_2_NAME)).thenReturn(true) + + val optimizablePkgs = pm.optimizablePackages + + assertTrue(optimizablePkgs.contains(TEST_PACKAGE_NAME)) + assertFalse(optimizablePkgs.contains(TEST_PACKAGE_2_NAME)) + } + private fun createPackageManagerService(): PackageManagerService { return PackageManagerService(rule.mocks().injector, false /*coreOnly*/, 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/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index b9aa554aa0ac..9513c6e276b1 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -70,10 +70,14 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD"/> <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/> <uses-permission android:name="android.permission.HARDWARE_TEST"/> <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> <uses-permission android:name="android.permission.DUMP"/> <uses-permission android:name="android.permission.READ_DREAM_STATE"/> diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java index 73a2febf72aa..4a67ec71fcaa 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java @@ -20,6 +20,7 @@ import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_BT; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO; import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI; import static org.junit.Assert.assertArrayEquals; @@ -93,6 +94,16 @@ public class BatteryExternalStatsWorkerTest { tempAllIds.add(btId); mPowerStatsInternal.incrementEnergyConsumption(btId, 34567); + final int gnssId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.GNSS, 0, + "gnss"); + tempAllIds.add(gnssId); + mPowerStatsInternal.incrementEnergyConsumption(gnssId, 787878); + + final int mobileRadioId = mPowerStatsInternal.addEnergyConsumer( + EnergyConsumerType.MOBILE_RADIO, 0, "mobile_radio"); + tempAllIds.add(mobileRadioId); + mPowerStatsInternal.incrementEnergyConsumption(mobileRadioId, 62626); + final int[] cpuClusterIds = new int[numCpuClusters]; for (int i = 0; i < numCpuClusters; i++) { cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer( @@ -135,6 +146,12 @@ public class BatteryExternalStatsWorkerTest { assertEquals(1, bluetoothResults.length); assertEquals(btId, bluetoothResults[0].id); + final EnergyConsumerResult[] mobileRadioResults = + mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_RADIO).getNow(null); + // Results should only have the mobile radio energy consumer + assertEquals(1, mobileRadioResults.length); + assertEquals(mobileRadioId, mobileRadioResults[0].id); + final EnergyConsumerResult[] cpuResults = mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null); // Results should only have the cpu cluster energy consumers diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 9ffb50176f0e..5c8a7d25a25d 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -80,6 +80,7 @@ import android.util.Log; import androidx.test.filters.SmallTest; +import com.android.internal.widget.LockPatternUtils; import com.android.server.FgThread; import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserManagerInternal; @@ -123,6 +124,7 @@ public class UserControllerTest { private static final long HANDLER_WAIT_TIME_MS = 100; private UserController mUserController; + private LockPatternUtils mLockPatternUtils; private TestInjector mInjector; private final HashMap<Integer, UserState> mUserStates = new HashMap<>(); @@ -161,6 +163,13 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); + + // Make it appear that calling unlockUserKey() is needed. + doReturn(true).when(mInjector).isFileEncryptedNativeOnly(); + mLockPatternUtils = mock(LockPatternUtils.class); + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false); + doReturn(mLockPatternUtils).when(mInjector).getLockPatternUtils(); + // All UserController params are set to default. mUserController = new UserController(mInjector); setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS); @@ -552,6 +561,20 @@ public class UserControllerTest { /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true); } + /** + * Test that if a user has a lock screen credential set, then UserController + * doesn't bother trying to unlock their storage key without a credential + * token, as it will never work. + */ + @Test + public void testSecureUserUnlockNotAttempted() throws Exception { + when(mLockPatternUtils.isSecure(eq(TEST_USER_ID1))).thenReturn(true); + setUpUser(TEST_USER_ID1, 0); + mUserController.startUser(TEST_USER_ID1, /* foreground= */ false); + verify(mInjector.mStorageManagerMock, times(0)) + .unlockUserKey(eq(TEST_USER_ID1), anyInt(), any(), any()); + } + @Test public void testStartProfile_fullUserFails() { setUpUser(TEST_USER_ID1, 0); diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java index 3d0895dab7a8..24a8b6131d1d 100644 --- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -49,14 +49,18 @@ public class GameManagerServiceTests { private static final int USER_ID_2 = 1002; // Stolen from ConnectivityServiceTest.MockContext - class MockContext extends ContextWrapper { + static class MockContext extends ContextWrapper { private static final String TAG = "MockContext"; // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); + @Mock + private final MockPackageManager mMockPackageManager; + MockContext(Context base) { super(base); + mMockPackageManager = new MockPackageManager(); } /** @@ -101,6 +105,11 @@ public class GameManagerServiceTests { throw new SecurityException("[Test] permission denied: " + permission); } } + + @Override + public PackageManager getPackageManager() { + return mMockPackageManager; + } } @Mock diff --git a/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java b/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java new file mode 100644 index 000000000000..5edbc16da2c3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/app/MockPackageManager.java @@ -0,0 +1,1133 @@ +/* + * 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.app; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ChangedPackages; +import android.content.pm.FeatureInfo; +import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstantAppInfo; +import android.content.pm.InstrumentationInfo; +import android.content.pm.IntentFilterVerificationInfo; +import android.content.pm.KeySet; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.SharedLibraryInfo; +import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.VersionedPackage; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.UserHandle; +import android.os.storage.VolumeInfo; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.List; + +public class MockPackageManager extends PackageManager { + private static final String TAG = "MockPackageManager"; + + private final ApplicationInfo mApplicationInfo = new ApplicationInfo(); + + public MockPackageManager() { + // Mock the ApplicationInfo, so we can treat the test as a "game". + mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME; + } + + @Override + public PackageInfo getPackageInfo(@NonNull String packageName, int flags) + throws NameNotFoundException { + return null; + } + + @Override + public PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage, int flags) + throws NameNotFoundException { + return null; + } + + @Override + public PackageInfo getPackageInfoAsUser(@NonNull String packageName, int flags, int userId) + throws NameNotFoundException { + return null; + } + + @Override + public String[] currentToCanonicalPackageNames(@NonNull String[] packageNames) { + return new String[0]; + } + + @Override + public String[] canonicalToCurrentPackageNames(@NonNull String[] packageNames) { + return new String[0]; + } + + @Nullable + @Override + public Intent getLaunchIntentForPackage(@NonNull String packageName) { + return null; + } + + @Nullable + @Override + public Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName) { + return null; + } + + @Nullable + @Override + public Intent getCarLaunchIntentForPackage(@NonNull String packageName) { + return null; + } + + @Override + public int[] getPackageGids(@NonNull String packageName) throws NameNotFoundException { + return new int[0]; + } + + @Override + public int[] getPackageGids(@NonNull String packageName, int flags) + throws NameNotFoundException { + return new int[0]; + } + + @Override + public int getPackageUid(@NonNull String packageName, int flags) + throws NameNotFoundException { + return 0; + } + + @Override + public int getPackageUidAsUser(@NonNull String packageName, int userId) + throws NameNotFoundException { + return 0; + } + + @Override + public int getPackageUidAsUser(@NonNull String packageName, int flags, int userId) + throws NameNotFoundException { + return 0; + } + + @Override + public PermissionInfo getPermissionInfo(@NonNull String permName, int flags) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public List<PermissionInfo> queryPermissionsByGroup(@NonNull String permissionGroup, + int flags) throws NameNotFoundException { + return null; + } + + @Override + public boolean arePermissionsIndividuallyControlled() { + return false; + } + + @Override + public boolean isWirelessConsentModeEnabled() { + return false; + } + + @NonNull + @Override + public PermissionGroupInfo getPermissionGroupInfo(@NonNull String groupName, int flags) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public List<PermissionGroupInfo> getAllPermissionGroups(int flags) { + return null; + } + + @NonNull + @Override + public ApplicationInfo getApplicationInfo(@NonNull String packageName, int flags) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, int flags, + int userId) throws NameNotFoundException { + return mApplicationInfo; + } + + @NonNull + @Override + public ActivityInfo getActivityInfo(@NonNull ComponentName component, int flags) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public ServiceInfo getServiceInfo(@NonNull ComponentName component, int flags) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public List<PackageInfo> getInstalledPackages(int flags) { + return null; + } + + @NonNull + @Override + public List<PackageInfo> getPackagesHoldingPermissions(@NonNull String[] permissions, + int flags) { + return null; + } + + @NonNull + @Override + public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) { + return null; + } + + @Override + public int checkPermission(@NonNull String permName, @NonNull String packageName) { + return 0; + } + + @Override + public boolean isPermissionRevokedByPolicy(@NonNull String permName, + @NonNull String packageName) { + return false; + } + + @Override + public boolean addPermission(@NonNull PermissionInfo info) { + return false; + } + + @Override + public boolean addPermissionAsync(@NonNull PermissionInfo info) { + return false; + } + + @Override + public void removePermission(@NonNull String permName) { + + } + + @Override + public void grantRuntimePermission(@NonNull String packageName, @NonNull String permName, + @NonNull UserHandle user) { + + } + + @Override + public void revokeRuntimePermission(@NonNull String packageName, @NonNull String permName, + @NonNull UserHandle user) { + + } + + @Override + public int getPermissionFlags(@NonNull String permName, @NonNull String packageName, + @NonNull UserHandle user) { + return 0; + } + + @Override + public void updatePermissionFlags(@NonNull String permName, @NonNull String packageName, + int flagMask, int flagValues, @NonNull UserHandle user) { + + } + + @Override + public boolean shouldShowRequestPermissionRationale(@NonNull String permName) { + return false; + } + + @Override + public int checkSignatures(@NonNull String packageName1, @NonNull String packageName2) { + return 0; + } + + @Override + public int checkSignatures(int uid1, int uid2) { + return 0; + } + + @Nullable + @Override + public String[] getPackagesForUid(int uid) { + return new String[0]; + } + + @Nullable + @Override + public String getNameForUid(int uid) { + return null; + } + + @Nullable + @Override + public String[] getNamesForUids(int[] uids) { + return new String[0]; + } + + @Override + public int getUidForSharedUser(@NonNull String sharedUserName) + throws NameNotFoundException { + return 0; + } + + @NonNull + @Override + public List<ApplicationInfo> getInstalledApplications(int flags) { + return null; + } + + @NonNull + @Override + public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) { + return null; + } + + @NonNull + @Override + public List<InstantAppInfo> getInstantApps() { + return null; + } + + @Nullable + @Override + public Drawable getInstantAppIcon(String packageName) { + return null; + } + + @Override + public boolean isInstantApp() { + return false; + } + + @Override + public boolean isInstantApp(@NonNull String packageName) { + return false; + } + + @Override + public int getInstantAppCookieMaxBytes() { + return 0; + } + + @Override + public int getInstantAppCookieMaxSize() { + return 0; + } + + @NonNull + @Override + public byte[] getInstantAppCookie() { + return new byte[0]; + } + + @Override + public void clearInstantAppCookie() { + + } + + @Override + public void updateInstantAppCookie(@Nullable byte[] cookie) { + + } + + @Override + public boolean setInstantAppCookie(@Nullable byte[] cookie) { + return false; + } + + @Nullable + @Override + public String[] getSystemSharedLibraryNames() { + return new String[0]; + } + + @NonNull + @Override + public List<SharedLibraryInfo> getSharedLibraries(int flags) { + return null; + } + + @NonNull + @Override + public List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) { + return null; + } + + @NonNull + @Override + public String getServicesSystemSharedLibraryPackageName() { + return null; + } + + @NonNull + @Override + public String getSharedSystemSharedLibraryPackageName() { + return null; + } + + @Nullable + @Override + public ChangedPackages getChangedPackages(int sequenceNumber) { + return null; + } + + @NonNull + @Override + public FeatureInfo[] getSystemAvailableFeatures() { + return new FeatureInfo[0]; + } + + @Override + public boolean hasSystemFeature(@NonNull String featureName) { + return false; + } + + @Override + public boolean hasSystemFeature(@NonNull String featureName, int version) { + return false; + } + + @Nullable + @Override + public ResolveInfo resolveActivity(@NonNull Intent intent, int flags) { + return null; + } + + @Nullable + @Override + public ResolveInfo resolveActivityAsUser(@NonNull Intent intent, int flags, int userId) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, int flags) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent, int flags, + int userId) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller, + @Nullable Intent[] specifics, @NonNull Intent intent, int flags) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, int flags) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent, int flags, + int userId) { + return null; + } + + @Nullable + @Override + public ResolveInfo resolveService(@NonNull Intent intent, int flags) { + return null; + } + + @Nullable + @Override + public ResolveInfo resolveServiceAsUser(@NonNull Intent intent, int flags, int userId) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryIntentServices(@NonNull Intent intent, int flags) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, int flags, + int userId) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent, + int flags, int userId) { + return null; + } + + @NonNull + @Override + public List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent, int flags) { + return null; + } + + @Nullable + @Override + public ProviderInfo resolveContentProvider(@NonNull String authority, int flags) { + return null; + } + + @Nullable + @Override + public ProviderInfo resolveContentProviderAsUser(@NonNull String providerName, int flags, + int userId) { + return null; + } + + @NonNull + @Override + public List<ProviderInfo> queryContentProviders(@Nullable String processName, int uid, + int flags) { + return null; + } + + @NonNull + @Override + public InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName className, + int flags) throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public List<InstrumentationInfo> queryInstrumentation(@NonNull String targetPackage, + int flags) { + return null; + } + + @Nullable + @Override + public Drawable getDrawable(@NonNull String packageName, int resid, + @Nullable ApplicationInfo appInfo) { + return null; + } + + @NonNull + @Override + public Drawable getActivityIcon(@NonNull ComponentName activityName) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public Drawable getActivityIcon(@NonNull Intent intent) throws NameNotFoundException { + return null; + } + + @Nullable + @Override + public Drawable getActivityBanner(@NonNull ComponentName activityName) + throws NameNotFoundException { + return null; + } + + @Nullable + @Override + public Drawable getActivityBanner(@NonNull Intent intent) throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public Drawable getDefaultActivityIcon() { + return null; + } + + @NonNull + @Override + public Drawable getApplicationIcon(@NonNull ApplicationInfo info) { + return null; + } + + @NonNull + @Override + public Drawable getApplicationIcon(@NonNull String packageName) + throws NameNotFoundException { + return null; + } + + @Nullable + @Override + public Drawable getApplicationBanner(@NonNull ApplicationInfo info) { + return null; + } + + @Nullable + @Override + public Drawable getApplicationBanner(@NonNull String packageName) + throws NameNotFoundException { + return null; + } + + @Nullable + @Override + public Drawable getActivityLogo(@NonNull ComponentName activityName) + throws NameNotFoundException { + return null; + } + + @Nullable + @Override + public Drawable getActivityLogo(@NonNull Intent intent) throws NameNotFoundException { + return null; + } + + @Nullable + @Override + public Drawable getApplicationLogo(@NonNull ApplicationInfo info) { + return null; + } + + @Nullable + @Override + public Drawable getApplicationLogo(@NonNull String packageName) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public Drawable getUserBadgedIcon(@NonNull Drawable drawable, @NonNull UserHandle user) { + return null; + } + + @NonNull + @Override + public Drawable getUserBadgedDrawableForDensity(@NonNull Drawable drawable, + @NonNull UserHandle user, @Nullable Rect badgeLocation, int badgeDensity) { + return null; + } + + @Nullable + @Override + public Drawable getUserBadgeForDensity(@NonNull UserHandle user, int density) { + return null; + } + + @Nullable + @Override + public Drawable getUserBadgeForDensityNoBackground(@NonNull UserHandle user, int density) { + return null; + } + + @NonNull + @Override + public CharSequence getUserBadgedLabel(@NonNull CharSequence label, + @NonNull UserHandle user) { + return null; + } + + @Nullable + @Override + public CharSequence getText(@NonNull String packageName, int resid, + @Nullable ApplicationInfo appInfo) { + return null; + } + + @Nullable + @Override + public XmlResourceParser getXml(@NonNull String packageName, int resid, + @Nullable ApplicationInfo appInfo) { + return null; + } + + @NonNull + @Override + public CharSequence getApplicationLabel(@NonNull ApplicationInfo info) { + return null; + } + + @NonNull + @Override + public Resources getResourcesForActivity(@NonNull ComponentName activityName) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public Resources getResourcesForApplication(@NonNull ApplicationInfo app) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public Resources getResourcesForApplication(@NonNull String packageName) + throws NameNotFoundException { + return null; + } + + @NonNull + @Override + public Resources getResourcesForApplicationAsUser(@NonNull String packageName, int userId) + throws NameNotFoundException { + return null; + } + + @Override + public int installExistingPackage(@NonNull String packageName) + throws NameNotFoundException { + return 0; + } + + @Override + public int installExistingPackage(@NonNull String packageName, int installReason) + throws NameNotFoundException { + return 0; + } + + @Override + public int installExistingPackageAsUser(@NonNull String packageName, int userId) + throws NameNotFoundException { + return 0; + } + + @Override + public void verifyPendingInstall(int id, int verificationCode) { + + } + + @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, + long millisecondsToDelay) { + + } + + @Override + public void verifyIntentFilter(int verificationId, int verificationCode, + @NonNull List<String> failedDomains) { + + } + + @Override + public int getIntentVerificationStatusAsUser(@NonNull String packageName, int userId) { + return 0; + } + + @Override + public boolean updateIntentVerificationStatusAsUser(@NonNull String packageName, int status, + int userId) { + return false; + } + + @NonNull + @Override + public List<IntentFilterVerificationInfo> getIntentFilterVerifications( + @NonNull String packageName) { + return null; + } + + @NonNull + @Override + public List<IntentFilter> getAllIntentFilters(@NonNull String packageName) { + return null; + } + + @Nullable + @Override + public String getDefaultBrowserPackageNameAsUser(int userId) { + return null; + } + + @Override + public boolean setDefaultBrowserPackageNameAsUser(@Nullable String packageName, + int userId) { + return false; + } + + @Override + public void setInstallerPackageName(@NonNull String targetPackage, + @Nullable String installerPackageName) { + + } + + @Override + public void setUpdateAvailable(@NonNull String packageName, boolean updateAvaialble) { + + } + + @Override + public void deletePackage(@NonNull String packageName, + @Nullable IPackageDeleteObserver observer, int flags) { + + } + + @Override + public void deletePackageAsUser(@NonNull String packageName, + @Nullable IPackageDeleteObserver observer, int flags, int userId) { + + } + + @Nullable + @Override + public String getInstallerPackageName(@NonNull String packageName) { + return null; + } + + @Override + public void clearApplicationUserData(@NonNull String packageName, + @Nullable IPackageDataObserver observer) { + + } + + @Override + public void deleteApplicationCacheFiles(@NonNull String packageName, + @Nullable IPackageDataObserver observer) { + + } + + @Override + public void deleteApplicationCacheFilesAsUser(@NonNull String packageName, int userId, + @Nullable IPackageDataObserver observer) { + + } + + @Override + public void freeStorageAndNotify(@Nullable String volumeUuid, long freeStorageSize, + @Nullable IPackageDataObserver observer) { + + } + + @Override + public void freeStorage(@Nullable String volumeUuid, long freeStorageSize, + @Nullable IntentSender pi) { + + } + + @Override + public void getPackageSizeInfoAsUser(@NonNull String packageName, int userId, + @Nullable IPackageStatsObserver observer) { + + } + + @Override + public void addPackageToPreferred(@NonNull String packageName) { + + } + + @Override + public void removePackageFromPreferred(@NonNull String packageName) { + + } + + @NonNull + @Override + public List<PackageInfo> getPreferredPackages(int flags) { + return null; + } + + @Override + public void addPreferredActivity(@NonNull IntentFilter filter, int match, + @Nullable ComponentName[] set, @NonNull ComponentName activity) { + + } + + @Override + public void replacePreferredActivity(@NonNull IntentFilter filter, int match, + @Nullable ComponentName[] set, @NonNull ComponentName activity) { + + } + + @Override + public void clearPackagePreferredActivities(@NonNull String packageName) { + + } + + @Override + public int getPreferredActivities(@NonNull List<IntentFilter> outFilters, + @NonNull List<ComponentName> outActivities, @Nullable String packageName) { + return 0; + } + + @Nullable + @Override + public ComponentName getHomeActivities(@NonNull List<ResolveInfo> outActivities) { + return null; + } + + @Override + public void setComponentEnabledSetting(@NonNull ComponentName componentName, int newState, + int flags) { + + } + + @Override + public int getComponentEnabledSetting(@NonNull ComponentName componentName) { + return 0; + } + + @Override + public void setApplicationEnabledSetting(@NonNull String packageName, int newState, + int flags) { + + } + + @Override + public int getApplicationEnabledSetting(@NonNull String packageName) { + return 0; + } + + @Override + public void flushPackageRestrictionsAsUser(int userId) { + + } + + @Override + public boolean setApplicationHiddenSettingAsUser(@NonNull String packageName, + boolean hidden, @NonNull UserHandle userHandle) { + return false; + } + + @Override + public boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, + @NonNull UserHandle userHandle) { + return false; + } + + @Override + public boolean isSafeMode() { + return false; + } + + @Override + public void addOnPermissionsChangeListener(@NonNull OnPermissionsChangedListener listener) { + + } + + @Override + public void removeOnPermissionsChangeListener( + @NonNull OnPermissionsChangedListener listener) { + + } + + @NonNull + @Override + public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) { + return null; + } + + @NonNull + @Override + public KeySet getSigningKeySet(@NonNull String packageName) { + return null; + } + + @Override + public boolean isSignedBy(@NonNull String packageName, @NonNull KeySet ks) { + return false; + } + + @Override + public boolean isSignedByExactly(@NonNull String packageName, @NonNull KeySet ks) { + return false; + } + + @Override + public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) { + return false; + } + + @Override + public void setApplicationCategoryHint(@NonNull String packageName, int categoryHint) { + + } + + @Override + public int getMoveStatus(int moveId) { + return 0; + } + + @Override + public void registerMoveCallback(@NonNull MoveCallback callback, @NonNull Handler handler) { + + } + + @Override + public void unregisterMoveCallback(@NonNull MoveCallback callback) { + + } + + @Override + public int movePackage(@NonNull String packageName, @NonNull VolumeInfo vol) { + return 0; + } + + @Nullable + @Override + public VolumeInfo getPackageCurrentVolume(@NonNull ApplicationInfo app) { + return null; + } + + @NonNull + @Override + public List<VolumeInfo> getPackageCandidateVolumes(@NonNull ApplicationInfo app) { + return null; + } + + @Override + public int movePrimaryStorage(@NonNull VolumeInfo vol) { + return 0; + } + + @Nullable + @Override + public VolumeInfo getPrimaryStorageCurrentVolume() { + return null; + } + + @NonNull + @Override + public List<VolumeInfo> getPrimaryStorageCandidateVolumes() { + return null; + } + + @NonNull + @Override + public VerifierDeviceIdentity getVerifierDeviceIdentity() { + return null; + } + + @Override + public boolean isUpgrade() { + return false; + } + + @NonNull + @Override + public PackageInstaller getPackageInstaller() { + return null; + } + + @Override + public void addCrossProfileIntentFilter(@NonNull IntentFilter filter, int sourceUserId, + int targetUserId, int flags) { + + } + + @Override + public void clearCrossProfileIntentFilters(int sourceUserId) { + + } + + @NonNull + @Override + public Drawable loadItemIcon(@NonNull PackageItemInfo itemInfo, + @Nullable ApplicationInfo appInfo) { + return null; + } + + @NonNull + @Override + public Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo, + @Nullable ApplicationInfo appInfo) { + return null; + } + + @Override + public boolean isPackageAvailable(@NonNull String packageName) { + return false; + } + + @Override + public int getInstallReason(@NonNull String packageName, @NonNull UserHandle user) { + return 0; + } + + @Override + public boolean canRequestPackageInstalls() { + return false; + } + + @Nullable + @Override + public ComponentName getInstantAppResolverSettingsComponent() { + return null; + } + + @Nullable + @Override + public ComponentName getInstantAppInstallerComponent() { + return null; + } + + @Nullable + @Override + public String getInstantAppAndroidId(@NonNull String packageName, + @NonNull UserHandle user) { + return null; + } + + @Override + public void registerDexModule(@NonNull String dexModulePath, + @Nullable DexModuleRegisterCallback callback) { + + } +} diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 1b8ab2175458..0dbf3fe0cea3 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.intThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.app.IActivityManager; @@ -36,6 +37,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.net.Uri; @@ -53,11 +55,11 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; /** * Tests for {@link com.android.server.apphibernation.AppHibernationService} @@ -81,6 +83,8 @@ public final class AppHibernationServiceTest { @Mock private IPackageManager mIPackageManager; @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock private IActivityManager mIActivityManager; @Mock private UserManager mUserManager; @@ -116,8 +120,8 @@ public final class AppHibernationServiceTest { mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); UserInfo userInfo = addUser(USER_ID_1); - mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); mAppHibernationService.mIsServiceEnabled = true; } @@ -150,8 +154,8 @@ public final class AppHibernationServiceTest { throws RemoteException { // WHEN a new user is added and a package from the user is hibernated UserInfo user2 = addUser(USER_ID_2); - mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); // THEN the new user's package is hibernated @@ -188,8 +192,8 @@ public final class AppHibernationServiceTest { // GIVEN an unlocked user with all packages installed UserInfo userInfo = addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3}); - mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); // WHEN packages are hibernated for the user mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); @@ -254,18 +258,29 @@ public final class AppHibernationServiceTest { } @Override + public PackageManagerInternal getPackageManagerInternal() { + return mPackageManagerInternal; + } + + @Override public UserManager getUserManager() { return mUserManager; } @Override + public Executor getBackgroundExecutor() { + // Just execute immediately in tests. + return r -> r.run(); + } + + @Override public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { - return Mockito.mock(HibernationStateDiskStore.class); + return mock(HibernationStateDiskStore.class); } @Override public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { - return Mockito.mock(HibernationStateDiskStore.class); + return mock(HibernationStateDiskStore.class); } } } 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..4240581bc504 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,16 +99,17 @@ public class AppSearchImplPlatformTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package1 document GenericDocument document1 = - new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build(); + new GenericDocument.Builder<>("namespace", "uri", "schema1").build(); mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null); // Insert package2 document GenericDocument document2 = - new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build(); + new GenericDocument.Builder<>("namespace", "uri", "schema2").build(); mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null); // No query filters specified, global query can retrieve all documents. @@ -120,8 +122,8 @@ public class AppSearchImplPlatformTest { // Document2 will be first since it got indexed later and has a "better", aka more recent // score. - assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2); - assertThat(searchResultPage.getResults().get(1).getDocument()).isEqualTo(document1); + assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); + assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(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,16 +153,17 @@ public class AppSearchImplPlatformTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package1 document GenericDocument document1 = - new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build(); + new GenericDocument.Builder<>("namespace", "uri", "schema1").build(); mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null); // Insert package2 document GenericDocument document2 = - new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build(); + new GenericDocument.Builder<>("namespace", "uri", "schema2").build(); mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null); // "package1" filter specified @@ -172,7 +176,7 @@ public class AppSearchImplPlatformTest { mAppSearchImpl.globalQuery( "", searchSpec, mContext.getPackageName(), mGlobalQuerierUid); assertThat(searchResultPage.getResults()).hasSize(1); - assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document1); + assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); // "package2" filter specified searchSpec = @@ -184,7 +188,7 @@ public class AppSearchImplPlatformTest { mAppSearchImpl.globalQuery( "", searchSpec, mContext.getPackageName(), mGlobalQuerierUid); assertThat(searchResultPage.getResults()).hasSize(1); - assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document2); + assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); } @Test @@ -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/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java index 6cdac1af87eb..557c14a0dfc1 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java @@ -81,7 +81,7 @@ public class UserAwareBiometricSchedulerTest { @NonNull @Override - public StartUserClient<?> getStartUserClient(int newUserId) { + public StartUserClient<?, ?> getStartUserClient(int newUserId) { return new TestStartUserClient(mContext, Object::new, mToken, newUserId, TEST_SENSOR_ID, mUserStartedCallback); } @@ -157,12 +157,12 @@ public class UserAwareBiometricSchedulerTest { } } - private class TestUserStartedCallback implements StartUserClient.UserStartedCallback { + private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> { int numInvocations; @Override - public void onUserStarted(int newUserId) { + public void onUserStarted(int newUserId, Object newObject) { numInvocations++; mCurrentUserId = newUserId; } @@ -183,8 +183,7 @@ public class UserAwareBiometricSchedulerTest { @Override public void start(@NonNull Callback callback) { super.start(callback); - mUserStoppedCallback.onUserStopped(); - callback.onClientFinished(this, true /* success */); + onUserStopped(); } @Override @@ -193,10 +192,10 @@ public class UserAwareBiometricSchedulerTest { } } - private static class TestStartUserClient extends StartUserClient<Object> { + private static class TestStartUserClient extends StartUserClient<Object, Object> { public TestStartUserClient(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId, - int sensorId, @NonNull UserStartedCallback callback) { + int sensorId, @NonNull UserStartedCallback<Object> callback) { super(context, lazyDaemon, token, userId, sensorId, callback); } @@ -208,7 +207,7 @@ public class UserAwareBiometricSchedulerTest { @Override public void start(@NonNull Callback callback) { super.start(callback); - mUserStartedCallback.onUserStarted(getTargetUserId()); + mUserStartedCallback.onUserStarted(getTargetUserId(), new Object()); callback.onClientFinished(this, true /* success */); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java index 04a7122ba426..0cd6d86a3ec9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java @@ -24,10 +24,12 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.common.CommonProps; +import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.SensorProps; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -56,7 +58,7 @@ public class FaceProviderTest { private SensorProps[] mSensorProps; private LockoutResetDispatcher mLockoutResetDispatcher; - private FaceProvider mFaceProvider; + private TestableFaceProvider mFaceProvider; private static void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -80,7 +82,7 @@ public class FaceProviderTest { mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); - mFaceProvider = new FaceProvider(mContext, mSensorProps, TAG, + mFaceProvider = new TestableFaceProvider(mContext, mSensorProps, TAG, mLockoutResetDispatcher); } @@ -123,4 +125,19 @@ public class FaceProviderTest { assertEquals(0, scheduler.getCurrentPendingCount()); } } + + private static class TestableFaceProvider extends FaceProvider { + public TestableFaceProvider(@NonNull Context context, + @NonNull SensorProps[] props, + @NonNull String halInstanceName, + @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + super(context, props, halInstanceName, lockoutResetDispatcher); + } + + @Override + synchronized IFace getHalInstance() { + return mock(IFace.class); + } + } + } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index d149880e5505..94cc666d740c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -24,10 +24,12 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.common.CommonProps; +import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorProps; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -59,7 +61,7 @@ public class FingerprintProviderTest { private SensorProps[] mSensorProps; private LockoutResetDispatcher mLockoutResetDispatcher; - private FingerprintProvider mFingerprintProvider; + private TestableFingerprintProvider mFingerprintProvider; private static void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -83,7 +85,7 @@ public class FingerprintProviderTest { mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); - mFingerprintProvider = new FingerprintProvider(mContext, mSensorProps, TAG, + mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } @@ -126,4 +128,20 @@ public class FingerprintProviderTest { assertEquals(0, scheduler.getCurrentPendingCount()); } } + + private static class TestableFingerprintProvider extends FingerprintProvider { + public TestableFingerprintProvider(@NonNull Context context, + @NonNull SensorProps[] props, + @NonNull String halInstanceName, + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { + super(context, props, halInstanceName, lockoutResetDispatcher, + gestureAvailabilityDispatcher); + } + + @Override + synchronized IFingerprint getHalInstance() { + return mock(IFingerprint.class); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/content/OWNERS b/services/tests/servicestests/src/com/android/server/content/OWNERS new file mode 100644 index 000000000000..6264a1427c7f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/content/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/content/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java index d093e7961b22..c2a81d9453e4 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java @@ -16,6 +16,7 @@ package com.android.server.content; +import android.content.ContentResolver; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; @@ -57,6 +58,23 @@ public class SyncManagerTest extends TestCase { SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */)); } + public void testSyncExtrasEqualsFails_WithNull() throws Exception { + Bundle b1 = new Bundle(); + b1.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + b1.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + + Bundle b2 = new Bundle(); + b2.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + b2.putString(null, "Hello NPE!"); + b2.putString("a", "b"); + b2.putString("c", "d"); + b2.putString("e", "f"); + + assertFalse("Extras not properly compared between bundles.", + SyncManager.syncExtrasEquals(b1, b2, false /* don't care about system extras */)); + } + public void testSyncExtrasEqualsFails_differentValues() throws Exception { Bundle b1 = new Bundle(); Bundle b2 = new Bundle(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 576f9c23e350..cc206a1353b8 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -92,6 +92,7 @@ import android.content.pm.StringParceledListSlice; import android.content.pm.UserInfo; import android.graphics.Color; import android.hardware.usb.UsbManager; +import android.net.ConnectivityManager; import android.net.Uri; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -124,9 +125,7 @@ import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; import org.mockito.internal.util.collections.Sets; @@ -221,16 +220,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { private static final String PROFILE_OFF_SUSPENSION_TEXT = "suspension_text"; private static final String PROFILE_OFF_SUSPENSION_SOON_TEXT = "suspension_tomorrow_text"; - @BeforeClass - public static void setUpClass() { - Notification.DevFlags.sForceDefaults = true; - } - - @AfterClass - public static void tearDownClass() { - Notification.DevFlags.sForceDefaults = false; - } - @Before public void setUp() throws Exception { @@ -4017,53 +4006,59 @@ public class DevicePolicyManagerTest extends DpmTestBase { @Test public void testUpdateNetworkPreferenceOnStartOnStopUser() throws Exception { - dpms.handleStartUser(CALLER_USER_HANDLE); - // TODO(b/178655595) - // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser( - // any(UserHandle.class), - // anyInt(), - // any(Executor.class), - // any(Runnable.class) - //); - - dpms.handleStopUser(CALLER_USER_HANDLE); - // TODO(b/178655595) - // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser( - // any(UserHandle.class), - // eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT), - // any(Executor.class), - // any(Runnable.class) - //); - } - - @Test - public void testGetSetNetworkSlicing() throws Exception { + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + mContext.binder.callingUid = managedProfileAdminUid; + mServiceContext.permissions.add(permission.INTERACT_ACROSS_USERS_FULL); + + dpms.handleStartUser(managedProfileUserId); + verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( + eq(UserHandle.of(managedProfileUserId)), + anyInt(), + any(), + any() + ); + + dpms.handleStopUser(managedProfileUserId); + verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( + eq(UserHandle.of(managedProfileUserId)), + eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT), + any(), + any() + ); + } + + @Test + public void testGetSetEnterpriseNetworkPreference() throws Exception { assertExpectException(SecurityException.class, null, - () -> dpm.setNetworkSlicingEnabled(false)); + () -> dpm.setEnterpriseNetworkPreferenceEnabled(false)); assertExpectException(SecurityException.class, null, - () -> dpm.isNetworkSlicingEnabled()); + () -> dpm.isEnterpriseNetworkPreferenceEnabled()); - setupProfileOwner(); - dpm.setNetworkSlicingEnabled(false); - assertThat(dpm.isNetworkSlicingEnabled()).isFalse(); - // TODO(b/178655595) - // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser( - // any(UserHandle.class), - // eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT), - // any(Executor.class), - // any(Runnable.class) - //); - - dpm.setNetworkSlicingEnabled(true); - assertThat(dpm.isNetworkSlicingEnabled()).isTrue(); - // TODO(b/178655595) - // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser( - // any(UserHandle.class), - // eq(ConnectivityManager.USER_PREFERENCE_ENTERPRISE), - // any(Executor.class), - // any(Runnable.class) - //); + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + mContext.binder.callingUid = managedProfileAdminUid; + + dpm.setEnterpriseNetworkPreferenceEnabled(false); + assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isFalse(); + verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( + eq(UserHandle.of(managedProfileUserId)), + eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT), + any(), + any() + ); + + dpm.setEnterpriseNetworkPreferenceEnabled(true); + assertThat(dpm.isEnterpriseNetworkPreferenceEnabled()).isTrue(); + verify(getServices().connectivityManager, times(1)).setProfileNetworkPreference( + eq(UserHandle.of(managedProfileUserId)), + eq(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE), + any(), + any() + ); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 15ada896512b..c8099e2467a1 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -464,6 +464,40 @@ public class DisplayModeDirectorTest { } @Test + public void testStaleAppRequestSize() { + DisplayModeDirector director = + new DisplayModeDirector(mContext, mHandler, mInjector); + Display.Mode[] modes = new Display.Mode[] { + new Display.Mode(1, 1280, 720, 60), + }; + Display.Mode defaultMode = modes[0]; + + // Inject supported modes + SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>(); + supportedModesByDisplay.put(DISPLAY_ID, modes); + director.injectSupportedModesByDisplay(supportedModesByDisplay); + + // Inject default mode + SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>(); + defaultModesByDisplay.put(DISPLAY_ID, defaultMode); + director.injectDefaultModeByDisplay(defaultModesByDisplay); + + // Inject votes + SparseArray<Vote> votes = new SparseArray<>(); + votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(1920, 1080)); + votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(50, 50)); + SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + director.injectVotesByDisplay(votesByDisplay); + + director.setShouldAlwaysRespectAppRequestedMode(true); + + // We should return the only available mode + DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(specs.baseModeId).isEqualTo(defaultMode.getModeId()); + } + + @Test public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0); diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index dbb415c88a5f..e7ffea0a650d 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -54,6 +54,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; @Presubmit @@ -117,10 +118,13 @@ public final class UpdatableFontDirTest { } } + private static final long CURRENT_TIME = 1234567890L; + private File mCacheDir; private File mUpdatableFontFilesDir; private File mConfigFile; private List<File> mPreinstalledFontDirs; + private Supplier<Long> mCurrentTimeSupplier = () -> CURRENT_TIME; @SuppressWarnings("ResultOfMethodCallIgnored") @Before @@ -147,7 +151,7 @@ public final class UpdatableFontDirTest { @Test public void construct() throws Exception { - long expectedModifiedDate = 1234567890; + long expectedModifiedDate = CURRENT_TIME / 2; FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); @@ -155,7 +159,7 @@ public final class UpdatableFontDirTest { writeConfig(config, mConfigFile); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) .isEqualTo(expectedModifiedDate); @@ -168,6 +172,9 @@ public final class UpdatableFontDirTest { + " <font>foo.ttf</font>" + " <font>bar.ttf</font>" + "</family>"))); + // Verifies that getLastModifiedTimeMillis() returns the value of currentTimeMillis. + assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) + .isEqualTo(CURRENT_TIME); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) @@ -175,7 +182,7 @@ public final class UpdatableFontDirTest { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3); @@ -199,7 +206,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); assertThat(dir.getFontFamilyMap()).isEmpty(); @@ -211,7 +218,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -229,7 +236,7 @@ public final class UpdatableFontDirTest { dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath()); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); // All font dirs (including dir for "bar.ttf") should be deleted. @@ -243,7 +250,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -262,7 +269,7 @@ public final class UpdatableFontDirTest { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); // All font dirs (including dir for "bar.ttf") should be deleted. @@ -276,7 +283,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -296,7 +303,7 @@ public final class UpdatableFontDirTest { FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2"); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); // For foo.ttf, preinstalled font (revision 5) should be used. assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf"); @@ -317,7 +324,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - new File("/dev/null")); + new File("/dev/null"), mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getFontFileMap()).isEmpty(); assertThat(dir.getFontFamilyMap()).isEmpty(); @@ -329,7 +336,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dirForPreparation.loadFontFileMap(); dirForPreparation.update(Arrays.asList( newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE), @@ -351,7 +358,7 @@ public final class UpdatableFontDirTest { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); // The state should be rolled back as a whole if one of the update requests fail. assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); @@ -369,7 +376,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE))); @@ -387,7 +394,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE))); @@ -406,7 +413,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2", GOOD_SIGNATURE))); @@ -428,7 +435,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE))); @@ -445,7 +452,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Arrays.asList( @@ -463,7 +470,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -485,7 +492,7 @@ public final class UpdatableFontDirTest { FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test.ttf,1"); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -517,7 +524,7 @@ public final class UpdatableFontDirTest { try { UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - readonlyFile); + readonlyFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -551,7 +558,7 @@ public final class UpdatableFontDirTest { public long getRevision(File file) throws IOException { return 0; } - }, fakeFsverityUtil, mConfigFile); + }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -580,7 +587,7 @@ public final class UpdatableFontDirTest { public long getRevision(File file) throws IOException { return 0; } - }, fakeFsverityUtil, mConfigFile); + }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -617,7 +624,7 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -637,7 +644,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE))); @@ -660,7 +667,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); dir.update(Arrays.asList( @@ -682,7 +689,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -703,7 +710,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); try { @@ -723,7 +730,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); // We assume we have monospace. assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace"); @@ -755,7 +762,7 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, - mConfigFile); + mConfigFile, mCurrentTimeSupplier); dir.loadFontFileMap(); assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 50ba761cef10..ee9de07a15d2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -107,7 +107,7 @@ public class ArcInitiationActionFromAvrTest { } @Override - Looper getServiceLooper() { + protected Looper getServiceLooper() { return mTestLooper.getLooper(); } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index aa5bc933002d..d5df07102d73 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -108,7 +108,7 @@ public class ArcTerminationActionFromAvrTest { } @Override - Looper getServiceLooper() { + protected Looper getServiceLooper() { return mTestLooper.getLooper(); } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 41f4a1e2d94c..8b23be511ac0 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -91,7 +91,7 @@ public class DetectTvSystemAudioModeSupportActionTest { } @Override - Looper getServiceLooper() { + protected Looper getServiceLooper() { return mTestLooper.getLooper(); } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index b3ee18ddff55..ee1a85745701 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -19,21 +19,35 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; +import static com.android.server.hdmi.Constants.ABORT_REFUSED; +import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BACKUP_1; import static com.android.server.hdmi.Constants.ADDR_BACKUP_2; +import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3; import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; +import static com.android.server.hdmi.Constants.HANDLED; +import static com.android.server.hdmi.Constants.MESSAGE_STANDBY; +import static com.android.server.hdmi.Constants.NOT_HANDLED; + +import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import android.content.Context; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + import android.hardware.hdmi.HdmiControlManager; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Binder; @@ -44,6 +58,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.SystemService; import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; import junit.framework.TestCase; @@ -53,6 +68,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ArrayList; import java.util.Optional; /** Tests for {@link com.android.server.hdmi.HdmiCecController} class. */ @@ -63,27 +79,7 @@ public class HdmiCecControllerTest { private FakeNativeWrapper mNativeWrapper; - private class MyHdmiControlService extends HdmiControlService { - - MyHdmiControlService(Context context) { - super(context); - } - - @Override - Looper getIoLooper() { - return mMyLooper; - } - - @Override - Looper getServiceLooper() { - return mMyLooper; - } - - @Override - int getCecVersion() { - return mCecVersion; - } - } + private HdmiControlService mHdmiControlServiceSpy; private HdmiCecController mHdmiCecController; private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; @@ -101,12 +97,39 @@ public class HdmiCecControllerTest { @Before public void SetUp() { mMyLooper = mTestLooper.getLooper(); - mMyLooper = mTestLooper.getLooper(); - HdmiControlService hdmiControlService = new MyHdmiControlService( - InstrumentationRegistry.getTargetContext()); + + mHdmiControlServiceSpy = spy(new HdmiControlService( + InstrumentationRegistry.getTargetContext())); + doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper(); + doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper(); + doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion(); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); + mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper( - hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + } + + /** Additional setup for tests for onMessage + * Adds a local playback device and allocates addresses + */ + public void setUpForOnMessageTest() { + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + + HdmiCecLocalDevicePlayback playbackDevice = + new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy); + playbackDevice.init(); + + ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); + localDevices.add(playbackDevice); + + mHdmiControlServiceSpy.initService(); + mHdmiControlServiceSpy.allocateLogicalAddress(localDevices, + HdmiControlService.INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + + mTestLooper.dispatchAll(); } /** Tests for {@link HdmiCecController#allocateLogicalAddress} */ @@ -119,7 +142,6 @@ public class HdmiCecControllerTest { @Test public void testAllocateLogicalAddress_TvDeviceNonPreferredNotOccupied() { - mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_TV, mLogicalAddress); @@ -308,4 +330,90 @@ public class HdmiCecControllerTest { TestCase.assertEquals(Optional.of(callerUid), uidReadingRunnable.getWorkSourceUid()); TestCase.assertEquals(runnerUid, Binder.getCallingWorkSourceUid()); } + + @Test + public void onMessage_broadcastMessage_doesNotSendFeatureAbort() { + setUpForOnMessageTest(); + + doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_BROADCAST); + + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + assertFalse("No <Feature Abort> messages should be sent", + mNativeWrapper.getResultMessages().stream().anyMatch( + message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)); + } + + @Test + public void onMessage_notTheDestination_doesNotSendFeatureAbort() { + setUpForOnMessageTest(); + + doReturn(ABORT_UNRECOGNIZED_OPCODE).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + assertFalse("No <Feature Abort> messages should be sent", + mNativeWrapper.getResultMessages().stream().anyMatch( + message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)); + } + + @Test + public void onMessage_handledMessage_doesNotSendFeatureAbort() { + setUpForOnMessageTest(); + + doReturn(HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_PLAYBACK_1); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + assertFalse("No <Feature Abort> messages should be sent", + mNativeWrapper.getResultMessages().stream().anyMatch( + message -> message.getOpcode() == Constants.MESSAGE_FEATURE_ABORT)); + } + + @Test + public void onMessage_unhandledMessage_sendsFeatureAbortUnrecognizedOpcode() { + setUpForOnMessageTest(); + + doReturn(NOT_HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_PLAYBACK_1); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_UNRECOGNIZED_OPCODE); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); + } + + @Test + public void onMessage_sendsFeatureAbortWithRequestedOperand() { + setUpForOnMessageTest(); + + doReturn(ABORT_REFUSED).when(mHdmiControlServiceSpy).handleCecCommand(any()); + + HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby( + ADDR_TV, ADDR_PLAYBACK_1); + mNativeWrapper.onCecMessage(receivedMessage); + + mTestLooper.dispatchAll(); + + HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( + DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_REFUSED); + assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 6bb148d43a57..38a44c6c0d55 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -20,7 +20,6 @@ import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; -import static com.android.server.hdmi.Constants.MESSAGE_GIVE_AUDIO_STATUS; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; @@ -248,7 +247,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true); HdmiCecMessage messageGive = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -262,45 +262,25 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage messageGive = HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @Test public void handleRequestShortAudioDescriptor_featureDisabled() throws Exception { - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, - Constants.ABORT_REFUSED); - mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false); - assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( MESSAGE_REQUEST_SAD_LCPM)) - .isTrue(); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + .isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleRequestShortAudioDescriptor_samOff() throws Exception { - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, - Constants.ABORT_NOT_IN_CORRECT_MODE); - mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false); - assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE); } // Testing device has sadConfig.xml @@ -315,10 +295,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.ABORT_UNABLE_TO_DETERMINE); mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); - assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -335,17 +314,18 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); // Check if correctly turned on mNativeWrapper.clearResultMessages(); expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isFalse(); @@ -365,7 +345,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildSetSystemAudioMode( ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); @@ -373,7 +353,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isTrue(); @@ -441,7 +421,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { public void handleActiveSource_updateActiveSource() throws Exception { HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource)) .isTrue(); @@ -513,17 +494,10 @@ public class HdmiCecLocalDeviceAudioSystemTest { public void handleRequestArcInitiate_isNotDirectConnectedToTv() throws Exception { HdmiCecMessage message = HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_ARC_INITIATION, - Constants.ABORT_NOT_IN_CORRECT_MODE); mNativeWrapper.setPhysicalAddress(0x1100); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isEqualTo(Constants.ABORT_NOT_IN_CORRECT_MODE); } @Test @@ -533,7 +507,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { mNativeWrapper.setPhysicalAddress(0x1000); mHdmiCecLocalDeviceAudioSystem.removeAction(ArcInitiationActionFromAvr.class); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class)) .isNotEmpty(); @@ -548,7 +523,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM); mHdmiCecLocalDeviceAudioSystem.removeAction(ArcTerminationActionFromAvr.class); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class)) .isNotEmpty(); @@ -567,7 +543,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_REQUEST_ARC_TERMINATION, Constants.ABORT_NOT_IN_CORRECT_MODE); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } @@ -576,17 +553,10 @@ public class HdmiCecLocalDeviceAudioSystemTest { public void handleRequestArcInit_arcIsNotSupported() throws Exception { HdmiCecMessage message = HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); - HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, - ADDR_TV, - Constants.MESSAGE_REQUEST_ARC_INITIATION, - Constants.ABORT_UNRECOGNIZED_OPCODE); mArcSupport = false; - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE); } @Test @@ -612,7 +582,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, Constants.ABORT_REFUSED); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)) + .isEqualTo(Constants.ABORT_UNRECOGNIZED_OPCODE); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -629,7 +600,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -642,7 +614,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource()) .isEqualTo(expectedActiveSource); @@ -659,7 +632,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { ActiveSource expectedActiveSource = ActiveSource.of(ADDR_PLAYBACK_1, SELF_PHYSICAL_ADDRESS); int expectedLocalActivePort = Constants.CEC_SWITCH_HOME; - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource()) .isEqualTo(expectedActiveSource); @@ -677,7 +651,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildRoutingInformation( ADDR_AUDIO_SYSTEM, HDMI_1_PHYSICAL_ADDRESS); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -691,7 +666,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } @@ -727,18 +703,15 @@ public class HdmiCecLocalDeviceAudioSystemTest { int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); HdmiCecMessage expected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, mute); - HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED); HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); mNativeWrapper.clearResultMessages(); - boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expected); - assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort); - assertThat(handled).isTrue(); } @Test @@ -758,18 +731,15 @@ public class HdmiCecLocalDeviceAudioSystemTest { int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); HdmiCecMessage unexpected = HdmiCecMessageBuilder.buildReportAudioStatus(ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, mute); - HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand( - ADDR_AUDIO_SYSTEM, ADDR_TV, MESSAGE_GIVE_AUDIO_STATUS, Constants.ABORT_REFUSED); HdmiCecMessage giveAudioStatus = HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); mNativeWrapper.clearResultMessages(); - boolean handled = mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus); + assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(giveAudioStatus)) + .isEqualTo(Constants.ABORT_REFUSED); mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected); - assertThat(handled).isTrue(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 1a6bad8b29cf..80da6961cf16 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -113,7 +113,7 @@ public class HdmiCecLocalDevicePlaybackTest { } @Override - boolean isStandbyMessageReceived() { + protected boolean isStandbyMessageReceived() { return mStandby; } @@ -184,7 +184,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -205,7 +206,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -226,7 +228,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -247,7 +250,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages().contains(expectedMessage)).isFalse(); @@ -270,7 +274,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); @@ -293,7 +298,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); @@ -309,7 +315,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( 0x5000); @@ -329,7 +336,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -349,7 +357,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -368,7 +377,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); } @@ -383,7 +393,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isFalse(); } @@ -399,7 +410,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mStandby).isFalse(); } @@ -461,7 +473,8 @@ public class HdmiCecLocalDevicePlaybackTest { mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest"); mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( 0x5000); @@ -481,7 +494,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -501,7 +515,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( mPlaybackPhysicalAddress); @@ -520,7 +535,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); } @@ -535,7 +551,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isFalse(); } @@ -551,7 +568,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mStandby).isFalse(); } @@ -606,7 +624,8 @@ public class HdmiCecLocalDevicePlaybackTest { public void handleSetStreamPath() { HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); } @Test @@ -616,7 +635,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildSetSystemAudioMode( Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true); - assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue(); } @@ -629,7 +649,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildSetSystemAudioMode( Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, false); - assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetSystemAudioMode(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue(); } @@ -640,7 +661,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildReportSystemAudioMode( Constants.ADDR_AUDIO_SYSTEM, mHdmiCecLocalDevicePlayback.mAddress, true); - assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSystemAudioModeStatus(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.mService.isSystemAudioActivated()).isTrue(); } @@ -883,7 +905,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); @@ -900,7 +923,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE); mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); @@ -918,7 +942,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); @@ -931,7 +956,8 @@ public class HdmiCecLocalDevicePlaybackTest { HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW); mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mStandby).isTrue(); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); @@ -996,12 +1022,14 @@ public class HdmiCecLocalDevicePlaybackTest { // 1. DUT is <AS>. HdmiCecMessage message1 = HdmiCecMessageBuilder.buildActiveSource( mHdmiCecLocalDevicePlayback.mAddress, mPlaybackPhysicalAddress); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message1)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue(); assertThat(mStandby).isFalse(); // 2. DUT loses <AS> and goes to sleep. HdmiCecMessage message2 = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message2)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); // 3. DUT becomes <AS> again. @@ -1271,7 +1299,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo( 0x5000); @@ -1290,7 +1319,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isTrue(); } @@ -1305,7 +1335,8 @@ public class HdmiCecLocalDevicePlaybackTest { mStandby = false; HdmiCecMessage message = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000); - assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)) + .isEqualTo(Constants.HANDLED); assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse(); assertThat(mStandby).isFalse(); } @@ -1492,7 +1523,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby( @@ -1510,7 +1542,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_ON); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildStandby( @@ -1525,7 +1558,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_STANDBY); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(mPlaybackLogicalAddress, @@ -1543,7 +1577,8 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.toggleAndFollowTvPower(); HdmiCecMessage tvPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(ADDR_TV, mPlaybackLogicalAddress, HdmiControlManager.POWER_STATUS_UNKNOWN); - assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.dispatchMessage(tvPowerStatus)) + .isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index b3f008598dc8..68803023c451 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -198,8 +198,8 @@ public class HdmiCecLocalDeviceTest { ADDR_PLAYBACK_1, Constants.MESSAGE_CEC_VERSION, HdmiCecMessage.EMPTY_PARAM); - boolean handleResult = mHdmiLocalDevice.dispatchMessage(msg); - assertFalse(handleResult); + @Constants.HandleMessageResult int handleResult = mHdmiLocalDevice.dispatchMessage(msg); + assertEquals(Constants.NOT_HANDLED, handleResult); } @Test @@ -213,7 +213,7 @@ public class HdmiCecLocalDeviceTest { (byte) (DEVICE_TV & 0xFF) }; callbackResult = -1; - boolean handleResult = + @Constants.HandleMessageResult int handleResult = mHdmiLocalDevice.handleGivePhysicalAddress( (int finalResult) -> callbackResult = finalResult); mTestLooper.dispatchAll(); @@ -221,7 +221,7 @@ public class HdmiCecLocalDeviceTest { * Test if CecMessage is sent successfully SendMessageResult#SUCCESS is defined in HAL as 0 */ assertEquals(0, callbackResult); - assertTrue(handleResult); + assertEquals(Constants.HANDLED, handleResult); } @Test @@ -251,85 +251,85 @@ public class HdmiCecLocalDeviceTest { public void handleUserControlPressed_volumeUp() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP)); - assertTrue(result); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_volumeDown() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); - assertTrue(result); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_volumeMute() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_MUTE)); - assertTrue(result); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_volumeUp_disabled() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP)); - assertFalse(result); + assertThat(result).isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleUserControlPressed_volumeDown_disabled() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN)); - assertFalse(result); + assertThat(result).isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleUserControlPressed_volumeMute_disabled() { mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_PLAYBACK_1, ADDR_TV, HdmiCecKeycode.CEC_KEYCODE_MUTE)); - assertFalse(result); + assertThat(result).isEqualTo(Constants.ABORT_REFUSED); } @Test public void handleCecVersion_isHandled() { - boolean result = mHdmiLocalDevice.onMessage( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.onMessage( HdmiCecMessageBuilder.buildCecVersion(ADDR_PLAYBACK_1, mHdmiLocalDevice.mAddress, HdmiControlManager.HDMI_CEC_VERSION_1_4_B)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); } @Test public void handleUserControlPressed_power_localDeviceInStandby_shouldTurnOn() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isTrue(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -337,11 +337,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_power_localDeviceOn_shouldNotChangePowerStatus() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -349,11 +349,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerToggleFunction_localDeviceInStandby_shouldTurnOn() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isTrue(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -361,11 +361,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerToggleFunction_localDeviceOn_shouldTurnOff() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isTrue(); } @@ -373,11 +373,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOnFunction_localDeviceInStandby_shouldTurnOn() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isTrue(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -385,11 +385,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOnFunction_localDeviceOn_noPowerStatusChange() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -397,11 +397,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOffFunction_localDeviceStandby_noPowerStatusChange() { mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isFalse(); } @@ -409,11 +409,11 @@ public class HdmiCecLocalDeviceTest { @Test public void handleUserControlPressed_powerOffFunction_localDeviceOn_shouldTurnOff() { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; - boolean result = mHdmiLocalDevice.handleUserControlPressed( + @Constants.HandleMessageResult int result = mHdmiLocalDevice.handleUserControlPressed( HdmiCecMessageBuilder.buildUserControlPressed(ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION)); - assertThat(result).isTrue(); + assertEquals(Constants.HANDLED, result); assertThat(mWakeupMessageReceived).isFalse(); assertThat(mStandbyMessageReceived).isTrue(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 4b3ef2f2cfd1..39e06a3a362d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -233,7 +233,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1, mTvLogicalAddress); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); } @@ -247,7 +247,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isTrue(); } @@ -261,7 +261,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1, mTvLogicalAddress); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); } @@ -275,7 +275,7 @@ public class HdmiCecLocalDeviceTvTest { mWokenUp = false; HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); - assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isTrue(); + assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); assertThat(mWokenUp).isFalse(); } 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/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index b5336e3fd807..68aa96a1be7f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -28,6 +28,9 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -68,7 +71,7 @@ import java.util.Optional; @RunWith(JUnit4.class) public class HdmiControlServiceTest { - private class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { + protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback { private boolean mCanGoToStandby; private boolean mIsStandby; @@ -118,7 +121,7 @@ public class HdmiControlServiceTest { mCanGoToStandby = canGoToStandby; } } - private class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem { + protected static class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem { private boolean mCanGoToStandby; private boolean mIsStandby; @@ -171,15 +174,14 @@ public class HdmiControlServiceTest { private static final String TAG = "HdmiControlServiceTest"; private Context mContextSpy; - private HdmiControlService mHdmiControlService; + private HdmiControlService mHdmiControlServiceSpy; private HdmiCecController mHdmiCecController; - private MockAudioSystemDevice mAudioSystemDevice; - private MockPlaybackDevice mPlaybackDevice; + private MockAudioSystemDevice mAudioSystemDeviceSpy; + private MockPlaybackDevice mPlaybackDeviceSpy; private FakeNativeWrapper mNativeWrapper; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); - private boolean mStandbyMessageReceived; private HdmiPortInfo[] mHdmiPortInfo; @Mock private IPowerManager mIPowerManagerMock; @@ -199,36 +201,32 @@ public class HdmiControlServiceTest { HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); - mHdmiControlService = new HdmiControlService(mContextSpy) { - @Override - boolean isStandbyMessageReceived() { - return mStandbyMessageReceived; - } + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy)); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); - @Override - protected void writeStringSystemProperty(String key, String value) { - } - }; mMyLooper = mTestLooper.getLooper(); - mAudioSystemDevice = new MockAudioSystemDevice(mHdmiControlService); - mPlaybackDevice = new MockPlaybackDevice(mHdmiControlService); - mAudioSystemDevice.init(); - mPlaybackDevice.init(); + mAudioSystemDeviceSpy = spy(new MockAudioSystemDevice(mHdmiControlServiceSpy)); + mPlaybackDeviceSpy = spy(new MockPlaybackDevice(mHdmiControlServiceSpy)); + mAudioSystemDeviceSpy.init(); + mPlaybackDeviceSpy.init(); - mHdmiControlService.setIoLooper(mMyLooper); - mHdmiControlService.setHdmiCecConfig(hdmiCecConfig); - mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); + mHdmiControlServiceSpy.setIoLooper(mMyLooper); + mHdmiControlServiceSpy.setHdmiCecConfig(hdmiCecConfig); + mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY); 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(mAudioSystemDevice); - mLocalDevices.add(mPlaybackDevice); + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create( + mHdmiControlServiceSpy)); + mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator( + mHdmiControlServiceSpy)); + + mLocalDevices.add(mAudioSystemDeviceSpy); + mLocalDevices.add(mPlaybackDeviceSpy); mHdmiPortInfo = new HdmiPortInfo[4]; mHdmiPortInfo[0] = new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false); @@ -239,80 +237,81 @@ public class HdmiControlServiceTest { mHdmiPortInfo[3] = new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.initService(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.initService(); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); } @Test public void onStandby_notByCec_cannotGoToStandby() { - mStandbyMessageReceived = false; - mPlaybackDevice.setCanGoToStandby(false); + doReturn(false).when(mHdmiControlServiceSpy).isStandbyMessageReceived(); + + mPlaybackDeviceSpy.setCanGoToStandby(false); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); - assertTrue(mPlaybackDevice.isStandby()); - assertTrue(mAudioSystemDevice.isStandby()); - assertFalse(mPlaybackDevice.isDisabled()); - assertFalse(mAudioSystemDevice.isDisabled()); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + assertTrue(mPlaybackDeviceSpy.isStandby()); + assertTrue(mAudioSystemDeviceSpy.isStandby()); + assertFalse(mPlaybackDeviceSpy.isDisabled()); + assertFalse(mAudioSystemDeviceSpy.isDisabled()); } @Test public void onStandby_byCec() { - mStandbyMessageReceived = true; + doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived(); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); - assertTrue(mPlaybackDevice.isStandby()); - assertTrue(mAudioSystemDevice.isStandby()); - assertTrue(mPlaybackDevice.isDisabled()); - assertTrue(mAudioSystemDevice.isDisabled()); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + assertTrue(mPlaybackDeviceSpy.isStandby()); + assertTrue(mAudioSystemDeviceSpy.isStandby()); + assertTrue(mPlaybackDeviceSpy.isDisabled()); + assertTrue(mAudioSystemDeviceSpy.isDisabled()); } @Test public void initialPowerStatus_normalBoot_isTransientToStandby() { - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); } @Test public void initialPowerStatus_quiescentBoot_isTransientToStandby() throws RemoteException { when(mIPowerManagerMock.isInteractive()).thenReturn(false); - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); } @Test public void powerStatusAfterBootComplete_normalBoot_isOn() { - mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); - mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED); - assertThat(mHdmiControlService.getPowerStatus()).isEqualTo( + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); + mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED); + assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); } @Test public void powerStatusAfterBootComplete_quiescentBoot_isStandby() throws RemoteException { when(mIPowerManagerMock.isInteractive()).thenReturn(false); - mHdmiControlService.onBootPhase(PHASE_BOOT_COMPLETED); - assertThat(mHdmiControlService.getPowerStatus()).isEqualTo( + mHdmiControlServiceSpy.onBootPhase(PHASE_BOOT_COMPLETED); + assertThat(mHdmiControlServiceSpy.getPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_STANDBY); } @Test public void initialPowerStatus_normalBoot_goToStandby_doesNotBroadcastsPowerStatus_1_4() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); mNativeWrapper.clearResultMessages(); - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( Constants.ADDR_PLAYBACK_1, Constants.ADDR_BROADCAST, @@ -322,21 +321,21 @@ public class HdmiControlServiceTest { @Test public void initialPowerStatus_normalBoot_goToStandby_broadcastsPowerStatus_2_0() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); mNativeWrapper.clearResultMessages(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getInitialPowerStatus()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); - mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); mTestLooper.dispatchAll(); HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus( @@ -347,53 +346,53 @@ public class HdmiControlServiceTest { @Test public void setAndGetCecVolumeControlEnabled_isApi() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @Test public void setAndGetCecVolumeControlEnabled_changesSetting() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_DISABLED); - assertThat(mHdmiControlService.readIntSetting( + assertThat(mHdmiControlServiceSpy.readIntSetting( Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); - assertThat(mHdmiControlService.readIntSetting( + assertThat(mHdmiControlServiceSpy.readIntSetting( Settings.Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, -1)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @Test public void setAndGetCecVolumeControlEnabledInternal_doesNotChangeSetting() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @@ -401,60 +400,61 @@ public class HdmiControlServiceTest { @Test public void disableAndReenableCec_volumeControlReturnsToOriginalValue_enabled() { int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_ENABLED; - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled); + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal(volumeControlEnabled); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecVolumeControl()).isEqualTo(volumeControlEnabled); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecVolumeControl()) + .isEqualTo(volumeControlEnabled); } @Test public void disableAndReenableCec_volumeControlReturnsToOriginalValue_disabled() { int volumeControlEnabled = HdmiControlManager.VOLUME_CONTROL_DISABLED; - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, volumeControlEnabled); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( volumeControlEnabled); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getHdmiCecConfig().getIntValue( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getHdmiCecConfig().getIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)).isEqualTo( volumeControlEnabled); } @Test public void disableAndReenableCec_volumeControlFeatureListenersNotified() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, HdmiControlManager.VOLUME_CONTROL_ENABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); assertThat(callback.mCallbackReceived).isTrue(); assertThat(callback.mVolumeControlEnabled).isEqualTo( HdmiControlManager.VOLUME_CONTROL_DISABLED); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); assertThat(callback.mVolumeControlEnabled).isEqualTo( HdmiControlManager.VOLUME_CONTROL_ENABLED); } @Test public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); mTestLooper.dispatchAll(); assertThat(callback.mCallbackReceived).isTrue(); @@ -464,11 +464,11 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); mTestLooper.dispatchAll(); assertThat(callback.mCallbackReceived).isTrue(); @@ -478,13 +478,13 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); mTestLooper.dispatchAll(); @@ -495,15 +495,15 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback); mTestLooper.dispatchAll(); - mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.removeHdmiControlVolumeControlStatusChangeListener(callback); + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); mTestLooper.dispatchAll(); @@ -514,16 +514,16 @@ public class HdmiControlServiceTest { @Test public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() { - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_DISABLED); VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback(); VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback(); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback1); - mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback1); + mHdmiControlServiceSpy.addHdmiCecVolumeControlFeatureListener(callback2); - mHdmiControlService.setHdmiCecVolumeControlEnabledInternal( + mHdmiControlServiceSpy.setHdmiCecVolumeControlEnabledInternal( HdmiControlManager.VOLUME_CONTROL_ENABLED); mTestLooper.dispatchAll(); @@ -537,47 +537,48 @@ public class HdmiControlServiceTest { @Test public void getCecVersion_1_4() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } @Test public void getCecVersion_2_0() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_2_0); } @Test public void getCecVersion_change() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_2_0); } @Test public void handleGiveFeatures_cec14_featureAbort() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV, @@ -592,11 +593,11 @@ public class HdmiControlServiceTest { @Test public void handleGiveFeatures_cec20_reportsFeatures() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV, @@ -606,43 +607,43 @@ public class HdmiControlServiceTest { HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), - mPlaybackDevice.getDeviceFeatures()); + mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), + mPlaybackDeviceSpy.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures); } @Test public void initializeCec_14_doesNotBroadcastReportFeatures() { mNativeWrapper.clearResultMessages(); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), - mPlaybackDevice.getDeviceFeatures()); + mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), + mPlaybackDeviceSpy.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportFeatures); } @Test public void initializeCec_20_reportsFeaturesBroadcast() { - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), - mPlaybackDevice.getRcProfile(), mPlaybackDevice.getRcFeatures(), - mPlaybackDevice.getDeviceFeatures()); + mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), + mPlaybackDeviceSpy.getDeviceFeatures()); assertThat(mNativeWrapper.getResultMessages()).contains(reportFeatures); } @@ -653,7 +654,7 @@ public class HdmiControlServiceTest { Binder.setCallingWorkSourceUid(callerUid); WorkSourceUidReadingRunnable uidReadingRunnable = new WorkSourceUidReadingRunnable(); - mHdmiControlService.runOnServiceThread(uidReadingRunnable); + mHdmiControlServiceSpy.runOnServiceThread(uidReadingRunnable); Binder.setCallingWorkSourceUid(runnerUid); @@ -666,36 +667,36 @@ public class HdmiControlServiceTest { @Test public void initCecVersion_limitToMinimumSupportedVersion() { mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } @Test public void initCecVersion_limitToAtLeast1_4() { mNativeWrapper.setCecVersion(0x0); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_1_4_B); } @Test public void initCecVersion_useHighestMatchingVersion() { mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0); - mHdmiControlService.getHdmiCecConfig().setIntValue( + mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue( HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mTestLooper.dispatchAll(); - assertThat(mHdmiControlService.getCecVersion()).isEqualTo( + assertThat(mHdmiControlServiceSpy.getCecVersion()).isEqualTo( HdmiControlManager.HDMI_CEC_VERSION_2_0); } @@ -710,4 +711,140 @@ public class HdmiControlServiceTest { this.mVolumeControlEnabled = enabled; } } + + @Test + public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() { + // Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("40:8D:03"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.ABORT_INVALID_OPERAND); + } + + @Test + public void handleCecCommand_errorSource_returnsHandled() { + // Validity ERROR_SOURCE. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("F0:8E"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + + } + + @Test + public void handleCecCommand_errorDestination_returnsHandled() { + // Validity ERROR_DESTINATION. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("0F:8E:00"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void handleCecCommand_errorParameterShort_returnsHandled() { + // Validity ERROR_PARAMETER_SHORT + // Taken from HdmiCecMessageValidatorTest#isValid_menuStatus + HdmiCecMessage message = HdmiUtils.buildMessage("40:8E"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void handleCecCommand_notHandledByLocalDevice_returnsNotHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + + doReturn(Constants.NOT_HANDLED).when(mHdmiControlServiceSpy) + .dispatchMessageToLocalDevice(message); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.NOT_HANDLED); + } + + @Test + public void handleCecCommand_handledByLocalDevice_returnsHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + + doReturn(Constants.HANDLED).when(mHdmiControlServiceSpy) + .dispatchMessageToLocalDevice(message); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void handleCecCommand_localDeviceReturnsFeatureAbort_returnsFeatureAbort() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1, + HdmiControlManager.POWER_STATUS_ON); + + doReturn(Constants.ABORT_REFUSED).when(mHdmiControlServiceSpy) + .dispatchMessageToLocalDevice(message); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) + .isEqualTo(Constants.ABORT_REFUSED); + } + + @Test + public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_BROADCAST); + + doReturn(Constants.ABORT_REFUSED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.ABORT_NOT_IN_CORRECT_MODE) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void dispatchMessageToLocalDevice_localDevicesDoNotHandleMessage_returnsUnhandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1); + + doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.NOT_HANDLED) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.NOT_HANDLED); + } + + @Test + public void dispatchMessageToLocalDevice_localDeviceHandlesMessage_returnsHandled() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1); + + doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.HANDLED) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.HANDLED); + } + + @Test + public void dispatchMessageToLocalDevice_localDeviceReturnsFeatureAbort_returnsFeatureAbort() { + HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( + Constants.ADDR_TV, + Constants.ADDR_PLAYBACK_1); + + doReturn(Constants.NOT_HANDLED).when(mPlaybackDeviceSpy).dispatchMessage(message); + doReturn(Constants.ABORT_REFUSED) + .when(mAudioSystemDeviceSpy).dispatchMessage(message); + + assertThat(mHdmiControlServiceSpy.dispatchMessageToLocalDevice(message)) + .isEqualTo(Constants.ABORT_REFUSED); + } } 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/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index eebc25aab279..6f1268e5de24 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -23,6 +23,7 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.content.Context; @@ -34,6 +35,7 @@ import android.content.res.Resources; import android.os.Build; import android.os.LocaleList; import android.os.Parcel; +import android.util.ArrayMap; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; @@ -48,6 +50,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -793,6 +796,97 @@ public class InputMethodUtilsTest { } } + @Test + public void testChooseSystemVoiceIme() throws Exception { + final InputMethodInfo systemIme = createFakeInputMethodInfo("SystemIme", "fake.voice0", + true /* isSystem */); + + // Returns null when the config value is null. + { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodMap.put(systemIme.getId(), systemIme); + assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, null, "")); + } + + // Returns null when the config value is empty. + { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodMap.put(systemIme.getId(), systemIme); + assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, "", "")); + } + + // Returns null when the configured package doesn't have an IME. + { + assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(), + systemIme.getPackageName(), "")); + } + + // Returns the right one when the current default is null. + { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodMap.put(systemIme.getId(), systemIme); + assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap, + systemIme.getPackageName(), null)); + } + + // Returns the right one when the current default is empty. + { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodMap.put(systemIme.getId(), systemIme); + assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap, + systemIme.getPackageName(), "")); + } + + // Returns null when the current default isn't found. + { + assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(), + systemIme.getPackageName(), systemIme.getId())); + } + + // Returns null when there are multiple IMEs defined by the config package. + { + final InputMethodInfo secondIme = createFakeInputMethodInfo(systemIme.getPackageName(), + "fake.voice1", true /* isSystem */); + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodMap.put(systemIme.getId(), systemIme); + methodMap.put(secondIme.getId(), secondIme); + assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(), + "")); + } + + // Returns the current one when the current default and config point to the same package. + { + final InputMethodInfo secondIme = createFakeInputMethodInfo("SystemIme", "fake.voice1", + true /* isSystem */); + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodMap.put(systemIme.getId(), systemIme); + methodMap.put(secondIme.getId(), secondIme); + assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap, + systemIme.getPackageName(), systemIme.getId())); + } + + // Doesn't return the current default if it isn't a system app. + { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme", + "fake.voice0", false /* isSystem */); + methodMap.put(nonSystemIme.getId(), nonSystemIme); + assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, + nonSystemIme.getPackageName(), nonSystemIme.getId())); + } + + // Returns null if the configured one isn't a system app. + { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final InputMethodInfo nonSystemIme = createFakeInputMethodInfo( + "FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */); + methodMap.put(systemIme.getId(), systemIme); + methodMap.put(nonSystemIme.getId(), nonSystemIme); + assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, + nonSystemIme.getPackageName(), "")); + } + } + private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes, final Locale systemLocale, String... expectedImeNames) { final Context context = createTargetContextWithLocales(new LocaleList(systemLocale)); @@ -866,6 +960,25 @@ public class InputMethodUtilsTest { } private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name, + boolean isSystem) { + final ResolveInfo ri = new ResolveInfo(); + final ServiceInfo si = new ServiceInfo(); + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.enabled = true; + if (isSystem) { + ai.flags |= ApplicationInfo.FLAG_SYSTEM; + } + si.applicationInfo = ai; + si.enabled = true; + si.packageName = packageName; + si.name = name; + si.exported = true; + ri.serviceInfo = si; + return new InputMethodInfo(ri, false, "", Collections.emptyList(), 1, true); + } + + private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name, CharSequence label, boolean isAuxIme, boolean isDefault, List<InputMethodSubtype> subtypes) { final ResolveInfo ri = new ResolveInfo(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index 91342ce925f6..8c08226201a8 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -21,6 +21,7 @@ import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_PROFILE; import static android.os.UserHandle.USER_SYSTEM; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -110,6 +111,10 @@ public class RebootEscrowManagerTests { public interface MockableRebootEscrowInjected { int getBootCount(); + long getCurrentTimeMillis(); + + boolean forceServerBased(); + void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete); } @@ -174,6 +179,9 @@ public class RebootEscrowManagerTests { @Override public boolean serverBasedResumeOnReboot() { + if (mInjected.forceServerBased()) { + return true; + } return mServerBased; } @@ -205,9 +213,20 @@ public class RebootEscrowManagerTests { } @Override + public String getVbmetaDigest(boolean other) { + return other ? "" : "fake digest"; + } + + @Override + public long getCurrentTimeMillis() { + return mInjected.getCurrentTimeMillis(); + } + + @Override public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount, int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete) { + mInjected.reportMetric(success, errorCode, serviceType, attemptCount, escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete); } @@ -430,16 +449,21 @@ public class RebootEscrowManagerTests { // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); + when(mInjected.getCurrentTimeMillis()).thenReturn(30000L); + mStorage.setLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, 10000L, + USER_SYSTEM); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */, - anyInt(), anyInt(), anyInt()); + eq(20), eq(0) /* vbmeta status */, anyInt()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); + assertEquals(mStorage.getLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, + -1, USER_SYSTEM), -1); } @Test @@ -468,7 +492,7 @@ public class RebootEscrowManagerTests { ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */, - anyInt(), anyInt(), anyInt()); + anyInt(), eq(0) /* vbmeta status */, anyInt()); when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -479,6 +503,84 @@ public class RebootEscrowManagerTests { } @Test + public void loadRebootEscrowDataIfAvailable_ServerBasedRemoteException_Failure() + throws Exception { + setServerBasedRebootEscrowProvider(); + + when(mInjected.getBootCount()).thenReturn(0); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + // Use x -> x for both wrap & unwrap functions. + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + metricsErrorCodeCaptor.capture(), eq(2) /* Server based */, + eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); + + when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class); + mService.loadRebootEscrowDataIfAvailable(null); + verify(mServiceConnection).unwrap(any(), anyLong()); + assertFalse(metricsSuccessCaptor.getValue()); + assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY), + metricsErrorCodeCaptor.getValue()); + } + + @Test + public void loadRebootEscrowDataIfAvailable_ServerBasedIoError_RetryFailure() throws Exception { + setServerBasedRebootEscrowProvider(); + + when(mInjected.getBootCount()).thenReturn(0); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + // Use x -> x for both wrap & unwrap functions. + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), + metricsErrorCodeCaptor.capture(), eq(2) /* Server based */, + eq(2) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt()); + when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(IOException.class); + + HandlerThread thread = new HandlerThread("RebootEscrowManagerTest"); + thread.start(); + mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper())); + // Sleep 5s for the retry to complete + Thread.sleep(5 * 1000); + assertFalse(metricsSuccessCaptor.getValue()); + assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_RETRY_COUNT_EXHAUSTED), + metricsErrorCodeCaptor.getValue()); + } + + @Test public void loadRebootEscrowDataIfAvailable_ServerBased_RetrySuccess() throws Exception { setServerBasedRebootEscrowProvider(); @@ -607,9 +709,14 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); + // Trigger a vbmeta digest mismatch + mStorage.setString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST, + "non sense value", USER_SYSTEM); mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected).reportMetric(eq(true), eq(0) /* error code */, eq(1) /* HAL based */, - eq(1) /* attempt count */, anyInt(), anyInt(), anyInt()); + eq(1) /* attempt count */, anyInt(), eq(2) /* vbmeta status */, anyInt()); + assertEquals(mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST, + "", USER_SYSTEM), ""); } @Test @@ -636,12 +743,17 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(1); ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); + // Return a null escrow key doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(), - anyInt() /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */, - anyInt(), anyInt(), anyInt()); - when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); + metricsErrorCodeCaptor.capture(), eq(1) /* HAL based */, + eq(1) /* attempt count */, anyInt(), anyInt(), anyInt()); + + when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> null); mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertFalse(metricsSuccessCaptor.getValue()); + assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY), + metricsErrorCodeCaptor.getValue()); } } 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..100d3ea87a89 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1773,57 +1773,75 @@ public class NetworkPolicyManagerServiceTest { true); } + private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) { + stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, + rxBytes, 1, txBytes, 1, 0); + when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) + .thenReturn(stats.getTotalBytes()); + when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) + .thenReturn(stats); + } + + private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException { + final NetworkPolicyManagerInternal npmi = LocalServices + .getService(NetworkPolicyManagerInternal.class); + npmi.onStatsProviderWarningOrLimitReached("TEST"); + // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED. + postMsgAndWaitForCompletion(); + verify(mStatsService).forceUpdate(); + // Wait for processing of MSG_*_INTERFACE_QUOTAS. + postMsgAndWaitForCompletion(); + } + /** - * Test that when StatsProvider triggers limit reached, new limit will be calculated and - * re-armed. + * Test that when StatsProvider triggers warning and limit reached, new quotas will be + * calculated and re-armed. */ @Test - public void testStatsProviderLimitReached() throws Exception { + public void testStatsProviderWarningAndLimitReached() throws Exception { final int CYCLE_DAY = 15; final NetworkStats stats = new NetworkStats(0L, 1); - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - 2999, 1, 2000, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); + increaseMockedTotalBytes(stats, 2999, 2000); // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, + Long.MAX_VALUE); - // Set limit to 10KB. + // Set warning to 7KB and limit to 10KB. setNetworkPolicies(new NetworkPolicy( - sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L, - true)); + sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true)); postMsgAndWaitForCompletion(); - // Verifies that remaining quota is set to providers. - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L); - + // Verifies that remaining quotas are set to providers. + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L); reset(mStatsService); - // Increase the usage. - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - 1000, 1, 999, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); + // Increase the usage and simulates that limit reached fires earlier by provider, + // but actually the quota is not yet reached. Verifies that the limit reached leads to + // a force update and new quotas should be set. + increaseMockedTotalBytes(stats, 1000, 999); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L); + reset(mStatsService); - // Simulates that limit reached fires earlier by provider, but actually the quota is not - // yet reached. - final NetworkPolicyManagerInternal npmi = LocalServices - .getService(NetworkPolicyManagerInternal.class); - npmi.onStatsProviderLimitReached("TEST"); + // Increase the usage and simulate warning reached, the new warning should be unlimited + // since service will disable warning quota to stop lower layer from keep triggering + // warning reached event. + increaseMockedTotalBytes(stats, 1000L, 1000); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync( + TEST_IFACE, Long.MAX_VALUE, 1002L); + reset(mStatsService); - // Verifies that the limit reached leads to a force update and new limit should be set. - postMsgAndWaitForCompletion(); - verify(mStatsService).forceUpdate(); - postMsgAndWaitForCompletion(); - verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L); + // Increase the usage that over the warning and limit, the new limit should set to 1 to + // block the network traffic. + increaseMockedTotalBytes(stats, 1000L, 1000); + triggerOnStatsProviderWarningOrLimitReached(); + verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L); + reset(mStatsService); } /** 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..8e2b20700e2c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -89,6 +89,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -641,7 +642,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 +667,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 { @@ -763,8 +770,24 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + public void writeQueryResultsToFile(String packageName, String databaseName, + ParcelFileDescriptor fileDescriptor, String queryExpression, + Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback) + throws RemoteException { + ignore(callback); + } + + @Override + public void putDocumentsFromFile(String packageName, String databaseName, + ParcelFileDescriptor fileDescriptor, int userId, IAppSearchResultCallback callback) + throws RemoteException { + ignore(callback); + } + + @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/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index d63a4674a83d..a2311690744e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -287,7 +287,7 @@ public class PackageManagerSettingsTests { final SuspendDialogInfo dialogInfo1 = new SuspendDialogInfo.Builder() .setIcon(0x11220001) - .setTitle(0x11220002) + .setTitle("String Title") .setMessage("1st message") .setNeutralButtonText(0x11220003) .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS) @@ -296,7 +296,7 @@ public class PackageManagerSettingsTests { .setIcon(0x22220001) .setTitle(0x22220002) .setMessage("2nd message") - .setNeutralButtonText(0x22220003) + .setNeutralButtonText("String button text") .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND) .build(); diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java index 322e448d983f..826a8d446e1d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java @@ -57,7 +57,7 @@ public class SuspendDialogInfoTest { } @Test - public void equalsComparesTitle() { + public void equalsComparesTitleIds() { final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder(); final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder(); assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); @@ -67,7 +67,39 @@ public class SuspendDialogInfoTest { } @Test - public void equalsComparesButtonText() { + public void equalsIgnoresTitleStringsWhenIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setTitle(VALID_TEST_RES_ID_1) + .setTitle("1st title"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setTitle(VALID_TEST_RES_ID_1) + .setTitle("2nd title"); + // String titles different but should get be ignored when resource ids are set + assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void equalsComparesTitleStringsWhenNoIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setTitle("1st title"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setTitle("2nd title"); + // Both have different titles, which are not ignored as resource ids aren't set + assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void titleStringClearedWhenResIdSet() { + final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder() + .setTitle(VALID_TEST_RES_ID_2) + .setTitle("Should be cleared on build") + .build(); + assertNull(dialogInfo.getTitle()); + assertEquals(VALID_TEST_RES_ID_2, dialogInfo.getTitleResId()); + } + + @Test + public void equalsComparesButtonTextIds() { final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder(); final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder(); assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); @@ -77,6 +109,38 @@ public class SuspendDialogInfoTest { } @Test + public void equalsIgnoresButtonStringsWhenIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setNeutralButtonText(VALID_TEST_RES_ID_1) + .setNeutralButtonText("1st button text"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setNeutralButtonText(VALID_TEST_RES_ID_1) + .setNeutralButtonText("2nd button text"); + // Button strings different but should get be ignored when resource ids are set + assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void equalsComparesButtonStringsWhenNoIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setNeutralButtonText("1st button text"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setNeutralButtonText("2nd button text"); + // Both have different button texts, which are not ignored as resource ids aren't set + assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void buttonStringClearedWhenResIdSet() { + final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder() + .setNeutralButtonText(VALID_TEST_RES_ID_2) + .setNeutralButtonText("Should be cleared on build") + .build(); + assertNull(dialogInfo.getNeutralButtonText()); + assertEquals(VALID_TEST_RES_ID_2, dialogInfo.getNeutralButtonTextResId()); + } + + @Test public void equalsComparesButtonAction() { final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder(); final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder(); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 395b643e3777..fc2661103491 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -58,6 +58,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; @@ -158,6 +159,40 @@ public final class UserManagerTest { fail("Didn't find a guest: " + list); } + @Test + public void testCloneUser() throws Exception { + // Test that only one clone user can be created + final int primaryUserId = mUserManager.getPrimaryUser().id; + UserInfo userInfo = createProfileForUser("Clone user1", + UserManager.USER_TYPE_PROFILE_CLONE, + primaryUserId); + assertThat(userInfo).isNotNull(); + UserInfo userInfo2 = createProfileForUser("Clone user2", + UserManager.USER_TYPE_PROFILE_CLONE, + primaryUserId); + assertThat(userInfo2).isNull(); + + final Context userContext = mContext.createPackageContextAsUser("system", 0, + UserHandle.of(userInfo.id)); + assertThat(userContext.getSystemService( + UserManager.class).sharesMediaWithParent()).isTrue(); + + List<UserInfo> list = mUserManager.getUsers(); + List<UserInfo> cloneUsers = list.stream().filter( + user -> (user.id == userInfo.id && user.name.equals("Clone user1") + && user.isCloneProfile())) + .collect(Collectors.toList()); + assertThat(cloneUsers.size()).isEqualTo(1); + + // Verify clone user parent + assertThat(mUserManager.getProfileParent(primaryUserId)).isNull(); + UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id); + assertThat(parentProfileInfo).isNotNull(); + assertThat(primaryUserId).isEqualTo(parentProfileInfo.id); + removeUser(userInfo.id); + assertThat(mUserManager.getProfileParent(primaryUserId)).isNull(); + } + @MediumTest @Test public void testAdd2Users() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 6e5fbd0b6ed0..2df6c5ab80e8 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -71,6 +71,7 @@ import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.test.mock.MockContentResolver; import android.view.Display; +import android.view.DisplayInfo; import androidx.test.InstrumentationRegistry; @@ -594,6 +595,8 @@ public class PowerManagerServiceTest { public void testWasDeviceIdleFor_true() { int interval = 1000; createService(); + mService.systemReady(null); + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mService.onUserActivity(); advanceTime(interval + 1 /* just a little more */); assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue(); @@ -603,6 +606,8 @@ public class PowerManagerServiceTest { public void testWasDeviceIdleFor_false() { int interval = 1000; createService(); + mService.systemReady(null); + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mService.onUserActivity(); assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse(); } @@ -757,6 +762,9 @@ public class PowerManagerServiceTest { @Test public void testInattentiveSleep_userActivityDismissesWarning() throws Exception { + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); setMinimumScreenOffTimeoutConfig(5); setAttentiveWarningDuration(1900); setAttentiveTimeout(2000); diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java index 324e5929f77f..7903a90979fb 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java @@ -22,6 +22,7 @@ import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -72,6 +73,7 @@ public class RecoverySystemServiceTest { private LockSettingsInternal mLockSettingsInternal; private IBootControl mIBootControl; private RecoverySystemServiceTestable.IMetricsReporter mMetricsReporter; + private RecoverySystemService.PreferencesManager mSharedPreferences; private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package"; private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package"; @@ -97,10 +99,11 @@ public class RecoverySystemServiceTest { when(mIBootControl.getActiveBootSlot()).thenReturn(1); mMetricsReporter = mock(RecoverySystemServiceTestable.IMetricsReporter.class); + mSharedPreferences = mock(RecoverySystemService.PreferencesManager.class); mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties, powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal, - mIBootControl, mMetricsReporter); + mIBootControl, mMetricsReporter, mSharedPreferences); } @Test @@ -237,6 +240,8 @@ public class RecoverySystemServiceTest { is(true)); verify(mMetricsReporter).reportRebootEscrowPreparationMetrics( eq(1000), eq(0) /* need preparation */, eq(1) /* client count */); + verify(mSharedPreferences).putLong(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), eq(100_000L)); } @@ -245,10 +250,19 @@ public class RecoverySystemServiceTest { IntentSender intentSender = mock(IntentSender.class); assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender), is(true)); + + when(mSharedPreferences.getLong(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX), anyLong())) + .thenReturn(200_000L).thenReturn(5000L); + mRecoverySystemService.onPreparedForReboot(true); + verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics( + eq(1000), eq(1) /* client count */, + eq(-1) /* invalid duration */); + mRecoverySystemService.onPreparedForReboot(true); verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any()); verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics( - eq(1000), eq(1) /* client count */, anyInt() /* duration */); + eq(1000), eq(1) /* client count */, eq(95) /* duration */); } @Test @@ -352,12 +366,19 @@ public class RecoverySystemServiceTest { public void rebootWithLskf_Success() throws Exception { assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true)); mRecoverySystemService.onPreparedForReboot(true); + + when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2); + when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF), + anyInt())).thenReturn(3); + when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF), + anyLong())).thenReturn(40_000L); assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true), is(true)); verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000), - eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(60) /* duration */, eq(3) /* lskf capture count */); } @@ -400,13 +421,19 @@ public class RecoverySystemServiceTest { assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true)); mRecoverySystemService.onPreparedForReboot(true); - // Client B's clear won't affect client A's preparation. + when(mSharedPreferences.getInt(eq(FAKE_OTA_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2); + when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF), + anyInt())).thenReturn(1); + when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF), + anyLong())).thenReturn(60_000L); + assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true), is(true)); verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(1000), - eq(2) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + eq(2) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(40), eq(1) /* lskf capture count */); } @Test @@ -415,22 +442,30 @@ public class RecoverySystemServiceTest { mRecoverySystemService.onPreparedForReboot(true); assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true)); + when(mSharedPreferences.getInt(eq(FAKE_OTHER_PACKAGE_NAME + + RecoverySystemService.REQUEST_LSKF_COUNT_PREF_SUFFIX), anyInt())).thenReturn(2); + when(mSharedPreferences.getInt(eq(RecoverySystemService.LSKF_CAPTURED_COUNT_PREF), + anyInt())).thenReturn(1); + when(mSharedPreferences.getLong(eq(RecoverySystemService.LSKF_CAPTURED_TIMESTAMP_PREF), + anyLong())).thenReturn(60_000L); + assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true)); assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true), is(false)); verifyNoMoreInteractions(mIPowerManager); verify(mMetricsReporter).reportRebootEscrowRebootMetrics(not(eq(0)), eq(1000), - eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + eq(1) /* client count */, anyInt() /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(40), eq(1)/* lskf capture count */); assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true)); assertThat( mRecoverySystemService.rebootWithLskf(FAKE_OTHER_PACKAGE_NAME, "ab-update", true), is(true)); verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); - verify(mMetricsReporter).reportRebootEscrowRebootMetrics(eq(0), eq(2000), - eq(1) /* client count */, eq(1) /* request count */, eq(true) /* slot switch */, - anyBoolean(), anyInt(), eq(1) /* lskf capture count */); + + verify(mMetricsReporter).reportRebootEscrowRebootMetrics((eq(0)), eq(2000), + eq(1) /* client count */, eq(2) /* request count */, eq(true) /* slot switch */, + anyBoolean(), eq(40), eq(1) /* lskf capture count */); } @Test diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java index a894178fca06..27e953f30fa0 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java @@ -33,11 +33,13 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { private final LockSettingsInternal mLockSettingsInternal; private final IBootControl mIBootControl; private final IMetricsReporter mIMetricsReporter; + private final RecoverySystemService.PreferencesManager mSharedPreferences; MockInjector(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal, - IBootControl bootControl, IMetricsReporter metricsReporter) { + IBootControl bootControl, IMetricsReporter metricsReporter, + RecoverySystemService.PreferencesManager preferences) { super(context); mSystemProperties = systemProperties; mPowerManager = powerManager; @@ -46,6 +48,7 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { mLockSettingsInternal = lockSettingsInternal; mIBootControl = bootControl; mIMetricsReporter = metricsReporter; + mSharedPreferences = preferences; } @Override @@ -114,12 +117,14 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { requestedClientCount); } + @Override public void reportRebootEscrowLskfCapturedMetrics(int uid, int requestedClientCount, int requestedToLskfCapturedDurationInSeconds) { mIMetricsReporter.reportRebootEscrowLskfCapturedMetrics(uid, requestedClientCount, requestedToLskfCapturedDurationInSeconds); } + @Override public void reportRebootEscrowRebootMetrics(int errorCode, int uid, int preparedClientCount, int requestCount, boolean slotSwitch, boolean serverBased, int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts) { @@ -127,14 +132,25 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { requestCount, slotSwitch, serverBased, lskfCapturedToRebootDurationInSeconds, lskfCapturedCounts); } + + @Override + public long getCurrentTimeMillis() { + return 100_000; + } + + @Override + public RecoverySystemService.PreferencesManager getMetricsPrefs() { + return mSharedPreferences; + } } RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal, - IBootControl bootControl, IMetricsReporter metricsReporter) { + IBootControl bootControl, IMetricsReporter metricsReporter, + RecoverySystemService.PreferencesManager preferences) { super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter, - uncryptSocket, lockSettingsInternal, bootControl, metricsReporter)); + uncryptSocket, lockSettingsInternal, bootControl, metricsReporter, preferences)); } public static class FakeSystemProperties { @@ -176,5 +192,4 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { int requestCount, boolean slotSwitch, boolean serverBased, int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts); } - } diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 106827078290..742f5034a248 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -217,20 +217,20 @@ public class TimeDetectorServiceTest { fail(); } finally { verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SET_TIME), anyString()); + eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString()); } } @Test public void testSuggestExternalTime() throws Exception { - doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); + doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); ExternalTimeSuggestion externalTimeSuggestion = createExternalTimeSuggestion(); mTimeDetectorService.suggestExternalTime(externalTimeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); verify(mMockContext).enforceCallingPermission( - eq(android.Manifest.permission.SET_TIME), anyString()); + eq(android.Manifest.permission.SUGGEST_EXTERNAL_TIME), anyString()); mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestExternalTimeCalled(externalTimeSuggestion); diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 86b162087f61..8991e9fedadf 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -940,7 +940,22 @@ public class AppStandbyControllerTests { .setLong("elapsed_threshold_restricted", -1); mInjector.mPropertiesChangedListener .onPropertiesChanged(mInjector.getDeviceConfigProperties()); - testTimeout(); + + reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_ACTIVE); + + mInjector.mElapsedRealtime = HOUR_MS; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_FREQUENT); + + mInjector.mElapsedRealtime = 2 * HOUR_MS; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RARE); + + mInjector.mElapsedRealtime = 4 * HOUR_MS; + mController.checkIdleStates(USER_ID); + assertBucket(STANDBY_BUCKET_RESTRICTED); } /** diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java new file mode 100644 index 000000000000..dcff4791a907 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import static org.junit.Assert.assertEquals; + +import android.os.VibrationEffect; +import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.platform.test.annotations.Presubmit; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +/** + * Tests for {@link DeviceVibrationEffectAdapter}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:DeviceVibrationEffectAdapterTest + */ +@Presubmit +public class DeviceVibrationEffectAdapterTest { + private static final float TEST_MIN_FREQUENCY = 50; + private static final float TEST_RESONANT_FREQUENCY = 150; + private static final float TEST_FREQUENCY_RESOLUTION = 25; + private static final float[] TEST_AMPLITUDE_MAP = new float[]{ + /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f}; + + private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING = + new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); + private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING = + new VibratorInfo.FrequencyMapping(TEST_MIN_FREQUENCY, + TEST_RESONANT_FREQUENCY, TEST_FREQUENCY_RESOLUTION, + /* suggestedSafeRangeHz= */ 50, TEST_AMPLITUDE_MAP); + + private DeviceVibrationEffectAdapter mAdapter; + + @Before + public void setUp() throws Exception { + mAdapter = new DeviceVibrationEffectAdapter(); + } + + @Test + public void testPrebakedAndPrimitiveSegments_returnsOriginalSegment() { + VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( + new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10), + new PrebakedSegment( + VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG), + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)), + /* repeatIndex= */ -1); + + assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING))); + assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING))); + } + + @Test + public void testStepAndRampSegments_emptyMapping_returnsSameAmplitudesAndFrequencyZero() { + VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ 1, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1, + /* startFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f, + /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)), + /* repeatIndex= */ 2); + + VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( + new StepSegment(/* amplitude= */ 0, /* frequency= */ Float.NaN, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ Float.NaN, + /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 1, + /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN, + /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0.7f, /* endAmplitude= */ 0.5f, + /* startFrequency= */ Float.NaN, /* endFrequency= */ Float.NaN, + /* duration= */ 20)), + /* repeatIndex= */ 2); + + assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING))); + } + + @Test + public void testStepAndRampSegments_nonEmptyMapping_returnsClippedValues() { + VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 0, /* duration= */ 10), + new StepSegment(/* amplitude= */ 1, /* frequency= */ -1, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 1, + /* startFrequency= */ -4, /* endFrequency= */ 2, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f, + /* startFrequency= */ 10, /* endFrequency= */ -5, /* duration= */ 20)), + /* repeatIndex= */ 2); + + VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList( + new StepSegment(/* amplitude= */ 0.5f, /* frequency= */ 150, /* duration= */ 10), + new StepSegment(/* amplitude= */ 0.8f, /* frequency= */ 125, /* duration= */ 100), + new RampSegment(/* startAmplitude= */ 0.1f, /* endAmplitude= */ 0.8f, + /* startFrequency= */ 50, /* endFrequency= */ 200, /* duration= */ 50), + new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.1f, + /* startFrequency= */ 200, /* endFrequency= */ 50, /* duration= */ 20)), + /* repeatIndex= */ 2); + + assertEquals(expected, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING))); + } + + private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping) { + return new VibratorInfo(/* id= */ 0, /* capabilities= */ 0, null, null, + /* qFactor= */ Float.NaN, frequencyMapping); + } +} diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java index b54b6969e7df..1e3c34474820 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -20,6 +20,10 @@ import android.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.os.VibrationEffect; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener; @@ -37,9 +41,9 @@ final class FakeVibratorControllerProvider { private static final int EFFECT_DURATION = 20; - private final Map<Long, VibrationEffect.Prebaked> mEnabledAlwaysOnEffects = new HashMap<>(); - private final List<VibrationEffect> mEffects = new ArrayList<>(); - private final List<Integer> mAmplitudes = new ArrayList<>(); + private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>(); + private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>(); + private final List<Float> mAmplitudes = new ArrayList<>(); private final Handler mHandler; private final FakeNativeWrapper mNativeWrapper; @@ -57,85 +61,96 @@ final class FakeVibratorControllerProvider { public OnVibrationCompleteListener listener; public boolean isInitialized; + @Override public void init(int vibratorId, OnVibrationCompleteListener listener) { isInitialized = true; this.vibratorId = vibratorId; this.listener = listener; } + @Override public boolean isAvailable() { return mIsAvailable; } + @Override public void on(long milliseconds, long vibrationId) { - VibrationEffect effect = VibrationEffect.createOneShot( - milliseconds, VibrationEffect.DEFAULT_AMPLITUDE); - mEffects.add(effect); + mEffectSegments.add(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, + /* frequency= */ 0, (int) milliseconds)); applyLatency(); scheduleListener(milliseconds, vibrationId); } + @Override public void off() { } - public void setAmplitude(int amplitude) { + @Override + public void setAmplitude(float amplitude) { mAmplitudes.add(amplitude); applyLatency(); } + @Override public int[] getSupportedEffects() { return mSupportedEffects; } + @Override public int[] getSupportedPrimitives() { return mSupportedPrimitives; } + @Override public float getResonantFrequency() { return mResonantFrequency; } + @Override public float getQFactor() { return mQFactor; } + @Override public long perform(long effect, long strength, long vibrationId) { if (mSupportedEffects == null || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) { return 0; } - mEffects.add(new VibrationEffect.Prebaked((int) effect, false, (int) strength)); + mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength)); applyLatency(); scheduleListener(EFFECT_DURATION, vibrationId); return EFFECT_DURATION; } - public long compose(VibrationEffect.Composition.PrimitiveEffect[] effect, - long vibrationId) { - VibrationEffect.Composed composed = new VibrationEffect.Composed(Arrays.asList(effect)); - mEffects.add(composed); - applyLatency(); + @Override + public long compose(PrimitiveSegment[] effects, long vibrationId) { long duration = 0; - for (VibrationEffect.Composition.PrimitiveEffect e : effect) { - duration += EFFECT_DURATION + e.delay; + for (PrimitiveSegment primitive : effects) { + duration += EFFECT_DURATION + primitive.getDelay(); + mEffectSegments.add(primitive); } + applyLatency(); scheduleListener(duration, vibrationId); return duration; } + @Override public void setExternalControl(boolean enabled) { } + @Override public long getCapabilities() { return mCapabilities; } + @Override public void alwaysOnEnable(long id, long effect, long strength) { - VibrationEffect.Prebaked prebaked = new VibrationEffect.Prebaked((int) effect, false, - (int) strength); + PrebakedSegment prebaked = new PrebakedSegment((int) effect, false, (int) strength); mEnabledAlwaysOnEffects.put(id, prebaked); } + @Override public void alwaysOnDisable(long id) { mEnabledAlwaysOnEffects.remove(id); } @@ -222,21 +237,21 @@ final class FakeVibratorControllerProvider { * Return the amplitudes set by this controller, including zeroes for each time the vibrator was * turned off. */ - public List<Integer> getAmplitudes() { + public List<Float> getAmplitudes() { return new ArrayList<>(mAmplitudes); } - /** Return list of {@link VibrationEffect} played by this controller, in order. */ - public List<VibrationEffect> getEffects() { - return new ArrayList<>(mEffects); + /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */ + public List<VibrationEffectSegment> getEffectSegments() { + return new ArrayList<>(mEffectSegments); } /** - * Return the {@link VibrationEffect.Prebaked} effect enabled with given id, or {@code null} if + * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if * missing or disabled. */ @Nullable - public VibrationEffect.Prebaked getAlwaysOnEffect(int id) { + public PrebakedSegment getAlwaysOnEffect(int id) { return mEnabledAlwaysOnEffects.get((long) id); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java index b6c11fe62ff6..59c0b0e96fcd 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationScalerTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; -import android.os.CombinedVibrationEffect; import android.os.Handler; import android.os.IExternalVibratorService; import android.os.PowerManagerInternal; @@ -35,6 +34,10 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.Presubmit; import android.provider.Settings; @@ -130,51 +133,13 @@ public class VibrationScalerTest { } @Test - public void scale_withCombined_resolvesAndScalesRecursively() { + public void scale_withPrebakedSegment_setsEffectStrengthBasedOnSettings() { setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); - VibrationEffect prebaked = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - VibrationEffect oneShot = VibrationEffect.createOneShot(10, 10); - - CombinedVibrationEffect.Mono monoScaled = mVibrationScaler.scale( - CombinedVibrationEffect.createSynced(prebaked), - VibrationAttributes.USAGE_NOTIFICATION); - VibrationEffect.Prebaked prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect(); - assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - - CombinedVibrationEffect.Stereo stereoScaled = mVibrationScaler.scale( - CombinedVibrationEffect.startSynced() - .addVibrator(1, prebaked) - .addVibrator(2, oneShot) - .combine(), - VibrationAttributes.USAGE_NOTIFICATION); - prebakedScaled = (VibrationEffect.Prebaked) stereoScaled.getEffects().get(1); - assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - VibrationEffect.OneShot oneshotScaled = - (VibrationEffect.OneShot) stereoScaled.getEffects().get(2); - assertTrue(oneshotScaled.getAmplitude() > 0); - - CombinedVibrationEffect.Sequential sequentialScaled = mVibrationScaler.scale( - CombinedVibrationEffect.startSequential() - .addNext(CombinedVibrationEffect.createSynced(prebaked)) - .addNext(CombinedVibrationEffect.createSynced(oneShot)) - .combine(), - VibrationAttributes.USAGE_NOTIFICATION); - monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(0); - prebakedScaled = (VibrationEffect.Prebaked) monoScaled.getEffect(); - assertEquals(prebakedScaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - monoScaled = (CombinedVibrationEffect.Mono) sequentialScaled.getEffects().get(1); - oneshotScaled = (VibrationEffect.OneShot) monoScaled.getEffect(); - assertTrue(oneshotScaled.getAmplitude() > 0); - } - - @Test - public void scale_withPrebaked_setsEffectStrengthBasedOnSettings() { - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); + PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK, + /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); - VibrationEffect.Prebaked scaled = mVibrationScaler.scale( + PrebakedSegment scaled = mVibrationScaler.scale( effect, VibrationAttributes.USAGE_NOTIFICATION); assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); @@ -196,25 +161,33 @@ public class VibrationScalerTest { } @Test - public void scale_withPrebakedAndFallback_resolvesAndScalesRecursively() { + public void scale_withPrebakedEffect_setsEffectStrengthBasedOnSettings() { setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); - VibrationEffect.OneShot fallback2 = (VibrationEffect.OneShot) VibrationEffect.createOneShot( - 10, VibrationEffect.DEFAULT_AMPLITUDE); - VibrationEffect.Prebaked fallback1 = new VibrationEffect.Prebaked( - VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback2); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, fallback1); - - VibrationEffect.Prebaked scaled = mVibrationScaler.scale( - effect, VibrationAttributes.USAGE_NOTIFICATION); - VibrationEffect.Prebaked scaledFallback1 = - (VibrationEffect.Prebaked) scaled.getFallbackEffect(); - VibrationEffect.OneShot scaledFallback2 = - (VibrationEffect.OneShot) scaledFallback1.getFallbackEffect(); + VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); + + PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_MEDIUM); + scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_LOW); + scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, + Vibrator.VIBRATION_INTENSITY_OFF); + scaled = getFirstSegment(mVibrationScaler.scale( + effect, VibrationAttributes.USAGE_NOTIFICATION)); + // Unexpected intensity setting will be mapped to STRONG. assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - assertEquals(scaledFallback1.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG); - assertTrue(scaledFallback2.getAmplitude() > 0); } @Test @@ -224,20 +197,20 @@ public class VibrationScalerTest { setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); - VibrationEffect.OneShot oneShot = mVibrationScaler.scale( + StepSegment resolved = getFirstSegment(mVibrationScaler.scale( VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE), - VibrationAttributes.USAGE_RINGTONE); - assertTrue(oneShot.getAmplitude() > 0); + VibrationAttributes.USAGE_RINGTONE)); + assertTrue(resolved.getAmplitude() > 0); - VibrationEffect.Waveform waveform = mVibrationScaler.scale( + resolved = getFirstSegment(mVibrationScaler.scale( VibrationEffect.createWaveform(new long[]{10}, new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1), - VibrationAttributes.USAGE_RINGTONE); - assertTrue(waveform.getAmplitudes()[0] > 0); + VibrationAttributes.USAGE_RINGTONE)); + assertTrue(resolved.getAmplitude() > 0); } @Test - public void scale_withOneShotWaveform_scalesAmplitude() { + public void scale_withOneShotAndWaveform_scalesAmplitude() { mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); @@ -248,21 +221,21 @@ public class VibrationScalerTest { setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); - VibrationEffect.OneShot oneShot = mVibrationScaler.scale( - VibrationEffect.createOneShot(100, 100), VibrationAttributes.USAGE_RINGTONE); + StepSegment scaled = getFirstSegment(mVibrationScaler.scale( + VibrationEffect.createOneShot(128, 128), VibrationAttributes.USAGE_RINGTONE)); // Ringtone scales up. - assertTrue(oneShot.getAmplitude() > 100); + assertTrue(scaled.getAmplitude() > 0.5); - VibrationEffect.Waveform waveform = mVibrationScaler.scale( - VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, -1), - VibrationAttributes.USAGE_NOTIFICATION); + scaled = getFirstSegment(mVibrationScaler.scale( + VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1), + VibrationAttributes.USAGE_NOTIFICATION)); // Notification scales down. - assertTrue(waveform.getAmplitudes()[0] < 100); + assertTrue(scaled.getAmplitude() < 0.5); - oneShot = mVibrationScaler.scale(VibrationEffect.createOneShot(100, 100), - VibrationAttributes.USAGE_TOUCH); + scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128), + VibrationAttributes.USAGE_TOUCH)); // Haptic feedback does not scale. - assertEquals(100, oneShot.getAmplitude()); + assertEquals(128f / 255, scaled.getAmplitude(), 1e-5); } @Test @@ -280,18 +253,23 @@ public class VibrationScalerTest { VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f).compose(); - VibrationEffect.Composed scaled = mVibrationScaler.scale(composed, - VibrationAttributes.USAGE_RINGTONE); + PrimitiveSegment scaled = getFirstSegment(mVibrationScaler.scale(composed, + VibrationAttributes.USAGE_RINGTONE)); // Ringtone scales up. - assertTrue(scaled.getPrimitiveEffects().get(0).scale > 0.5f); + assertTrue(scaled.getScale() > 0.5f); - scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_NOTIFICATION); + scaled = getFirstSegment(mVibrationScaler.scale(composed, + VibrationAttributes.USAGE_NOTIFICATION)); // Notification scales down. - assertTrue(scaled.getPrimitiveEffects().get(0).scale < 0.5f); + assertTrue(scaled.getScale() < 0.5f); - scaled = mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH); + scaled = getFirstSegment(mVibrationScaler.scale(composed, VibrationAttributes.USAGE_TOUCH)); // Haptic feedback does not scale. - assertEquals(0.5, scaled.getPrimitiveEffects().get(0).scale, 1e-5); + assertEquals(0.5, scaled.getScale(), 1e-5); + } + + private <T extends VibrationEffectSegment> T getFirstSegment(VibrationEffect.Composed effect) { + return (T) effect.getSegments().get(0); } private void setUserSetting(String settingName, int value) { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index 7d5eec0834a1..37e0ec2159d9 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -40,6 +40,10 @@ import android.os.SystemClock; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.LargeTest; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; @@ -61,6 +65,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Tests for {@link VibrationThread}. @@ -142,8 +147,8 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test @@ -161,7 +166,7 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty()); } @@ -183,8 +188,9 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(15)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); - assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); + assertEquals(expectedAmplitudes(1, 2, 3), + mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test @@ -213,12 +219,12 @@ public class VibrationThreadTest { verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); - List<Integer> playedAmplitudes = fakeVibrator.getAmplitudes(); - assertFalse(fakeVibrator.getEffects().isEmpty()); + List<Float> playedAmplitudes = fakeVibrator.getAmplitudes(); + assertFalse(fakeVibrator.getEffectSegments().isEmpty()); assertFalse(playedAmplitudes.isEmpty()); for (int i = 0; i < playedAmplitudes.size(); i++) { - assertEquals(amplitudes[i % amplitudes.length], playedAmplitudes.get(i).intValue()); + assertEquals(amplitudes[i % amplitudes.length] / 255f, playedAmplitudes.get(i), 1e-5); } } @@ -292,7 +298,7 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); } @Test @@ -302,9 +308,10 @@ public class VibrationThreadTest { long vibrationId = 1; VibrationEffect fallback = VibrationEffect.createOneShot(10, 100); - VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_STRENGTH_STRONG, fallback); - VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); + Vibration vibration = createVibration(vibrationId, CombinedVibrationEffect.createSynced( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK))); + vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback); + VibrationThread thread = startThreadAndDispatcher(vibration); waitForCompletion(thread); verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); @@ -314,8 +321,8 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test @@ -331,7 +338,7 @@ public class VibrationThreadTest { verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED_UNSUPPORTED)); - assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty()); + assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); } @Test @@ -351,7 +358,10 @@ public class VibrationThreadTest { verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); - assertEquals(Arrays.asList(effect), mVibratorProviders.get(VIBRATOR_ID).getEffects()); + assertEquals(Arrays.asList( + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)), + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); } @Test @@ -368,7 +378,7 @@ public class VibrationThreadTest { verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED_UNSUPPORTED)); - assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty()); + assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); } @Test @@ -428,7 +438,7 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)), - mVibratorProviders.get(VIBRATOR_ID).getEffects()); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments()); } @Test @@ -454,10 +464,10 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating()); - VibrationEffect expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK); - assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffects()); + VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(3).getEffectSegments()); } @Test @@ -495,12 +505,16 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(4).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(2).getAmplitudes()); - assertEquals(Arrays.asList(expectedOneShot(20)), mVibratorProviders.get(3).getEffects()); - assertEquals(Arrays.asList(1, 2), mVibratorProviders.get(3).getAmplitudes()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(4).getEffects()); + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expectedOneShot(10)), + mVibratorProviders.get(2).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(20)), + mVibratorProviders.get(3).getEffectSegments()); + assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes()); + assertEquals(Arrays.asList( + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + mVibratorProviders.get(4).getEffectSegments()); } @Test @@ -540,11 +554,14 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating()); - assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects()); + assertEquals(Arrays.asList(expectedOneShot(10)), + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(Arrays.asList( + expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), + mVibratorProviders.get(2).getEffectSegments()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(3).getEffects()); + mVibratorProviders.get(3).getEffectSegments()); } @Test @@ -563,8 +580,9 @@ public class VibrationThreadTest { CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(composed); VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); - assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffects().isEmpty() - && !mVibratorProviders.get(2).getEffects().isEmpty(), thread, TEST_TIMEOUT_MILLIS)); + assertTrue(waitUntil(t -> !mVibratorProviders.get(1).getEffectSegments().isEmpty() + && !mVibratorProviders.get(2).getEffectSegments().isEmpty(), thread, + TEST_TIMEOUT_MILLIS)); thread.syncedVibrationComplete(); waitForCompletion(thread); @@ -574,8 +592,10 @@ public class VibrationThreadTest { verify(mThreadCallbacks, never()).cancelSyncedVibration(); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.FINISHED)); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects()); + VibrationEffectSegment expected = expectedPrimitive( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments()); } @Test @@ -634,10 +654,12 @@ public class VibrationThreadTest { verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId)); verify(mThreadCallbacks, never()).cancelSyncedVibration(); - assertEquals(Arrays.asList(expectedOneShot(10)), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(100), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList(expectedOneShot(5)), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(200), mVibratorProviders.get(2).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(10)), + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(5)), + mVibratorProviders.get(2).getEffectSegments()); + assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes()); } @Test @@ -704,12 +726,15 @@ public class VibrationThreadTest { assertFalse(thread.getVibrators().get(2).isVibrating()); assertFalse(thread.getVibrators().get(3).isVibrating()); - assertEquals(Arrays.asList(expectedOneShot(25)), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(expectedOneShot(80)), mVibratorProviders.get(2).getEffects()); - assertEquals(Arrays.asList(expectedOneShot(60)), mVibratorProviders.get(3).getEffects()); - assertEquals(Arrays.asList(1, 2, 3), mVibratorProviders.get(1).getAmplitudes()); - assertEquals(Arrays.asList(4, 5), mVibratorProviders.get(2).getAmplitudes()); - assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes()); + assertEquals(Arrays.asList(expectedOneShot(25)), + mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expectedOneShot(80)), + mVibratorProviders.get(2).getEffectSegments()); + assertEquals(Arrays.asList(expectedOneShot(60)), + mVibratorProviders.get(3).getEffectSegments()); + assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes()); + assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes()); } @LargeTest @@ -826,7 +851,7 @@ public class VibrationThreadTest { verify(mVibrationToken).linkToDeath(same(thread), eq(0)); verify(mVibrationToken).unlinkToDeath(same(thread), eq(0)); verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED)); - assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffects().isEmpty()); + assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); } @@ -843,12 +868,16 @@ public class VibrationThreadTest { private VibrationThread startThreadAndDispatcher(long vibrationId, CombinedVibrationEffect effect) { - VibrationThread thread = new VibrationThread(createVibration(vibrationId, effect), - createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks); + return startThreadAndDispatcher(createVibration(vibrationId, effect)); + } + + private VibrationThread startThreadAndDispatcher(Vibration vib) { + VibrationThread thread = new VibrationThread(vib, createVibratorControllers(), mWakeLock, + mIBatteryStatsMock, mThreadCallbacks); doAnswer(answer -> { thread.vibratorComplete(answer.getArgument(0)); return null; - }).when(mControllerCallbacks).onComplete(anyInt(), eq(vibrationId)); + }).when(mControllerCallbacks).onComplete(anyInt(), eq(vib.id)); mTestLooper.startAutoDispatch(); thread.start(); return thread; @@ -891,12 +920,21 @@ public class VibrationThreadTest { return array; } - private VibrationEffect expectedOneShot(long millis) { - return VibrationEffect.createOneShot(millis, VibrationEffect.DEFAULT_AMPLITUDE); + private VibrationEffectSegment expectedOneShot(long millis) { + return new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, /* frequency= */ 0, (int) millis); + } + + private VibrationEffectSegment expectedPrebaked(int effectId) { + return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); + } + + private VibrationEffectSegment expectedPrimitive(int primitiveId, float scale, int delay) { + return new PrimitiveSegment(primitiveId, scale, delay); } - private VibrationEffect expectedPrebaked(int effectId) { - return new VibrationEffect.Prebaked(effectId, false, - VibrationEffect.EFFECT_STRENGTH_MEDIUM); + private List<Float> expectedAmplitudes(int... amplitudes) { + return Arrays.stream(amplitudes) + .mapToObj(amplitude -> amplitude / 255f) + .collect(Collectors.toList()); } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java index bad3e4c2ed92..70ea219911b7 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java @@ -39,6 +39,9 @@ import android.os.IBinder; import android.os.IVibratorStateListener; import android.os.VibrationEffect; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -49,7 +52,6 @@ import com.android.internal.util.test.FakeSettingsProviderRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; @@ -161,9 +163,9 @@ public class VibratorControllerTest { @Test public void updateAlwaysOn_withCapability_enablesAlwaysOnEffect() { mockVibratorCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL); - VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked) - VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - createController().updateAlwaysOn(1, effect); + PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_MEDIUM); + createController().updateAlwaysOn(1, prebaked); verify(mNativeWrapperMock).alwaysOnEnable( eq(1L), eq((long) VibrationEffect.EFFECT_CLICK), @@ -179,9 +181,9 @@ public class VibratorControllerTest { @Test public void updateAlwaysOn_withoutCapability_ignoresEffect() { - VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked) - VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - createController().updateAlwaysOn(1, effect); + PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_MEDIUM); + createController().updateAlwaysOn(1, prebaked); verify(mNativeWrapperMock, never()).alwaysOnDisable(anyLong()); verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); @@ -201,9 +203,9 @@ public class VibratorControllerTest { when(mNativeWrapperMock.perform(anyLong(), anyLong(), anyLong())).thenReturn(10L); VibratorController controller = createController(); - VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked) - VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - assertEquals(10L, controller.on(effect, 11)); + PrebakedSegment prebaked = createPrebaked(VibrationEffect.EFFECT_CLICK, + VibrationEffect.EFFECT_STRENGTH_MEDIUM); + assertEquals(10L, controller.on(prebaked, 11)); assertTrue(controller.isVibrating()); verify(mNativeWrapperMock).perform(eq((long) VibrationEffect.EFFECT_CLICK), @@ -216,24 +218,25 @@ public class VibratorControllerTest { when(mNativeWrapperMock.compose(any(), anyLong())).thenReturn(15L); VibratorController controller = createController(); - VibrationEffect.Composed effect = (VibrationEffect.Composed) - VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .compose(); - assertEquals(15L, controller.on(effect, 12)); - - ArgumentCaptor<VibrationEffect.Composition.PrimitiveEffect[]> primitivesCaptor = - ArgumentCaptor.forClass(VibrationEffect.Composition.PrimitiveEffect[].class); + PrimitiveSegment[] primitives = new PrimitiveSegment[]{ + new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) + }; + assertEquals(15L, controller.on(primitives, 12)); assertTrue(controller.isVibrating()); - verify(mNativeWrapperMock).compose(primitivesCaptor.capture(), eq(12L)); - - // Check all primitive effect fields are passed down to the HAL. - assertEquals(1, primitivesCaptor.getValue().length); - VibrationEffect.Composition.PrimitiveEffect primitive = primitivesCaptor.getValue()[0]; - assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.id); - assertEquals(0.5f, primitive.scale, /* delta= */ 1e-2); - assertEquals(10, primitive.delay); + verify(mNativeWrapperMock).compose(eq(primitives), eq(12L)); + } + + @Test + public void on_withComposedPwle_ignoresEffect() { + VibratorController controller = createController(); + + RampSegment[] primitives = new RampSegment[]{ + new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1, + /* startFrequency= */ -1, /* endFrequency= */ 1, /* duration= */ 10) + }; + assertEquals(0L, controller.on(primitives, 12)); + assertFalse(controller.isVibrating()); } @Test @@ -286,4 +289,8 @@ public class VibratorControllerTest { private void mockVibratorCapabilities(int capabilities) { when(mNativeWrapperMock.getCapabilities()).thenReturn((long) capabilities); } + + private PrebakedSegment createPrebaked(int effectId, int effectStrength) { + return new PrebakedSegment(effectId, /* shouldFallback= */ false, effectStrength); + } } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index ce6639c6b4aa..12ced388d5f7 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -65,6 +65,8 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorInfo; import android.os.test.TestLooper; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.InputDevice; @@ -364,13 +366,13 @@ public class VibratorManagerServiceTest { assertTrue(createSystemReadyService().setAlwaysOnEffect( UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); - VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked( + PrebakedSegment expected = new PrebakedSegment( VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); // Only vibrators 1 and 3 have always-on capabilities. - assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expectedEffect); + assertEquals(mVibratorProviders.get(1).getAlwaysOnEffect(1), expected); assertNull(mVibratorProviders.get(2).getAlwaysOnEffect(1)); - assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expectedEffect); + assertEquals(mVibratorProviders.get(3).getAlwaysOnEffect(1), expected); } @Test @@ -388,10 +390,10 @@ public class VibratorManagerServiceTest { assertTrue(createSystemReadyService().setAlwaysOnEffect( UID, PACKAGE_NAME, 1, effect, ALARM_ATTRS)); - VibrationEffect.Prebaked expectedClick = new VibrationEffect.Prebaked( + PrebakedSegment expectedClick = new PrebakedSegment( VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); - VibrationEffect.Prebaked expectedTick = new VibrationEffect.Prebaked( + PrebakedSegment expectedTick = new PrebakedSegment( VibrationEffect.EFFECT_TICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); // Enables click on vibrator 1 and tick on vibrator 2 only. @@ -487,8 +489,9 @@ public class VibratorManagerServiceTest { vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS); assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); - assertEquals(2, mVibratorProviders.get(1).getEffects().size()); - assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes()); + assertEquals(2, mVibratorProviders.get(1).getEffectSegments().size()); + assertEquals(Arrays.asList(10 / 255f, 100 / 255f), + mVibratorProviders.get(1).getAmplitudes()); } @Test @@ -500,19 +503,19 @@ public class VibratorManagerServiceTest { mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS); vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, service, TEST_TIMEOUT_MILLIS)); mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3, service, TEST_TIMEOUT_MILLIS)); - assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes()); + assertEquals(Arrays.asList(2 / 255f, 3 / 255f, 4 / 255f), fakeVibrator.getAmplitudes()); } @Test @@ -579,7 +582,7 @@ public class VibratorManagerServiceTest { verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any()); // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), + assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, /* timeout= */ 50)); } @@ -640,8 +643,11 @@ public class VibratorManagerServiceTest { verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); verify(mNativeWrapperMock).triggerSynced(anyLong()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(1).getEffects()); - assertEquals(Arrays.asList(composed), mVibratorProviders.get(2).getEffects()); + + PrimitiveSegment expected = new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments()); + assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments()); } @Test @@ -665,7 +671,7 @@ public class VibratorManagerServiceTest { .compose()) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); @@ -689,7 +695,7 @@ public class VibratorManagerServiceTest { .addVibrator(2, VibrationEffect.createOneShot(10, 100)) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !fakeVibrator1.getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock, never()).prepareSynced(any()); @@ -709,7 +715,7 @@ public class VibratorManagerServiceTest { .addVibrator(2, VibrationEffect.createOneShot(10, 100)) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); @@ -730,7 +736,7 @@ public class VibratorManagerServiceTest { .addVibrator(2, VibrationEffect.createOneShot(10, 100)) .combine(); vibrate(service, effect, ALARM_ATTRS); - assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(), service, + assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); @@ -758,40 +764,38 @@ public class VibratorManagerServiceTest { vibrate(service, CombinedVibrationEffect.startSynced() .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .combine(), ALARM_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, service, TEST_TIMEOUT_MILLIS)); vibrate(service, CombinedVibrationEffect.startSequential() .addNext(1, VibrationEffect.createOneShot(20, 100)) .combine(), NOTIFICATION_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(), HAPTIC_FEEDBACK_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3, + assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 4, service, TEST_TIMEOUT_MILLIS)); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); - assertEquals(3, fakeVibrator.getEffects().size()); + assertEquals(4, fakeVibrator.getEffectSegments().size()); assertEquals(1, fakeVibrator.getAmplitudes().size()); // Alarm vibration is always VIBRATION_INTENSITY_HIGH. - VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false, - VibrationEffect.EFFECT_STRENGTH_STRONG); - assertEquals(expected, fakeVibrator.getEffects().get(0)); + PrebakedSegment expected = new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); + assertEquals(expected, fakeVibrator.getEffectSegments().get(0)); // Notification vibrations will be scaled with SCALE_VERY_HIGH. - assertTrue(150 < fakeVibrator.getAmplitudes().get(0)); + assertTrue(0.6 < fakeVibrator.getAmplitudes().get(0)); // Haptic feedback vibrations will be scaled with SCALE_LOW. - VibrationEffect.Composed played = - (VibrationEffect.Composed) fakeVibrator.getEffects().get(2); - assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale); - assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale); + assertTrue(0.5 < ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale()); + assertTrue(0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(3)).getScale()); // Ring vibrations have intensity OFF and are not played. } 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/servicestests/utils-mockito/com/android/server/testutils/OWNERS b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS new file mode 100644 index 000000000000..d825dfd7cf00 --- /dev/null +++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pm/OWNERS diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 5462f47e3a4c..ff881748cfea 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -1688,8 +1688,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { @Override public boolean matches(VibrationEffect actual) { - if (actual instanceof VibrationEffect.Waveform && - ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) { + if (actual instanceof VibrationEffect.Composed + && ((VibrationEffect.Composed) actual).getRepeatIndex() == mRepeatIndex) { return true; } // All non-waveform effects are essentially one shots. diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index aa9feeaaf959..55ebe11e4ca8 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -900,10 +900,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testDefaultAssistant_overrideDefault() { - final int userId = 0; + final int userId = mContext.getUserId(); final String testComponent = "package/class"; final List<UserInfo> userInfos = new ArrayList<>(); - userInfos.add(new UserInfo(0, "", 0)); + userInfos.add(new UserInfo(userId, "", 0)); final ArraySet<ComponentName> validAssistants = new ArraySet<>(); validAssistants.add(ComponentName.unflattenFromString(testComponent)); when(mActivityManager.isLowRamDevice()).thenReturn(false); @@ -2346,7 +2346,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(mTestNotificationChannel); reset(mListeners); - mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel); + mBinderService.updateNotificationChannelForPackage(PKG, mUid, mTestNotificationChannel); verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG), eq(Process.myUserHandle()), eq(mTestNotificationChannel), eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); @@ -2882,7 +2882,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testSetListenerAccessForUser() throws Exception { - UserHandle user = UserHandle.of(10); + UserHandle user = UserHandle.of(mContext.getUserId() + 10); ComponentName c = ComponentName.unflattenFromString("package/Component"); mBinderService.setNotificationListenerAccessGrantedForUser( c, user.getIdentifier(), true, true); @@ -2899,20 +2899,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testSetAssistantAccessForUser() throws Exception { - UserHandle user = UserHandle.of(10); - List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 10; + ui.id = mContext.getUserId() + 10; + UserHandle user = UserHandle.of(ui.id); + List<UserInfo> uis = new ArrayList<>(); uis.add(ui); ComponentName c = ComponentName.unflattenFromString("package/Component"); - when(mUm.getEnabledProfiles(10)).thenReturn(uis); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); mBinderService.setNotificationAssistantAccessGrantedForUser(c, user.getIdentifier(), true); verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any()); verify(mAssistants, times(1)).setPackageOrComponentEnabled( c.flattenToString(), user.getIdentifier(), true, true, true); - verify(mAssistants).setUserSet(10, true); + verify(mAssistants).setUserSet(ui.id, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( c.flattenToString(), user.getIdentifier(), false, true); verify(mListeners, never()).setPackageOrComponentEnabled( @@ -2921,7 +2921,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testGetAssistantAllowedForUser() throws Exception { - UserHandle user = UserHandle.of(10); + UserHandle user = UserHandle.of(mContext.getUserId() + 10); try { mBinderService.getAllowedNotificationAssistantForUser(user.getIdentifier()); } catch (IllegalStateException e) { @@ -2941,12 +2941,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throw e; } } - verify(mAssistants, times(1)).getAllowedComponents(0); + verify(mAssistants, times(1)).getAllowedComponents(mContext.getUserId()); } @Test public void testSetDndAccessForUser() throws Exception { - UserHandle user = UserHandle.of(10); + UserHandle user = UserHandle.of(mContext.getUserId() + 10); ComponentName c = ComponentName.unflattenFromString("package/Component"); mBinderService.setNotificationPolicyAccessGrantedForUser( c.getPackageName(), user.getIdentifier(), true); @@ -2966,9 +2966,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationListenerAccessGranted(c, true, true); verify(mListeners, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, true, true, true); + c.flattenToString(), mContext.getUserId(), true, true, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, false, true, true); + c.flattenToString(), mContext.getUserId(), false, true, true); verify(mAssistants, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); } @@ -2977,7 +2977,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testSetAssistantAccess() throws Exception { List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 0; + ui.id = mContext.getUserId(); uis.add(ui); when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); ComponentName c = ComponentName.unflattenFromString("package/Component"); @@ -2985,9 +2985,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationAssistantAccessGranted(c, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, true, true, true); + c.flattenToString(), ui.id, true, true, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, false, true); + c.flattenToString(), ui.id, false, true); verify(mListeners, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean()); } @@ -2996,10 +2996,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testSetAssistantAccess_multiProfile() throws Exception { List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 0; + ui.id = mContext.getUserId(); uis.add(ui); UserInfo ui10 = new UserInfo(); - ui10.id = 10; + ui10.id = mContext.getUserId() + 10; uis.add(ui10); when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); ComponentName c = ComponentName.unflattenFromString("package/Component"); @@ -3007,13 +3007,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationAssistantAccessGranted(c, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, true, true, true); + c.flattenToString(), ui.id, true, true, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 10, true, true, true); + c.flattenToString(), ui10.id, true, true, true); + verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, false, true); + c.flattenToString(), ui.id, false, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 10, false, true); + c.flattenToString(), ui10.id, false, true); verify(mListeners, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean()); } @@ -3026,16 +3027,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList); List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 0; + ui.id = mContext.getUserId(); uis.add(ui); when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); mBinderService.setNotificationAssistantAccessGranted(null, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, true, false, true); + c.flattenToString(), ui.id, true, false, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, false, false); + c.flattenToString(), ui.id, false, false); verify(mListeners, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean()); } @@ -3044,21 +3045,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testSetAssistantAccessForUser_nullWithAllowedAssistant() throws Exception { List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 10; + ui.id = mContext.getUserId() + 10; uis.add(ui); UserHandle user = ui.getUserHandle(); ArrayList<ComponentName> componentList = new ArrayList<>(); ComponentName c = ComponentName.unflattenFromString("package/Component"); componentList.add(c); when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList); - when(mUm.getEnabledProfiles(10)).thenReturn(uis); + when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); mBinderService.setNotificationAssistantAccessGrantedForUser( null, user.getIdentifier(), true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( c.flattenToString(), user.getIdentifier(), true, false, true); - verify(mAssistants).setUserSet(10, true); + verify(mAssistants).setUserSet(ui.id, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( c.flattenToString(), user.getIdentifier(), false, false); verify(mListeners, never()).setPackageOrComponentEnabled( @@ -3070,10 +3071,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws Exception { List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 0; + ui.id = mContext.getUserId(); uis.add(ui); UserInfo ui10 = new UserInfo(); - ui10.id = 10; + ui10.id = mContext.getUserId() + 10; uis.add(ui10); UserHandle user = ui.getUserHandle(); ArrayList<ComponentName> componentList = new ArrayList<>(); @@ -3089,8 +3090,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { c.flattenToString(), user.getIdentifier(), true, false, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( c.flattenToString(), ui10.id, true, false, true); - verify(mAssistants).setUserSet(0, true); - verify(mAssistants).setUserSet(10, true); + verify(mAssistants).setUserSet(ui.id, true); + verify(mAssistants).setUserSet(ui10.id, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( c.flattenToString(), user.getIdentifier(), false, false); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( @@ -3106,7 +3107,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.getPackageName(), 0, true, true); + c.getPackageName(), mContext.getUserId(), true, true); verify(mAssistants, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); verify(mListeners, never()).setPackageOrComponentEnabled( @@ -3133,7 +3134,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ComponentName c = ComponentName.unflattenFromString("package/Component"); List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 0; + ui.id = mContext.getUserId(); uis.add(ui); when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); @@ -3168,9 +3169,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setNotificationListenerAccessGranted(c, true, true); verify(mListeners, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, true, true, true); + c.flattenToString(), mContext.getUserId(), true, true, true); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, false, true, true); + c.flattenToString(), mContext.getUserId(), false, true, true); verify(mAssistants, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); } @@ -3182,7 +3183,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ComponentName c = ComponentName.unflattenFromString("package/Component"); List<UserInfo> uis = new ArrayList<>(); UserInfo ui = new UserInfo(); - ui.id = 0; + ui.id = mContext.getUserId(); uis.add(ui); when(mUm.getEnabledProfiles(ui.id)).thenReturn(uis); @@ -3191,9 +3192,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mListeners, never()).setPackageOrComponentEnabled( anyString(), anyInt(), anyBoolean(), anyBoolean()); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, false, true); + c.flattenToString(), ui.id, false, true); verify(mAssistants, times(1)).setPackageOrComponentEnabled( - c.flattenToString(), 0, true, true, true); + c.flattenToString(), ui.id, true, true, true); } @Test @@ -3207,7 +3208,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mListeners, never()).setPackageOrComponentEnabled( anyString(), anyInt(), anyBoolean(), anyBoolean()); verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( - c.getPackageName(), 0, true, true); + c.getPackageName(), mContext.getUserId(), true, true); verify(mAssistants, never()).setPackageOrComponentEnabled( any(), anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java deleted file mode 100644 index 1700707d9866..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; -import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; -import static com.android.server.wm.WindowContainer.POSITION_TOP; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link DisplayContent} class. - * - * Build/Install/Run: - * atest WmTests:ActivityDisplayTests - */ -@SmallTest -@Presubmit -@RunWith(WindowTestRunner.class) -// TODO(b/144248496): Merge to DisplayContentTests -public class ActivityDisplayTests extends WindowTestsBase { - - @Test - public void testLastFocusedStackIsUpdatedWhenMovingStack() { - // Create a stack at bottom. - final TaskDisplayArea taskDisplayAreas = - mRootWindowContainer.getDefaultDisplay().getDefaultTaskDisplayArea(); - final Task stack = - new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build(); - final Task prevFocusedStack = taskDisplayAreas.getFocusedRootTask(); - - stack.moveToFront("moveStackToFront"); - // After moving the stack to front, the previous focused should be the last focused. - assertTrue(stack.isFocusedRootTaskOnDisplay()); - assertEquals(prevFocusedStack, taskDisplayAreas.getLastFocusedRootTask()); - - stack.moveToBack("moveStackToBack", null /* task */); - // After moving the stack to back, the stack should be the last focused. - assertEquals(stack, taskDisplayAreas.getLastFocusedRootTask()); - } - - /** - * This test simulates the picture-in-picture menu activity launches an activity to fullscreen - * stack. The fullscreen stack should be the top focused for resuming correctly. - */ - @Test - public void testFullscreenStackCanBeFocusedWhenFocusablePinnedStackExists() { - // Create a pinned stack and move to front. - final Task pinnedStack = mRootWindowContainer.getDefaultTaskDisplayArea() - .createRootTask(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); - final Task pinnedTask = new TaskBuilder(mAtm.mTaskSupervisor) - .setParentTask(pinnedStack).build(); - new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE) - .setTask(pinnedTask).build(); - pinnedStack.moveToFront("movePinnedStackToFront"); - - // The focused stack should be the pinned stack. - assertTrue(pinnedStack.isFocusedRootTaskOnDisplay()); - - // Create a fullscreen stack and move to front. - final Task fullscreenStack = createFullscreenStackWithSimpleActivityAt( - mRootWindowContainer.getDefaultDisplay()); - fullscreenStack.moveToFront("moveFullscreenStackToFront"); - - // The focused stack should be the fullscreen stack. - assertTrue(fullscreenStack.isFocusedRootTaskOnDisplay()); - } - - /** - * Test {@link TaskDisplayArea#mPreferredTopFocusableRootTask} will be cleared when - * the stack is removed or moved to back, and the focused stack will be according to z-order. - */ - @Test - public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() { - // Create a display which only contains 2 stacks. - final DisplayContent display = addNewDisplayContentAt(POSITION_TOP); - final Task stack1 = createFullscreenStackWithSimpleActivityAt(display); - final Task stack2 = createFullscreenStackWithSimpleActivityAt(display); - - // Put stack1 and stack2 on top. - stack1.moveToFront("moveStack1ToFront"); - stack2.moveToFront("moveStack2ToFront"); - assertTrue(stack2.isFocusedRootTaskOnDisplay()); - - // Stack1 should be focused after moving stack2 to back. - stack2.moveToBack("moveStack2ToBack", null /* task */); - assertTrue(stack1.isFocusedRootTaskOnDisplay()); - - // Stack2 should be focused after removing stack1. - stack1.getDisplayArea().removeRootTask(stack1); - assertTrue(stack2.isFocusedRootTaskOnDisplay()); - } - - /** - * Verifies {@link DisplayContent#remove} should not resume home stack on the removing display. - */ - @Test - public void testNotResumeHomeStackOnRemovingDisplay() { - // Create a display which supports system decoration and allows reparenting stacks to - // another display when the display is removed. - final DisplayContent display = new TestDisplayContent.Builder( - mAtm, 1000, 1500).setSystemDecorations(true).build(); - doReturn(false).when(display).shouldDestroyContentOnRemove(); - - // Put home stack on the display. - final Task homeStack = new TaskBuilder(mSupervisor) - .setDisplay(display).setActivityType(ACTIVITY_TYPE_HOME).build(); - - // Put a finishing standard activity which will be reparented. - final Task stack = createFullscreenStackWithSimpleActivityAt(display); - stack.topRunningActivity().makeFinishingLocked(); - - clearInvocations(homeStack); - display.remove(); - - // The removed display should have no focused stack and its home stack should never resume. - assertNull(display.getFocusedRootTask()); - verify(homeStack, never()).resumeTopActivityUncheckedLocked(any(), any()); - } - - private Task createFullscreenStackWithSimpleActivityAt(DisplayContent display) { - final Task fullscreenStack = display.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); - final Task fullscreenTask = new TaskBuilder(mAtm.mTaskSupervisor) - .setParentTask(fullscreenStack).build(); - new ActivityBuilder(mAtm).setTask(fullscreenTask).build(); - return fullscreenStack; - } - - /** - * Verifies the correct activity is returned when querying the top running activity. - */ - @Test - public void testTopRunningActivity() { - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final KeyguardController keyguard = mSupervisor.getKeyguardController(); - final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); - final ActivityRecord activity = stack.getTopNonFinishingActivity(); - - // Create empty stack on top. - final Task emptyStack = new TaskBuilder(mSupervisor).build(); - - // Make sure the top running activity is not affected when keyguard is not locked. - assertTopRunningActivity(activity, display); - - // Check to make sure activity not reported when it cannot show on lock and lock is on. - doReturn(true).when(keyguard).isKeyguardLocked(); - assertEquals(activity, display.topRunningActivity()); - assertNull(display.topRunningActivity(true /* considerKeyguardState */)); - - // Move stack with activity to top. - stack.moveToFront("testStackToFront"); - assertEquals(stack, display.getFocusedRootTask()); - assertEquals(activity, display.topRunningActivity()); - assertNull(display.topRunningActivity(true /* considerKeyguardState */)); - - // Add activity that should be shown on the keyguard. - final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mAtm) - .setTask(stack) - .setActivityFlags(FLAG_SHOW_WHEN_LOCKED) - .build(); - - // Ensure the show when locked activity is returned. - assertTopRunningActivity(showWhenLockedActivity, display); - - // Move empty stack to front. The running activity in focusable stack which below the - // empty stack should be returned. - emptyStack.moveToFront("emptyStackToFront"); - assertEquals(stack, display.getFocusedRootTask()); - assertTopRunningActivity(showWhenLockedActivity, display); - } - - private static void assertTopRunningActivity(ActivityRecord top, DisplayContent display) { - assertEquals(top, display.topRunningActivity()); - assertEquals(top, display.topRunningActivity(true /* considerKeyguardState */)); - } - - /** - * This test enforces that alwaysOnTop stack is placed at proper position. - */ - @Test - public void testAlwaysOnTopStackLocation() { - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - final Task alwaysOnTopStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM, - ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(alwaysOnTopStack).build(); - alwaysOnTopStack.setAlwaysOnTop(true); - taskDisplayArea.positionChildAt(POSITION_TOP, alwaysOnTopStack, - false /* includingParents */); - assertTrue(alwaysOnTopStack.isAlwaysOnTop()); - // Ensure always on top state is synced to the children of the stack. - assertTrue(alwaysOnTopStack.getTopNonFinishingActivity().isAlwaysOnTop()); - assertEquals(alwaysOnTopStack, taskDisplayArea.getTopRootTask()); - - final Task pinnedStack = taskDisplayArea.createRootTask( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(pinnedStack, taskDisplayArea.getRootPinnedTask()); - assertEquals(pinnedStack, taskDisplayArea.getTopRootTask()); - - final Task anotherAlwaysOnTopStack = taskDisplayArea.createRootTask( - WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); - anotherAlwaysOnTopStack.setAlwaysOnTop(true); - taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopStack, - false /* includingParents */); - assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - int topPosition = taskDisplayArea.getRootTaskCount() - 1; - // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the - // existing alwaysOnTop stack. - assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack)); - - final Task nonAlwaysOnTopStack = taskDisplayArea.createRootTask( - WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(taskDisplayArea, nonAlwaysOnTopStack.getDisplayArea()); - topPosition = taskDisplayArea.getRootTaskCount() - 1; - // Ensure the non-alwaysOnTop stack is put below the three alwaysOnTop stacks, but above the - // existing other non-alwaysOnTop stacks. - assertEquals(topPosition - 3, getTaskIndexOf(taskDisplayArea, nonAlwaysOnTopStack)); - - anotherAlwaysOnTopStack.setAlwaysOnTop(false); - taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopStack, - false /* includingParents */); - assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); - // Ensure, when always on top is turned off for a stack, the stack is put just below all - // other always on top stacks. - assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack)); - anotherAlwaysOnTopStack.setAlwaysOnTop(true); - - // Ensure always on top state changes properly when windowing mode changes. - anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack)); - anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopStack)); - - final Task dreamStack = taskDisplayArea.createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, true /* onTop */); - assertEquals(taskDisplayArea, dreamStack.getDisplayArea()); - assertTrue(dreamStack.isAlwaysOnTop()); - topPosition = taskDisplayArea.getRootTaskCount() - 1; - // Ensure dream shows above all activities, including PiP - assertEquals(dreamStack, taskDisplayArea.getTopRootTask()); - assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, pinnedStack)); - - final Task assistStack = taskDisplayArea.createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - assertEquals(taskDisplayArea, assistStack.getDisplayArea()); - assertFalse(assistStack.isAlwaysOnTop()); - topPosition = taskDisplayArea.getRootTaskCount() - 1; - - // Ensure Assistant shows as a non-always-on-top activity when config_assistantOnTopOfDream - // is false and on top of everything when true. - final boolean isAssistantOnTop = mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_assistantOnTopOfDream); - assertEquals(isAssistantOnTop ? topPosition : topPosition - 4, - getTaskIndexOf(taskDisplayArea, assistStack)); - } - - @Test - public void testRemoveRootTaskInWindowingModes() { - removeStackTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes( - WINDOWING_MODE_FULLSCREEN)); - } - - @Test - public void testRemoveStackWithActivityTypes() { - removeStackTests(() -> mRootWindowContainer.removeRootTasksWithActivityTypes( - ACTIVITY_TYPE_STANDARD)); - } - - private void removeStackTests(Runnable runnable) { - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - final Task stack1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, ON_TOP); - final Task stack2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, ON_TOP); - final Task stack3 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, ON_TOP); - final Task stack4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, ON_TOP); - final Task task1 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack1).build(); - final Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack2).build(); - final Task task3 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack3).build(); - final Task task4 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(stack4).build(); - - // Reordering stacks while removing stacks. - doAnswer(invocation -> { - taskDisplayArea.positionChildAt(POSITION_TOP, stack3, false /*includingParents*/); - return true; - }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any()); - - // Removing stacks from the display while removing stacks. - doAnswer(invocation -> { - taskDisplayArea.removeRootTask(stack2); - return true; - }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any()); - - runnable.run(); - verify(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any()); - verify(mSupervisor).removeTask(eq(task3), anyBoolean(), anyBoolean(), any()); - verify(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any()); - verify(mSupervisor).removeTask(eq(task1), anyBoolean(), anyBoolean(), any()); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index ce5fc4021eac..678defe36566 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -405,7 +405,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { public void testGetAnimationTargets_taskContainsMultipleTasks() { // [DisplayContent] - [Task] -+- [Task1] - [ActivityRecord1] (opening, invisible) // +- [Task2] - [ActivityRecord2] (closing, visible) - final Task parentTask = createTaskStackOnDisplay(mDisplayContent); + final Task parentTask = createTask(mDisplayContent); final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask); activity1.setVisible(false); activity1.mVisibleRequested = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 83aca5e2d482..c3279bf05737 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -177,13 +177,13 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - public void testCleanAppTransitionWhenTaskStackReparent() { + public void testCleanAppTransitionWhenRootTaskReparent() { // Create 2 displays & presume both display the state is ON for ready to display & animate. final DisplayContent dc1 = createNewDisplay(Display.STATE_ON); final DisplayContent dc2 = createNewDisplay(Display.STATE_ON); - final Task stack1 = createTaskStackOnDisplay(dc1); - final Task task1 = createTaskInStack(stack1, 0 /* userId */); + final Task rootTask1 = createTask(dc1); + final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */); final ActivityRecord activity1 = createNonAttachedActivityRecord(dc1); task1.addChild(activity1, 0); @@ -198,8 +198,8 @@ public class AppTransitionTests extends WindowTestsBase { dc1.mOpeningApps.add(activity1); assertTrue(dc1.mOpeningApps.size() > 0); - // Move stack to another display. - stack1.reparent(dc2.getDefaultTaskDisplayArea(), true); + // Move root task to another display. + rootTask1.reparent(dc2.getDefaultTaskDisplayArea(), true); // Verify if token are cleared from both pending transition list in former display. assertFalse(dc1.mOpeningApps.contains(activity1)); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index adf8fa461c06..0afd39f1ed01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -16,10 +16,12 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -65,6 +67,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.same; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; @@ -330,21 +333,6 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(startingWin, imeTarget); } - @UseTestDisplay(addAllCommonWindows = true) - @Test - public void testComputeImeTarget_placeImeToTheTargetRoot() { - ActivityRecord activity = createActivityRecord(mDisplayContent); - - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity, - "startingWin"); - startingWin.setHasSurface(true); - assertTrue(startingWin.canBeImeTarget()); - DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); - - WindowState imeTarget = mDisplayContent.computeImeTarget(true /* updateImeTarget */); - verify(imeTarget.getRootDisplayArea()).placeImeContainer(imeContainer); - } - @Test public void testUpdateImeParent_forceUpdateRelativeLayer() { final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer(); @@ -365,29 +353,29 @@ public class DisplayContentTests extends WindowTestsBase { } /** - * This tests stack movement between displays and proper stack's, task's and app token's display - * container references updates. + * This tests root task movement between displays and proper root task's, task's and app token's + * display container references updates. */ @Test - public void testMoveStackBetweenDisplays() { + public void testMoveRootTaskBetweenDisplays() { // Create a second display. final DisplayContent dc = createNewDisplay(); - // Add stack with activity. - final Task stack = createTaskStackOnDisplay(dc); - assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId()); - assertEquals(dc, stack.getDisplayContent()); + // Add root task with activity. + final Task rootTask = createTask(dc); + assertEquals(dc.getDisplayId(), rootTask.getDisplayContent().getDisplayId()); + assertEquals(dc, rootTask.getDisplayContent()); - final Task task = createTaskInStack(stack, 0 /* userId */); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createNonAttachedActivityRecord(dc); task.addChild(activity, 0); assertEquals(dc, task.getDisplayContent()); assertEquals(dc, activity.getDisplayContent()); - // Move stack to first display. - stack.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */); - assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); - assertEquals(mDisplayContent, stack.getDisplayContent()); + // Move root task to first display. + rootTask.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */); + assertEquals(mDisplayContent.getDisplayId(), rootTask.getDisplayContent().getDisplayId()); + assertEquals(mDisplayContent, rootTask.getDisplayContent()); assertEquals(mDisplayContent, task.getDisplayContent()); assertEquals(mDisplayContent, activity.getDisplayContent()); } @@ -439,7 +427,7 @@ public class DisplayContentTests extends WindowTestsBase { } /** - * Tests tapping on a stack in different display results in window gaining focus. + * Tests tapping on a root task in different display results in window gaining focus. */ @Test public void testInputEventBringsCorrectDisplayInFocus() { @@ -447,16 +435,16 @@ public class DisplayContentTests extends WindowTestsBase { // Create a second display final DisplayContent dc1 = createNewDisplay(); - // Add stack with activity. - final Task stack0 = createTaskStackOnDisplay(dc0); - final Task task0 = createTaskInStack(stack0, 0 /* userId */); + // Add root task with activity. + final Task rootTask0 = createTask(dc0); + final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */); final ActivityRecord activity = createNonAttachedActivityRecord(dc0); task0.addChild(activity, 0); dc0.configureDisplayPolicy(); assertNotNull(dc0.mTapDetector); - final Task stack1 = createTaskStackOnDisplay(dc1); - final Task task1 = createTaskInStack(stack1, 0 /* userId */); + final Task rootTask1 = createTask(dc1); + final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */); final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0); task1.addChild(activity1, 0); dc1.configureDisplayPolicy(); @@ -904,13 +892,13 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent newDisplay = createNewDisplay(); final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); - final Task stack = mDisplayContent.getTopRootTask(); - final ActivityRecord activity = stack.topRunningActivity(); + final Task rootTask = mDisplayContent.getTopRootTask(); + final ActivityRecord activity = rootTask.topRunningActivity(); doReturn(true).when(activity).shouldBeVisibleUnchecked(); final WindowState appWin1 = createWindow(null, TYPE_APPLICATION, newDisplay, "appWin1"); - final Task stack1 = newDisplay.getTopRootTask(); - final ActivityRecord activity1 = stack1.topRunningActivity(); + final Task rootTask1 = newDisplay.getTopRootTask(); + final ActivityRecord activity1 = rootTask1.topRunningActivity(); doReturn(true).when(activity1).shouldBeVisibleUnchecked(); appWin.setHasSurface(true); appWin1.setHasSurface(true); @@ -949,28 +937,28 @@ public class DisplayContentTests extends WindowTestsBase { dc.getDisplayRotation().setFixedToUserRotation( IWindowManager.FIXED_TO_USER_ROTATION_DISABLED); - final Task stack = new TaskBuilder(mSupervisor) + final Task rootTask = new TaskBuilder(mSupervisor) .setDisplay(dc) .setCreateActivity(true) .build(); - doReturn(true).when(stack).isVisible(); + doReturn(true).when(rootTask).isVisible(); - final Task freeformStack = new TaskBuilder(mSupervisor) + final Task freeformRootTask = new TaskBuilder(mSupervisor) .setDisplay(dc) .setCreateActivity(true) .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); - doReturn(true).when(freeformStack).isVisible(); - freeformStack.getTopChild().setBounds(100, 100, 300, 400); + doReturn(true).when(freeformRootTask).isVisible(); + freeformRootTask.getTopChild().setBounds(100, 100, 300, 400); assertTrue(dc.getDefaultTaskDisplayArea().isRootTaskVisible(WINDOWING_MODE_FREEFORM)); - freeformStack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - stack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT); + freeformRootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + rootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT); assertEquals(SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation()); - stack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - freeformStack.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT); + rootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + freeformRootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT); assertEquals(SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); } @@ -1698,66 +1686,6 @@ public class DisplayContentTests extends WindowTestsBase { } @Test - public void testGetOrCreateRootHomeTask_defaultDisplay() { - TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea(); - - // Remove the current home stack if it exists so a new one can be created below. - Task homeTask = defaultTaskDisplayArea.getRootHomeTask(); - if (homeTask != null) { - defaultTaskDisplayArea.removeChild(homeTask); - } - assertNull(defaultTaskDisplayArea.getRootHomeTask()); - - assertNotNull(defaultTaskDisplayArea.getOrCreateRootHomeTask()); - } - - @Test - public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() { - DisplayContent display = createNewDisplay(); - doReturn(true).when(display).supportsSystemDecorations(); - - // Remove the current home stack if it exists so a new one can be created below. - TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); - Task homeTask = taskDisplayArea.getRootHomeTask(); - if (homeTask != null) { - taskDisplayArea.removeChild(homeTask); - } - assertNull(taskDisplayArea.getRootHomeTask()); - - assertNotNull(taskDisplayArea.getOrCreateRootHomeTask()); - } - - @Test - public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() { - DisplayContent display = createNewDisplay(); - TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); - doReturn(false).when(display).supportsSystemDecorations(); - - assertNull(taskDisplayArea.getRootHomeTask()); - assertNull(taskDisplayArea.getOrCreateRootHomeTask()); - } - - @Test - public void testGetOrCreateRootHomeTask_untrustedDisplay() { - DisplayContent display = createNewDisplay(); - TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); - doReturn(false).when(display).isTrusted(); - - assertNull(taskDisplayArea.getRootHomeTask()); - assertNull(taskDisplayArea.getOrCreateRootHomeTask()); - } - - @Test - public void testGetOrCreateRootHomeTask_dontMoveToTop() { - DisplayContent display = createNewDisplay(); - display.mDontMoveToTop = true; - TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); - - assertNull(taskDisplayArea.getRootHomeTask()); - assertNull(taskDisplayArea.getOrCreateRootHomeTask()); - } - - @Test public void testValidWindowingLayer() { final SurfaceControl windowingLayer = mDisplayContent.getWindowingLayer(); assertNotNull(windowingLayer); @@ -1776,8 +1704,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testFindScrollCaptureTargetWindow_behindWindow() { DisplayContent display = createNewDisplay(); - Task stack = createTaskStackOnDisplay(display); - Task task = createTaskInStack(stack, 0 /* userId */); + Task rootTask = createTask(display); + Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window"); WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); @@ -1789,8 +1717,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testFindScrollCaptureTargetWindow_cantReceiveKeys() { DisplayContent display = createNewDisplay(); - Task stack = createTaskStackOnDisplay(display); - Task task = createTaskInStack(stack, 0 /* userId */); + Task rootTask = createTask(display); + Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window"); WindowState invisible = createWindow(null, TYPE_APPLICATION, "invisible"); invisible.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false @@ -1803,8 +1731,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testFindScrollCaptureTargetWindow_taskId() { DisplayContent display = createNewDisplay(); - Task stack = createTaskStackOnDisplay(display); - Task task = createTaskInStack(stack, 0 /* userId */); + Task rootTask = createTask(display); + Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window"); WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); @@ -1815,8 +1743,8 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testFindScrollCaptureTargetWindow_taskIdCantReceiveKeys() { DisplayContent display = createNewDisplay(); - Task stack = createTaskStackOnDisplay(display); - Task task = createTaskInStack(stack, 0 /* userId */); + Task rootTask = createTask(display); + Task task = createTaskInRootTask(rootTask, 0 /* userId */); WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window"); window.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot"); @@ -1843,7 +1771,7 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testSetWindowingModeAtomicallyUpdatesWindoingModeAndDisplayWindowingMode() { final DisplayContent dc = createNewDisplay(); - final Task stack = new TaskBuilder(mSupervisor) + final Task rootTask = new TaskBuilder(mSupervisor) .setDisplay(dc) .build(); doAnswer(invocation -> { @@ -1852,7 +1780,7 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(config.windowConfiguration.getWindowingMode(), config.windowConfiguration.getDisplayWindowingMode()); return null; - }).when(stack).onConfigurationChanged(any()); + }).when(rootTask).onConfigurationChanged(any()); dc.setWindowingMode(WINDOWING_MODE_FREEFORM); dc.setWindowingMode(WINDOWING_MODE_FULLSCREEN); } @@ -2087,6 +2015,130 @@ public class DisplayContentTests extends WindowTestsBase { 0 /* delta */); } + /** + * Verifies {@link DisplayContent#remove} should not resume home root task on the removing + * display. + */ + @Test + public void testNotResumeHomeRootTaskOnRemovingDisplay() { + // Create a display which supports system decoration and allows reparenting root tasks to + // another display when the display is removed. + final DisplayContent display = new TestDisplayContent.Builder( + mAtm, 1000, 1500).setSystemDecorations(true).build(); + doReturn(false).when(display).shouldDestroyContentOnRemove(); + + // Put home root task on the display. + final Task homeRootTask = new TaskBuilder(mSupervisor) + .setDisplay(display).setActivityType(ACTIVITY_TYPE_HOME).build(); + + // Put a finishing standard activity which will be reparented. + final Task rootTask = createTaskWithActivity(display.getDefaultTaskDisplayArea(), + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true /* twoLevelTask */); + rootTask.topRunningActivity().makeFinishingLocked(); + + clearInvocations(homeRootTask); + display.remove(); + + // The removed display should have no focused root task and its home root task should never + // resume. + assertNull(display.getFocusedRootTask()); + verify(homeRootTask, never()).resumeTopActivityUncheckedLocked(any(), any()); + } + + /** + * Verifies the correct activity is returned when querying the top running activity. + */ + @Test + public void testTopRunningActivity() { + final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); + final KeyguardController keyguard = mSupervisor.getKeyguardController(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord activity = rootTask.getTopNonFinishingActivity(); + + // Create empty root task on top. + final Task emptyRootTask = new TaskBuilder(mSupervisor).build(); + + // Make sure the top running activity is not affected when keyguard is not locked. + assertTopRunningActivity(activity, display); + + // Check to make sure activity not reported when it cannot show on lock and lock is on. + doReturn(true).when(keyguard).isKeyguardLocked(); + assertEquals(activity, display.topRunningActivity()); + assertNull(display.topRunningActivity(true /* considerKeyguardState */)); + + // Move root task with activity to top. + rootTask.moveToFront("testRootTaskToFront"); + assertEquals(rootTask, display.getFocusedRootTask()); + assertEquals(activity, display.topRunningActivity()); + assertNull(display.topRunningActivity(true /* considerKeyguardState */)); + + // Add activity that should be shown on the keyguard. + final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mAtm) + .setTask(rootTask) + .setActivityFlags(FLAG_SHOW_WHEN_LOCKED) + .build(); + + // Ensure the show when locked activity is returned. + assertTopRunningActivity(showWhenLockedActivity, display); + + // Move empty root task to front. The running activity in focusable root task which below + // the empty root task should be returned. + emptyRootTask.moveToFront("emptyRootTaskToFront"); + assertEquals(rootTask, display.getFocusedRootTask()); + assertTopRunningActivity(showWhenLockedActivity, display); + } + + private static void assertTopRunningActivity(ActivityRecord top, DisplayContent display) { + assertEquals(top, display.topRunningActivity()); + assertEquals(top, display.topRunningActivity(true /* considerKeyguardState */)); + } + + @Test + public void testRemoveRootTaskInWindowingModes() { + removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes( + WINDOWING_MODE_FULLSCREEN)); + } + + @Test + public void testRemoveRootTaskWithActivityTypes() { + removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksWithActivityTypes( + ACTIVITY_TYPE_STANDARD)); + } + + private void removeRootTaskTests(Runnable runnable) { + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, ON_TOP); + final Task rootTask2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, ON_TOP); + final Task rootTask3 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, ON_TOP); + final Task rootTask4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, ON_TOP); + final Task task1 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask1).build(); + final Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask2).build(); + final Task task3 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask3).build(); + final Task task4 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask4).build(); + + // Reordering root tasks while removing root tasks. + doAnswer(invocation -> { + taskDisplayArea.positionChildAt(POSITION_TOP, rootTask3, false /*includingParents*/); + return true; + }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any()); + + // Removing root tasks from the display while removing root tasks. + doAnswer(invocation -> { + taskDisplayArea.removeRootTask(rootTask2); + return true; + }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any()); + + runnable.run(); + verify(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any()); + verify(mSupervisor).removeTask(eq(task3), anyBoolean(), anyBoolean(), any()); + verify(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any()); + verify(mSupervisor).removeTask(eq(task1), anyBoolean(), anyBoolean(), any()); + } + private boolean isOptionsPanelAtRight(int displayId) { return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT; } diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 4e2697ab64f8..1bddd7b4ef64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -17,8 +17,6 @@ package com.android.server.wm; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; @@ -124,9 +122,8 @@ public class DragDropControllerTests extends WindowTestsBase { */ private WindowState createDropTargetWindow(String name, int ownerId) { final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent); - final Task stack = createTaskStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task task = createTaskInStack(stack, ownerId); + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, ownerId); task.addChild(activity, 0); // Use a new TestIWindow so we don't collect events for other windows diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index e9c356d6c6c4..e9907c1fd1a5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -311,6 +311,41 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test + public void testPlaceImeContainer_hidesImeWhenParentChanges() { + setupImeWindow(); + final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); + final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); + final WindowState firstActivityWin = + createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, + "firstActivityWin"); + spyOn(firstActivityWin); + final WindowState secondActivityWin = + createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity, + "secondActivityWin"); + spyOn(secondActivityWin); + + // firstActivityWin should be the target + doReturn(true).when(firstActivityWin).canBeImeTarget(); + doReturn(false).when(secondActivityWin).canBeImeTarget(); + + WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); + assertThat(imeTarget).isEqualTo(firstActivityWin); + verify(mFirstRoot).placeImeContainer(imeContainer); + + // secondActivityWin should be the target + doReturn(false).when(firstActivityWin).canBeImeTarget(); + doReturn(true).when(secondActivityWin).canBeImeTarget(); + + spyOn(mDisplay.mInputMethodWindow); + imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); + + assertThat(imeTarget).isEqualTo(secondActivityWin); + verify(mSecondRoot).placeImeContainer(imeContainer); + // verify hide() was called on InputMethodWindow. + verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */); + } + + @Test public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 925b6f9601be..6ffdb099695d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -219,7 +219,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testAddTasksNoMultiple_expectNoTrim() { + public void testAddDocumentTasksNoMultiple_expectNoTrim() { // Add same non-multiple-task document tasks will remove the task (to re-add it) but not // trim it Task documentTask1 = createDocumentTask(".DocumentTask1"); @@ -262,7 +262,7 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testAddTasksMultipleDocumentTasks_expectNoTrim() { + public void testAddMultipleDocumentTasks_expectNoTrim() { // Add same multiple-task document tasks does not trim the first tasks Task documentTask1 = createDocumentTask(".DocumentTask1", FLAG_ACTIVITY_MULTIPLE_TASK); @@ -278,14 +278,14 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - public void testAddTasksMultipleTasks_expectRemovedNoTrim() { - // Add multiple same-affinity non-document tasks, ensure that it removes the other task, - // but that it does not trim it + public void testAddTasks_expectRemovedNoTrim() { + // Add multiple same-affinity non-document tasks, ensure that it removes, but does not trim + // the other task Task task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .setFlags(FLAG_ACTIVITY_NEW_TASK) .build(); Task task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .setFlags(FLAG_ACTIVITY_NEW_TASK) .build(); mRecentTasks.add(task1); assertThat(mCallbacksRecorder.mAdded).hasSize(1); @@ -302,6 +302,29 @@ public class RecentTasksTest extends WindowTestsBase { } @Test + public void testAddMultipleTasks_expectNotRemoved() { + // Add multiple same-affinity non-document tasks with MULTIPLE_TASK, ensure that it does not + // remove the other task + Task task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .build(); + Task task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .build(); + mRecentTasks.add(task1); + assertThat(mCallbacksRecorder.mAdded).hasSize(1); + assertThat(mCallbacksRecorder.mAdded).contains(task1); + assertThat(mCallbacksRecorder.mTrimmed).isEmpty(); + assertThat(mCallbacksRecorder.mRemoved).isEmpty(); + mCallbacksRecorder.clear(); + mRecentTasks.add(task2); + assertThat(mCallbacksRecorder.mAdded).hasSize(1); + assertThat(mCallbacksRecorder.mAdded).contains(task2); + assertThat(mCallbacksRecorder.mTrimmed).isEmpty(); + assertThat(mCallbacksRecorder.mRemoved).isEmpty(); + } + + @Test public void testAddTasksDifferentStacks_expectNoRemove() { // Adding the same task with different activity types should not trigger removal of the // other task diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index f97e79444d59..7d137bcb2cff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -488,9 +488,9 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testIsAnimatingByRecents() { final ActivityRecord homeActivity = createHomeActivity(); - final Task rootTask = createTaskStackOnDisplay(mDefaultDisplay); - final Task childTask = createTaskInStack(rootTask, 0 /* userId */); - final Task leafTask = createTaskInStack(childTask, 0 /* userId */); + final Task rootTask = createTask(mDefaultDisplay); + final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */); + final Task leafTask = createTaskInRootTask(childTask, 0 /* userId */); spyOn(leafTask); doReturn(true).when(leafTask).isVisible(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java deleted file mode 100644 index 8388f2a6c6fa..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ /dev/null @@ -1,953 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Display.TYPE_VIRTUAL; -import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; -import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE; -import static com.android.server.wm.Task.ActivityState.STOPPED; -import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.contains; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.refEq; - -import android.app.ActivityOptions; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.ResolveInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.platform.test.annotations.Presubmit; -import android.util.MergedConfiguration; -import android.util.Pair; - -import androidx.test.filters.MediumTest; - -import com.android.internal.app.ResolverActivity; -import com.android.server.wm.Task.ActivityState; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; - -/** - * Tests for the {@link RootWindowContainer} class. - * - * Build/Install/Run: - * atest WmTests:RootActivityContainerTests - */ -@MediumTest -@Presubmit -@RunWith(WindowTestRunner.class) -public class RootActivityContainerTests extends WindowTestsBase { - - @Before - public void setUp() throws Exception { - doNothing().when(mAtm).updateSleepIfNeededLocked(); - } - - /** - * This test ensures that we do not try to restore a task based off an invalid task id. We - * should expect {@code null} to be returned in this case. - */ - @Test - public void testRestoringInvalidTask() { - mRootWindowContainer.getDefaultDisplay().removeAllTasks(); - Task task = mRootWindowContainer.anyTaskForId(0 /*taskId*/, - MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */); - assertNull(task); - } - - /** - * This test ensures that an existing task in the pinned stack is moved to the fullscreen - * activity stack when a new task is added. - */ - @Test - public void testReplacingTaskInPinnedStack() { - Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord firstActivity = new ActivityBuilder(mAtm) - .setTask(fullscreenTask).build(); - final ActivityRecord secondActivity = new ActivityBuilder(mAtm) - .setTask(fullscreenTask).build(); - - fullscreenTask.moveToFront("testReplacingTaskInPinnedStack"); - - // Ensure full screen stack has both tasks. - ensureStackPlacement(fullscreenTask, firstActivity, secondActivity); - - // Move first activity to pinned stack. - mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove"); - - final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea(); - Task pinnedStack = taskDisplayArea.getRootPinnedTask(); - // Ensure a task has moved over. - ensureStackPlacement(pinnedStack, firstActivity); - ensureStackPlacement(fullscreenTask, secondActivity); - - // Move second activity to pinned stack. - mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove"); - - // Need to get stacks again as a new instance might have been created. - pinnedStack = taskDisplayArea.getRootPinnedTask(); - fullscreenTask = taskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD); - // Ensure stacks have swapped tasks. - ensureStackPlacement(pinnedStack, secondActivity); - ensureStackPlacement(fullscreenTask, firstActivity); - } - - @Test - public void testMovingBottomMostStackActivityToPinnedStack() { - final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord firstActivity = new ActivityBuilder(mAtm) - .setTask(fullscreenTask).build(); - final Task task = firstActivity.getTask(); - - final ActivityRecord secondActivity = new ActivityBuilder(mAtm) - .setTask(fullscreenTask).build(); - - fullscreenTask.moveTaskToBack(task); - - // Ensure full screen stack has both tasks. - ensureStackPlacement(fullscreenTask, firstActivity, secondActivity); - assertEquals(task.getTopMostActivity(), secondActivity); - firstActivity.setState(STOPPED, "testMovingBottomMostStackActivityToPinnedStack"); - - - // Move first activity to pinned stack. - mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove"); - - assertTrue(firstActivity.mRequestForceTransition); - } - - private static void ensureStackPlacement(Task task, ActivityRecord... activities) { - final ArrayList<ActivityRecord> taskActivities = new ArrayList<>(); - - task.forAllActivities((Consumer<ActivityRecord>) taskActivities::add, false); - - assertEquals("Expecting " + Arrays.deepToString(activities) + " got " + taskActivities, - taskActivities.size(), activities != null ? activities.length : 0); - - if (activities == null) { - return; - } - - for (ActivityRecord activity : activities) { - assertTrue(taskActivities.contains(activity)); - } - } - - @Test - public void testApplySleepTokens() { - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final KeyguardController keyguard = mSupervisor.getKeyguardController(); - final Task stack = new TaskBuilder(mSupervisor) - .setDisplay(display) - .setOnTop(false) - .build(); - - // Make sure we wake and resume in the case the display is turning on and the keyguard is - // not showing. - verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, - false /* displayShouldSleep */, true /* isFocusedStack */, - false /* keyguardShowing */, true /* expectWakeFromSleep */, - true /* expectResumeTopActivity */); - - // Make sure we wake and don't resume when the display is turning on and the keyguard is - // showing. - verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, - false /* displayShouldSleep */, true /* isFocusedStack */, - true /* keyguardShowing */, true /* expectWakeFromSleep */, - false /* expectResumeTopActivity */); - - // Make sure we wake and don't resume when the display is turning on and the keyguard is - // not showing as unfocused. - verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, - false /* displayShouldSleep */, false /* isFocusedStack */, - false /* keyguardShowing */, true /* expectWakeFromSleep */, - false /* expectResumeTopActivity */); - - // Should not do anything if the display state hasn't changed. - verifySleepTokenBehavior(display, keyguard, stack, false /*displaySleeping*/, - false /* displayShouldSleep */, true /* isFocusedStack */, - false /* keyguardShowing */, false /* expectWakeFromSleep */, - false /* expectResumeTopActivity */); - } - - private void verifySleepTokenBehavior(DisplayContent display, KeyguardController keyguard, - Task stack, boolean displaySleeping, boolean displayShouldSleep, - boolean isFocusedStack, boolean keyguardShowing, boolean expectWakeFromSleep, - boolean expectResumeTopActivity) { - reset(stack); - - doReturn(displayShouldSleep).when(display).shouldSleep(); - doReturn(displaySleeping).when(display).isSleeping(); - doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt()); - - doReturn(isFocusedStack).when(stack).isFocusedRootTaskOnDisplay(); - doReturn(isFocusedStack ? stack : null).when(display).getFocusedRootTask(); - TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea(); - doReturn(isFocusedStack ? stack : null).when(defaultTaskDisplayArea).getFocusedRootTask(); - mRootWindowContainer.applySleepTokens(true); - verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); - verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked( - null /* target */, null /* targetOptions */); - } - - @Test - public void testAwakeFromSleepingWithAppConfiguration() { - final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); - final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); - activity.moveFocusableActivityToTop("test"); - assertTrue(activity.getRootTask().isFocusedRootTaskOnDisplay()); - ActivityRecordTests.setRotatedScreenOrientationSilently(activity); - - final Configuration rotatedConfig = new Configuration(); - display.computeScreenConfiguration(rotatedConfig, display.getDisplayRotation() - .rotationForOrientation(activity.getOrientation(), display.getRotation())); - assertNotEquals(activity.getConfiguration().orientation, rotatedConfig.orientation); - // Assume the activity was shown in different orientation. For example, the top activity is - // landscape and the portrait lockscreen is shown. - activity.setLastReportedConfiguration( - new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig)); - activity.setState(ActivityState.STOPPED, "sleep"); - - display.setIsSleeping(true); - doReturn(false).when(display).shouldSleep(); - // Allow to resume when awaking. - setBooted(mAtm); - mRootWindowContainer.applySleepTokens(true); - - // The display orientation should be changed by the activity so there is no relaunch. - verify(activity, never()).relaunchActivityLocked(anyBoolean()); - assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation); - } - - /** - * Verifies that removal of activity with task and stack is done correctly. - */ - @Test - public void testRemovingStackOnAppCrash() { - final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer - .getDefaultTaskDisplayArea(); - final int originalStackCount = defaultTaskDisplayArea.getRootTaskCount(); - final Task stack = defaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(stack).build(); - - assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getRootTaskCount()); - - // Let's pretend that the app has crashed. - firstActivity.app.setThread(null); - mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test"); - - // Verify that the stack was removed. - assertEquals(originalStackCount, defaultTaskDisplayArea.getRootTaskCount()); - } - - /** - * Verifies that removal of activities with task and stack is done correctly when there are - * several task display areas. - */ - @Test - public void testRemovingStackOnAppCrash_multipleDisplayAreas() { - final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer - .getDefaultTaskDisplayArea(); - final int originalStackCount = defaultTaskDisplayArea.getRootTaskCount(); - final Task stack = defaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(stack).build(); - assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getRootTaskCount()); - - final DisplayContent dc = defaultTaskDisplayArea.getDisplayContent(); - final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( - dc, mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST); - final Task secondStack = secondTaskDisplayArea.createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - new ActivityBuilder(mAtm).setTask(secondStack).setUseProcess(firstActivity.app).build(); - assertEquals(1, secondTaskDisplayArea.getRootTaskCount()); - - // Let's pretend that the app has crashed. - firstActivity.app.setThread(null); - mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test"); - - // Verify that the stacks were removed. - assertEquals(originalStackCount, defaultTaskDisplayArea.getRootTaskCount()); - assertEquals(0, secondTaskDisplayArea.getRootTaskCount()); - } - - @Test - public void testFocusability() { - final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer - .getDefaultTaskDisplayArea(); - final Task stack = defaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build(); - - // Created stacks are focusable by default. - assertTrue(stack.isTopActivityFocusable()); - assertTrue(activity.isFocusable()); - - // If the stack is made unfocusable, its activities should inherit that. - stack.setFocusable(false); - assertFalse(stack.isTopActivityFocusable()); - assertFalse(activity.isFocusable()); - - final Task pinnedStack = defaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm) - .setTask(pinnedStack).build(); - - // We should not be focusable when in pinned mode - assertFalse(pinnedStack.isTopActivityFocusable()); - assertFalse(pinnedActivity.isFocusable()); - - // Add flag forcing focusability. - pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE; - - // Task with FLAG_ALWAYS_FOCUSABLE should be focusable. - assertTrue(pinnedStack.isTopActivityFocusable()); - assertTrue(pinnedActivity.isFocusable()); - } - - /** - * Verify that split-screen primary stack will be chosen if activity is launched that targets - * split-screen secondary, but a matching existing instance is found on top of split-screen - * primary stack. - */ - @Test - public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() { - // Create primary split-screen stack with a task and an activity. - final Task primaryStack = mRootWindowContainer.getDefaultTaskDisplayArea() - .createRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task task = new TaskBuilder(mSupervisor).setParentTask(primaryStack).build(); - final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build(); - - // Find a launch stack for the top activity in split-screen primary, while requesting - // split-screen secondary. - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); - final Task result = - mRootWindowContainer.getLaunchRootTask(r, options, task, true /* onTop */); - - // Assert that the primary stack is returned. - assertEquals(primaryStack, result); - } - - /** - * Verify that home stack would be moved to front when the top activity is Recents. - */ - @Test - public void testFindTaskToMoveToFrontWhenRecentsOnTop() { - // Create stack/task on default display. - final Task targetStack = new TaskBuilder(mSupervisor) - .setCreateActivity(true) - .setOnTop(false) - .build(); - final Task targetTask = targetStack.getBottomMostTask(); - - // Create Recents on top of the display. - final Task stack = new TaskBuilder(mSupervisor) - .setCreateActivity(true) - .setActivityType(ACTIVITY_TYPE_RECENTS) - .build(); - - final String reason = "findTaskToMoveToFront"; - mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, - false); - - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - verify(taskDisplayArea).moveHomeRootTaskToFront(contains(reason)); - } - - /** - * Verify that home stack won't be moved to front if the top activity on other display is - * Recents. - */ - @Test - public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() { - // Create tasks on default display. - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - final Task targetRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, false /* onTop */); - final Task targetTask = new TaskBuilder(mSupervisor).setParentTask(targetRootTask).build(); - - // Create Recents on secondary display. - final TestDisplayContent secondDisplay = addNewDisplayContentAt( - DisplayContent.POSITION_TOP); - final Task rootTask = secondDisplay.getDefaultTaskDisplayArea() - .createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); - new ActivityBuilder(mAtm).setTask(rootTask).build(); - - final String reason = "findTaskToMoveToFront"; - mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, - false); - - verify(taskDisplayArea, never()).moveHomeRootTaskToFront(contains(reason)); - } - - /** - * Verify if a stack is not at the topmost position, it should be able to resume its activity if - * the stack is the top focused. - */ - @Test - public void testResumeActivityWhenNonTopmostStackIsTopFocused() { - // Create a root task at bottom. - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, false /* onTop */)); - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build(); - taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/); - - // Assume the task is not at the topmost position (e.g. behind always-on-top stacks) but it - // is the current top focused task. - assertFalse(rootTask.isTopRootTaskInDisplayArea()); - doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); - - // Use the task as target to resume. - mRootWindowContainer.resumeFocusedTasksTopActivities( - rootTask, activity, null /* targetOptions */); - - // Verify the target task should resume its activity. - verify(rootTask, times(1)).resumeTopActivityUncheckedLocked( - eq(activity), eq(null /* targetOptions */)); - } - - /** - * Verify that home activity will be started on a display even if another display has a - * focusable activity. - */ - @Test - public void testResumeFocusedStacksStartsHomeActivity_NoActivities() { - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - taskDisplayArea.getRootHomeTask().removeIfPossible(); - taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); - - doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any()); - - mAtm.setBooted(true); - - // Trigger resume on all displays - mRootWindowContainer.resumeFocusedTasksTopActivities(); - - // Verify that home activity was started on the default display - verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea)); - } - - /** - * Verify that home activity will be started on a display even if another display has a - * focusable activity. - */ - @Test - public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() { - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - taskDisplayArea.getRootHomeTask().removeIfPossible(); - taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); - - // Create an activity on secondary display. - final TestDisplayContent secondDisplay = addNewDisplayContentAt( - DisplayContent.POSITION_TOP); - final Task rootTask = secondDisplay.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - new ActivityBuilder(mAtm).setTask(rootTask).build(); - - doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any()); - - mAtm.setBooted(true); - - // Trigger resume on all displays - mRootWindowContainer.resumeFocusedTasksTopActivities(); - - // Verify that home activity was started on the default display - verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea)); - } - - /** - * Verify that a lingering transition is being executed in case the activity to be resumed is - * already resumed - */ - @Test - public void testResumeActivityLingeringTransition() { - // Create a root task at top. - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, false /* onTop */)); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(rootTask).setOnTop(true).build(); - activity.setState(ActivityState.RESUMED, "test"); - - // Assume the task is at the topmost position - assertTrue(rootTask.isTopRootTaskInDisplayArea()); - - // Use the task as target to resume. - mRootWindowContainer.resumeFocusedTasksTopActivities(); - - // Verify the lingering app transition is being executed because it's already resumed - verify(rootTask, times(1)).executeAppTransition(any()); - } - - @Test - public void testResumeActivityLingeringTransition_notExecuted() { - // Create a root task at bottom. - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, false /* onTop */)); - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setTask(rootTask).setOnTop(true).build(); - activity.setState(ActivityState.RESUMED, "test"); - taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/); - - // Assume the task is at the topmost position - assertFalse(rootTask.isTopRootTaskInDisplayArea()); - doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); - - // Use the task as target to resume. - mRootWindowContainer.resumeFocusedTasksTopActivities(); - - // Verify the lingering app transition is being executed because it's already resumed - verify(rootTask, never()).executeAppTransition(any()); - } - - /** - * Tests that home activities can be started on the displays that supports system decorations. - */ - @Test - public void testStartHomeOnAllDisplays() { - mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); - mockResolveSecondaryHomeActivity(); - - // Create secondary displays. - final TestDisplayContent secondDisplay = - new TestDisplayContent.Builder(mAtm, 1000, 1500) - .setSystemDecorations(true).build(); - - doReturn(true).when(mRootWindowContainer) - .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean()); - doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), - anyBoolean()); - - mRootWindowContainer.startHomeOnAllDisplays(0, "testStartHome"); - - assertTrue(mRootWindowContainer.getDefaultDisplay().getTopRootTask().isActivityTypeHome()); - assertNotNull(secondDisplay.getTopRootTask()); - assertTrue(secondDisplay.getTopRootTask().isActivityTypeHome()); - } - - /** - * Tests that home activities won't be started before booting when display added. - */ - @Test - public void testNotStartHomeBeforeBoot() { - final int displayId = 1; - final boolean isBooting = mAtm.mAmInternal.isBooting(); - final boolean isBooted = mAtm.mAmInternal.isBooted(); - try { - mAtm.mAmInternal.setBooting(false); - mAtm.mAmInternal.setBooted(false); - mRootWindowContainer.onDisplayAdded(displayId); - verify(mRootWindowContainer, never()).startHomeOnDisplay(anyInt(), any(), anyInt()); - } finally { - mAtm.mAmInternal.setBooting(isBooting); - mAtm.mAmInternal.setBooted(isBooted); - } - } - - /** - * Tests whether home can be started if being instrumented. - */ - @Test - public void testCanStartHomeWhenInstrumented() { - final ActivityInfo info = new ActivityInfo(); - info.applicationInfo = new ApplicationInfo(); - final WindowProcessController app = mock(WindowProcessController.class); - doReturn(app).when(mAtm).getProcessController(any(), anyInt()); - - // Can not start home if we don't want to start home while home is being instrumented. - doReturn(true).when(app).isInstrumenting(); - final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer - .getDefaultTaskDisplayArea(); - assertFalse(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, - false /* allowInstrumenting*/)); - - // Can start home for other cases. - assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, - true /* allowInstrumenting*/)); - - doReturn(false).when(app).isInstrumenting(); - assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, - false /* allowInstrumenting*/)); - assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, - true /* allowInstrumenting*/)); - } - - /** - * Tests that secondary home activity should not be resolved if device is still locked. - */ - @Test - public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() { - // Create secondary displays. - final TestDisplayContent secondDisplay = - new TestDisplayContent.Builder(mAtm, 1000, 1500) - .setSystemDecorations(true).build(); - - // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false. - final int currentUser = mRootWindowContainer.mCurrentUser; - mRootWindowContainer.mCurrentUser = -1; - - mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome", - secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */); - - try { - verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any()); - } finally { - mRootWindowContainer.mCurrentUser = currentUser; - } - } - - /** - * Tests that secondary home activity should not be resolved if display does not support system - * decorations. - */ - @Test - public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() { - // Create secondary displays. - final TestDisplayContent secondDisplay = - new TestDisplayContent.Builder(mAtm, 1000, 1500) - .setSystemDecorations(false).build(); - - mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome", - secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */); - - verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any()); - } - - /** - * Tests that when starting {@link #ResolverActivity} for home, it should use the standard - * activity type (in a new stack) so the order of back stack won't be broken. - */ - @Test - public void testStartResolverActivityForHome() { - final ActivityInfo info = new ActivityInfo(); - info.applicationInfo = new ApplicationInfo(); - info.applicationInfo.packageName = "android"; - info.name = ResolverActivity.class.getName(); - doReturn(info).when(mRootWindowContainer).resolveHomeActivity(anyInt(), any()); - - mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "test", DEFAULT_DISPLAY); - final ActivityRecord resolverActivity = mRootWindowContainer.topRunningActivity(); - - assertEquals(info, resolverActivity.info); - assertEquals(ACTIVITY_TYPE_STANDARD, resolverActivity.getRootTask().getActivityType()); - } - - /** - * Tests that secondary home should be selected if primary home not set. - */ - @Test - public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSet() { - // Setup: primary home not set. - final Intent primaryHomeIntent = mAtm.getHomeIntent(); - final ActivityInfo aInfoPrimary = new ActivityInfo(); - aInfoPrimary.name = ResolverActivity.class.getName(); - doReturn(aInfoPrimary).when(mRootWindowContainer).resolveHomeActivity(anyInt(), - refEq(primaryHomeIntent)); - // Setup: set secondary home. - mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */); - - // Run the test. - final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer - .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); - final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/); - assertEquals(aInfoSecondary.name, resolvedInfo.first.name); - assertEquals(aInfoSecondary.applicationInfo.packageName, - resolvedInfo.first.applicationInfo.packageName); - } - - /** - * Tests that the default secondary home activity is always picked when it is in forced by - * config_useSystemProvidedLauncherForSecondary. - */ - @Test - public void testResolveSecondaryHomeActivityForced() { - // SetUp: set primary home. - mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); - // SetUp: set secondary home and force it. - mockResolveHomeActivity(false /* primaryHome */, true /* forceSystemProvided */); - final Intent secondaryHomeIntent = - mAtm.getSecondaryHomeIntent(null /* preferredPackage */); - final List<ResolveInfo> resolutions = new ArrayList<>(); - final ResolveInfo resolveInfo = new ResolveInfo(); - final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/); - resolveInfo.activityInfo = aInfoSecondary; - resolutions.add(resolveInfo); - doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), - refEq(secondaryHomeIntent)); - doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), - anyBoolean()); - - // Run the test. - final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer - .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); - assertEquals(aInfoSecondary.name, resolvedInfo.first.name); - assertEquals(aInfoSecondary.applicationInfo.packageName, - resolvedInfo.first.applicationInfo.packageName); - } - - /** - * Tests that secondary home should be selected if primary home not support secondary displays - * or there is no matched activity in the same package as selected primary home. - */ - @Test - public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSupportMultiDisplay() { - // Setup: there is no matched activity in the same package as selected primary home. - mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); - final List<ResolveInfo> resolutions = new ArrayList<>(); - doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any()); - // Setup: set secondary home. - mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */); - - // Run the test. - final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer - .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); - final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/); - assertEquals(aInfoSecondary.name, resolvedInfo.first.name); - assertEquals(aInfoSecondary.applicationInfo.packageName, - resolvedInfo.first.applicationInfo.packageName); - } - /** - * Tests that primary home activity should be selected if it already support secondary displays. - */ - @Test - public void testResolveSecondaryHomeActivityWhenPrimaryHomeSupportMultiDisplay() { - // SetUp: set primary home. - mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); - // SetUp: put primary home info on 2nd item - final List<ResolveInfo> resolutions = new ArrayList<>(); - final ResolveInfo infoFake1 = new ResolveInfo(); - infoFake1.activityInfo = new ActivityInfo(); - infoFake1.activityInfo.name = "fakeActivity1"; - infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); - infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; - final ResolveInfo infoFake2 = new ResolveInfo(); - final ActivityInfo aInfoPrimary = getFakeHomeActivityInfo(true /* primaryHome */); - infoFake2.activityInfo = aInfoPrimary; - resolutions.add(infoFake1); - resolutions.add(infoFake2); - doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any()); - - doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), - anyBoolean()); - - // Run the test. - final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer - .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); - assertEquals(aInfoPrimary.name, resolvedInfo.first.name); - assertEquals(aInfoPrimary.applicationInfo.packageName, - resolvedInfo.first.applicationInfo.packageName); - } - - /** - * Tests that the first one that matches should be selected if there are multiple activities. - */ - @Test - public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() { - // SetUp: set primary home. - mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); - // Setup: prepare two eligible activity info. - final List<ResolveInfo> resolutions = new ArrayList<>(); - final ResolveInfo infoFake1 = new ResolveInfo(); - infoFake1.activityInfo = new ActivityInfo(); - infoFake1.activityInfo.name = "fakeActivity1"; - infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); - infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; - final ResolveInfo infoFake2 = new ResolveInfo(); - infoFake2.activityInfo = new ActivityInfo(); - infoFake2.activityInfo.name = "fakeActivity2"; - infoFake2.activityInfo.applicationInfo = new ApplicationInfo(); - infoFake2.activityInfo.applicationInfo.packageName = "fakePackage2"; - resolutions.add(infoFake1); - resolutions.add(infoFake2); - doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any()); - - doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), - anyBoolean()); - - // Use the first one of matched activities in the same package as selected primary home. - final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer - .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); - - assertEquals(infoFake1.activityInfo.applicationInfo.packageName, - resolvedInfo.first.applicationInfo.packageName); - assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name); - } - - /** - * Test that {@link RootWindowContainer#getLaunchRootTask} with the real caller id will get the - * expected stack when requesting the activity launch on the secondary display. - */ - @Test - public void testGetLaunchStackWithRealCallerId() { - // Create a non-system owned virtual display. - final TestDisplayContent secondaryDisplay = - new TestDisplayContent.Builder(mAtm, 1000, 1500) - .setType(TYPE_VIRTUAL).setOwnerUid(100).build(); - - // Create an activity with specify the original launch pid / uid. - final ActivityRecord r = new ActivityBuilder(mAtm).setLaunchedFromPid(200) - .setLaunchedFromUid(200).build(); - - // Simulate ActivityStarter to find a launch stack for requesting the activity to launch - // on the secondary display with realCallerId. - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchDisplayId(secondaryDisplay.mDisplayId); - options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); - doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId, - 300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info); - final Task result = mRootWindowContainer.getLaunchRootTask(r, options, - null /* task */, true /* onTop */, null, 300 /* test realCallerPid */, - 300 /* test realCallerUid */); - - // Assert that the stack is returned as expected. - assertNotNull(result); - assertEquals("The display ID of the stack should same as secondary display ", - secondaryDisplay.mDisplayId, result.getDisplayId()); - } - - @Test - public void testGetValidLaunchStackOnDisplayWithCandidateRootTask() { - // Create a root task with an activity on secondary display. - final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mAtm, 300, - 600).build(); - final Task task = new TaskBuilder(mSupervisor) - .setDisplay(secondaryDisplay).build(); - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); - - // Make sure the root task is valid and can be reused on default display. - final Task stack = mRootWindowContainer.getValidLaunchRootTaskInTaskDisplayArea( - mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task, - null /* options */, null /* launchParams */); - assertEquals(task, stack); - } - - @Test - public void testSwitchUser_missingHomeRootTask() { - final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(fullscreenTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); - - final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); - Task homeStack = taskDisplayArea.getRootHomeTask(); - if (homeStack != null) { - homeStack.removeImmediately(); - } - assertNull(taskDisplayArea.getRootHomeTask()); - - int currentUser = mRootWindowContainer.mCurrentUser; - int otherUser = currentUser + 1; - - mRootWindowContainer.switchUser(otherUser, null); - - assertNotNull(taskDisplayArea.getRootHomeTask()); - assertEquals(taskDisplayArea.getTopRootTask(), taskDisplayArea.getRootHomeTask()); - } - - /** - * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity - * info for test cases. - * - * @param primaryHome Indicate to use primary home intent as parameter, otherwise, use - * secondary home intent. - * @param forceSystemProvided Indicate to force using system provided home activity. - */ - private void mockResolveHomeActivity(boolean primaryHome, boolean forceSystemProvided) { - ActivityInfo targetActivityInfo = getFakeHomeActivityInfo(primaryHome); - Intent targetIntent; - if (primaryHome) { - targetIntent = mAtm.getHomeIntent(); - } else { - Resources resources = mContext.getResources(); - spyOn(resources); - doReturn(targetActivityInfo.applicationInfo.packageName).when(resources).getString( - com.android.internal.R.string.config_secondaryHomePackage); - doReturn(forceSystemProvided).when(resources).getBoolean( - com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary); - targetIntent = mAtm.getSecondaryHomeIntent(null /* preferredPackage */); - } - doReturn(targetActivityInfo).when(mRootWindowContainer).resolveHomeActivity(anyInt(), - refEq(targetIntent)); - } - - /** - * Mock {@link RootWindowContainer#resolveSecondaryHomeActivity} for returning consistent - * activity info for test cases. - */ - private void mockResolveSecondaryHomeActivity() { - final Intent secondaryHomeIntent = mAtm - .getSecondaryHomeIntent(null /* preferredPackage */); - final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false); - doReturn(Pair.create(aInfoSecondary, secondaryHomeIntent)).when(mRootWindowContainer) - .resolveSecondaryHomeActivity(anyInt(), any()); - } - - private ActivityInfo getFakeHomeActivityInfo(boolean primaryHome) { - final ActivityInfo aInfo = new ActivityInfo(); - aInfo.name = primaryHome ? "fakeHomeActivity" : "fakeSecondaryHomeActivity"; - aInfo.applicationInfo = new ApplicationInfo(); - aInfo.applicationInfo.packageName = - primaryHome ? "fakeHomePackage" : "fakeSecondaryHomePackage"; - return aInfo; - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index 3a7954b3a5ce..748622b810d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2016 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. @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.server.wm; @@ -27,12 +27,14 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; @@ -49,6 +51,8 @@ import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE; import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE; import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove; +import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; +import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -56,26 +60,30 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import android.app.ActivityManager; import android.app.IApplicationThread; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.pm.ActivityInfo; +import android.graphics.Rect; import android.os.Binder; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; -import com.android.server.wm.TaskDisplayArea.OnRootTaskOrderChangedListener; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,15 +92,16 @@ import java.util.ArrayList; import java.util.function.Consumer; /** - * Tests for the {@link ActivityStack} class. + * Tests for the root {@link Task} behavior. * * Build/Install/Run: - * atest WmTests:ActivityStackTests + * atest WmTests:RootTaskTests */ @SmallTest @Presubmit @RunWith(WindowTestRunner.class) -public class ActivityStackTests extends WindowTestsBase { +public class RootTaskTests extends WindowTestsBase { + private TaskDisplayArea mDefaultTaskDisplayArea; @Before @@ -101,6 +110,172 @@ public class ActivityStackTests extends WindowTestsBase { } @Test + public void testRootTaskPositionChildAt() { + final Task rootTask = createTask(mDisplayContent); + final Task task1 = createTaskInRootTask(rootTask, 0 /* userId */); + final Task task2 = createTaskInRootTask(rootTask, 1 /* userId */); + + // Current user root task should be moved to top. + rootTask.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */); + assertEquals(rootTask.mChildren.get(0), task2); + assertEquals(rootTask.mChildren.get(1), task1); + + // Non-current user won't be moved to top. + rootTask.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */); + assertEquals(rootTask.mChildren.get(0), task2); + assertEquals(rootTask.mChildren.get(1), task1); + + // Non-leaf task should be moved to top regardless of the user id. + createTaskInRootTask(task2, 0 /* userId */); + createTaskInRootTask(task2, 1 /* userId */); + rootTask.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */); + assertEquals(rootTask.mChildren.get(0), task1); + assertEquals(rootTask.mChildren.get(1), task2); + } + + @Test + public void testClosingAppDifferentTaskOrientation() { + final ActivityRecord activity1 = createActivityRecord(mDisplayContent); + activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + final ActivityRecord activity2 = createActivityRecord(mDisplayContent); + activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); + + final WindowContainer parent = activity1.getTask().getParent(); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation()); + mDisplayContent.mClosingApps.add(activity2); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parent.getOrientation()); + } + + @Test + public void testMoveTaskToBackDifferentTaskOrientation() { + final ActivityRecord activity1 = createActivityRecord(mDisplayContent); + activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + final ActivityRecord activity2 = createActivityRecord(mDisplayContent); + activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); + + final WindowContainer parent = activity1.getTask().getParent(); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation()); + } + + @Test + public void testRootTaskRemoveImmediately() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + assertEquals(rootTask, task.getRootTask()); + + // Remove root task and check if its child is also removed. + rootTask.removeImmediately(); + assertNull(rootTask.getDisplayContent()); + assertNull(task.getParent()); + } + + @Test + public void testRemoveContainer() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + + assertNotNull(rootTask); + assertNotNull(task); + rootTask.removeIfPossible(); + // Assert that the container was removed. + assertNull(rootTask.getParent()); + assertEquals(0, rootTask.getChildCount()); + assertNull(rootTask.getDisplayContent()); + assertNull(task.getDisplayContent()); + assertNull(task.getParent()); + } + + @Test + public void testRemoveContainer_deferRemoval() { + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); + + // Root task removal is deferred if one of its child is animating. + doReturn(true).when(rootTask).hasWindowsAlive(); + doReturn(rootTask).when(task).getAnimatingContainer( + eq(TRANSITION | CHILDREN), anyInt()); + + rootTask.removeIfPossible(); + // For the case of deferred removal the task controller will still be connected to its + // container until the root task window container is removed. + assertNotNull(rootTask.getParent()); + assertNotEquals(0, rootTask.getChildCount()); + assertNotNull(task); + + rootTask.removeImmediately(); + // After removing, the task will be isolated. + assertNull(task.getParent()); + assertEquals(0, task.getChildCount()); + } + + @Test + public void testReparent() { + // Create first root task on primary display. + final Task rootTask1 = createTask(mDisplayContent); + final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */); + + // Create second display and put second root task on it. + final DisplayContent dc = createNewDisplay(); + final Task rootTask2 = createTask(dc); + + // Reparent + clearInvocations(task1); // reset the number of onDisplayChanged for task. + rootTask1.reparent(dc.getDefaultTaskDisplayArea(), true /* onTop */); + assertEquals(dc, rootTask1.getDisplayContent()); + final int stack1PositionInParent = rootTask1.getParent().mChildren.indexOf(rootTask1); + final int stack2PositionInParent = rootTask1.getParent().mChildren.indexOf(rootTask2); + assertEquals(stack1PositionInParent, stack2PositionInParent + 1); + verify(task1, times(1)).onDisplayChanged(any()); + } + + @Test + public void testTaskOutset() { + final Task task = createTask(mDisplayContent); + final int taskOutset = 10; + spyOn(task); + doReturn(taskOutset).when(task).getTaskOutset(); + doReturn(true).when(task).inMultiWindowMode(); + + // Mock the resolved override windowing mode to non-fullscreen + final WindowConfiguration windowConfiguration = + task.getResolvedOverrideConfiguration().windowConfiguration; + spyOn(windowConfiguration); + doReturn(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) + .when(windowConfiguration).getWindowingMode(); + + // Prevent adjust task dimensions + doNothing().when(task).adjustForMinimalTaskDimensions(any(), any(), any()); + + final Rect taskBounds = new Rect(200, 200, 800, 1000); + // Update surface position and size by the given bounds. + task.setBounds(taskBounds); + + assertEquals(taskBounds.width() + 2 * taskOutset, task.getLastSurfaceSize().x); + assertEquals(taskBounds.height() + 2 * taskOutset, task.getLastSurfaceSize().y); + assertEquals(taskBounds.left - taskOutset, task.getLastSurfacePosition().x); + assertEquals(taskBounds.top - taskOutset, task.getLastSurfacePosition().y); + } + + @Test + public void testActivityAndTaskGetsProperType() { + final Task task1 = new TaskBuilder(mSupervisor).build(); + ActivityRecord activity1 = createNonAttachedActivityRecord(mDisplayContent); + + // First activity should become standard + task1.addChild(activity1, 0); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity1.getActivityType()); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType()); + + // Second activity should also become standard + ActivityRecord activity2 = createNonAttachedActivityRecord(mDisplayContent); + task1.addChild(activity2, WindowContainer.POSITION_TOP); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity2.getActivityType()); + assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType()); + } + + @Test public void testResumedActivity() { final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build(); final Task task = r.getTask(); @@ -113,40 +288,40 @@ public class ActivityStackTests extends WindowTestsBase { @Test public void testResumedActivityFromTaskReparenting() { - final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); + final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final ActivityRecord r = new ActivityBuilder(mAtm) - .setCreateTask(true).setParentTask(parentTask).build(); + .setCreateTask(true).setParentTask(rootTask).build(); final Task task = r.getTask(); - // Ensure moving task between two stacks updates resumed activity + // Ensure moving task between two root tasks updates resumed activity r.setState(RESUMED, "testResumedActivityFromTaskReparenting"); - assertEquals(r, parentTask.getResumedActivity()); + assertEquals(r, rootTask.getResumedActivity()); - final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build(); - task.reparent(destStack, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT, + final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); + task.reparent(destRootTask, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT, false /* animate */, true /* deferResume*/, "testResumedActivityFromTaskReparenting"); - assertNull(parentTask.getResumedActivity()); - assertEquals(r, destStack.getResumedActivity()); + assertNull(rootTask.getResumedActivity()); + assertEquals(r, destRootTask.getResumedActivity()); } @Test public void testResumedActivityFromActivityReparenting() { - final Task parentTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); + final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final ActivityRecord r = new ActivityBuilder(mAtm) - .setCreateTask(true).setParentTask(parentTask).build(); + .setCreateTask(true).setParentTask(rootTask).build(); final Task task = r.getTask(); - // Ensure moving task between two stacks updates resumed activity + // Ensure moving task between two root tasks updates resumed activity r.setState(RESUMED, "testResumedActivityFromActivityReparenting"); - assertEquals(r, parentTask.getResumedActivity()); + assertEquals(r, rootTask.getResumedActivity()); - final Task destStack = new TaskBuilder(mSupervisor).setOnTop(true).build(); - task.reparent(destStack, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT, + final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); + task.reparent(destRootTask, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT, false /* animate */, false /* deferResume*/, "testResumedActivityFromActivityReparenting"); - assertNull(parentTask.getResumedActivity()); - assertEquals(r, destStack.getResumedActivity()); + assertNull(rootTask.getResumedActivity()); + assertEquals(r, destRootTask.getResumedActivity()); } @Test @@ -155,7 +330,7 @@ public class ActivityStackTests extends WindowTestsBase { // We're testing an edge case here where we have primary + fullscreen rather than secondary. organizer.setMoveToSecondaryOnEnter(false); - // Create primary splitscreen stack. + // Create primary splitscreen root task. final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor) .setParentTask(organizer.mPrimary) .setOnTop(true) @@ -168,7 +343,7 @@ public class ActivityStackTests extends WindowTestsBase { primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", null /* task */); - // Assert that stack is at the bottom. + // Assert that root task is at the bottom. assertEquals(0, getTaskIndexOf(mDefaultTaskDisplayArea, primarySplitScreen)); // Ensure no longer in splitscreen. @@ -182,7 +357,7 @@ public class ActivityStackTests extends WindowTestsBase { @Test public void testMoveToPrimarySplitScreenThenMoveToBack() { TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); - // This time, start with a fullscreen activitystack + // This time, start with a fullscreen activity root task. final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -196,7 +371,7 @@ public class ActivityStackTests extends WindowTestsBase { primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", null /* task */); - // Assert that stack is at the bottom. + // Assert that root task is at the bottom. assertEquals(primarySplitScreen, organizer.mSecondary.getChildAt(0)); // Ensure that the override mode is restored to what it was (fullscreen) @@ -208,7 +383,7 @@ public class ActivityStackTests extends WindowTestsBase { public void testSplitScreenMoveToBack() { TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); // Explicitly reparent task to primary split root to enter split mode, in which implies - // primary on top and secondary containing the home task below another stack. + // primary on top and secondary containing the home task below another root task. final Task primaryTask = mDefaultTaskDisplayArea.createRootTask( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask( @@ -244,24 +419,24 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testRemoveOrganizedTask_UpdateStackReference() { + public void testRemoveOrganizedTask_UpdateRootTaskReference() { final Task rootHomeTask = mDefaultTaskDisplayArea.getRootHomeTask(); final ActivityRecord homeActivity = new ActivityBuilder(mAtm) .setTask(rootHomeTask) .build(); - final Task secondaryStack = mAtm.mTaskOrganizerController.createRootTask( + final Task secondaryRootTask = mAtm.mTaskOrganizerController.createRootTask( rootHomeTask.getDisplayContent(), WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); - rootHomeTask.reparent(secondaryStack, POSITION_TOP); - assertEquals(secondaryStack, rootHomeTask.getParent()); + rootHomeTask.reparent(secondaryRootTask, POSITION_TOP); + assertEquals(secondaryRootTask, rootHomeTask.getParent()); - // This should call to {@link TaskDisplayArea#removeStackReferenceIfNeeded}. + // This should call to {@link TaskDisplayArea#removeRootTaskReferenceIfNeeded}. homeActivity.removeImmediately(); assertNull(mDefaultTaskDisplayArea.getRootHomeTask()); } @Test - public void testStackInheritsDisplayWindowingMode() { + public void testRootTaskInheritsDisplayWindowingMode() { final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -276,7 +451,7 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testStackOverridesDisplayWindowingMode() { + public void testRootTaskOverridesDisplayWindowingMode() { final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -358,95 +533,92 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testMoveStackToBackIncludingParent() { + public void testMoveRootTaskToBackIncludingParent() { final TaskDisplayArea taskDisplayArea = addNewDisplayContentAt(DisplayContent.POSITION_TOP) .getDefaultTaskDisplayArea(); - final Task stack1 = createStackForShouldBeVisibleTest(taskDisplayArea, + final Task rootTask1 = createTaskForShouldBeVisibleTest(taskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */, true /* twoLevelTask */); - final Task stack2 = createStackForShouldBeVisibleTest(taskDisplayArea, + final Task rootTask2 = createTaskForShouldBeVisibleTest(taskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */, true /* twoLevelTask */); - // Do not move display to back because there is still another stack. - stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask()); - verify(stack2).positionChildAtBottom(any(), eq(false) /* includingParents */); + // Do not move display to back because there is still another root task. + rootTask2.moveToBack("testMoveRootTaskToBackIncludingParent", rootTask2.getTopMostTask()); + verify(rootTask2).positionChildAtBottom(any(), eq(false) /* includingParents */); - // Also move display to back because there is only one stack left. - taskDisplayArea.removeRootTask(stack1); - stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask()); - verify(stack2).positionChildAtBottom(any(), eq(true) /* includingParents */); + // Also move display to back because there is only one root task left. + taskDisplayArea.removeRootTask(rootTask1); + rootTask2.moveToBack("testMoveRootTaskToBackIncludingParent", rootTask2.getTopMostTask()); + verify(rootTask2).positionChildAtBottom(any(), eq(true) /* includingParents */); } @Test public void testShouldBeVisible_Fullscreen() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - // Add an activity to the pinned stack so it isn't considered empty for visibility check. + // Add an activity to the pinned root task so it isn't considered empty for visibility + // check. final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm) - .setTask(pinnedStack) + .setTask(pinnedRootTask) .build(); - assertTrue(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - - final Task fullscreenStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack - // should be visible since it is always on-top. - doReturn(false).when(fullscreenStack).isTranslucent(any()); - assertFalse(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - assertTrue(fullscreenStack.shouldBeVisible(null /* starting */)); + assertTrue(homeRootTask.shouldBeVisible(null /* starting */)); + assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */)); - // Home stack should be visible behind a translucent fullscreen stack. - doReturn(true).when(fullscreenStack).isTranslucent(any()); - assertTrue(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); + final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + // Home root task shouldn't be visible behind an opaque fullscreen root task, but pinned + // root task should be visible since it is always on-top. + doReturn(false).when(fullscreenRootTask).isTranslucent(any()); + assertFalse(homeRootTask.shouldBeVisible(null /* starting */)); + assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */)); + assertTrue(fullscreenRootTask.shouldBeVisible(null /* starting */)); + + // Home root task should be visible behind a translucent fullscreen root task. + doReturn(true).when(fullscreenRootTask).isTranslucent(any()); + assertTrue(homeRootTask.shouldBeVisible(null /* starting */)); + assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */)); } @Test public void testShouldBeVisible_SplitScreen() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - // Home stack should always be fullscreen for this test. - doReturn(false).when(homeStack).supportsSplitScreenWindowingMode(); - final Task splitScreenPrimary = - createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + // Home root task should always be fullscreen for this test. + doReturn(false).when(homeRootTask).supportsSplitScreenWindowingMode(); + final Task splitScreenPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task splitScreenSecondary = - createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task splitScreenSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - // Home stack shouldn't be visible if both halves of split-screen are opaque. + // Home root task shouldn't be visible if both halves of split-screen are opaque. doReturn(false).when(splitScreenPrimary).isTranslucent(any()); doReturn(false).when(splitScreenSecondary).isTranslucent(any()); - assertFalse(homeStack.shouldBeVisible(null /* starting */)); + assertFalse(homeRootTask.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_INVISIBLE, homeRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, splitScreenSecondary.getVisibility(null /* starting */)); - // Home stack should be visible if one of the halves of split-screen is translucent. + // Home root task should be visible if one of the halves of split-screen is translucent. doReturn(true).when(splitScreenPrimary).isTranslucent(any()); - assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(homeRootTask.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - homeStack.getVisibility(null /* starting */)); + homeRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, splitScreenSecondary.getVisibility(null /* starting */)); - final Task splitScreenSecondary2 = - createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task splitScreenSecondary2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); // First split-screen secondary shouldn't be visible behind another opaque split-split // secondary. @@ -468,18 +640,17 @@ public class ActivityStackTests extends WindowTestsBase { assertEquals(TASK_VISIBILITY_VISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); - final Task assistantStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, - true /* onTop */); + final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. - doReturn(false).when(assistantStack).isTranslucent(any()); - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + // Split-screen root tasks shouldn't be visible behind an opaque fullscreen root task. + doReturn(false).when(assistantRootTask).isTranslucent(any()); + assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, - assistantStack.getVisibility(null /* starting */)); + assistantRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_INVISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_INVISIBLE, @@ -487,14 +658,14 @@ public class ActivityStackTests extends WindowTestsBase { assertEquals(TASK_VISIBILITY_INVISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); - // Split-screen stacks should be visible behind a translucent fullscreen stack. - doReturn(true).when(assistantStack).isTranslucent(any()); - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + // Split-screen root tasks should be visible behind a translucent fullscreen root task. + doReturn(true).when(assistantRootTask).isTranslucent(any()); + assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, - assistantStack.getVisibility(null /* starting */)); + assistantRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitScreenPrimary.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -502,20 +673,20 @@ public class ActivityStackTests extends WindowTestsBase { assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitScreenSecondary2.getVisibility(null /* starting */)); - // Assistant stack shouldn't be visible behind translucent split-screen stack, + // Assistant root task shouldn't be visible behind translucent split-screen root task, // unless it is configured to show on top of everything. - doReturn(false).when(assistantStack).isTranslucent(any()); + doReturn(false).when(assistantRootTask).isTranslucent(any()); doReturn(true).when(splitScreenPrimary).isTranslucent(any()); doReturn(true).when(splitScreenSecondary2).isTranslucent(any()); splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen"); splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen"); if (isAssistantOnTop()) { - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, - assistantStack.getVisibility(null /* starting */)); + assistantRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_INVISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_INVISIBLE, @@ -523,11 +694,11 @@ public class ActivityStackTests extends WindowTestsBase { assertEquals(TASK_VISIBILITY_INVISIBLE, splitScreenSecondary2.getVisibility(null /* starting */)); } else { - assertFalse(assistantStack.shouldBeVisible(null /* starting */)); + assertFalse(assistantRootTask.shouldBeVisible(null /* starting */)); assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); assertEquals(TASK_VISIBILITY_INVISIBLE, - assistantStack.getVisibility(null /* starting */)); + assistantRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, splitScreenPrimary.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_INVISIBLE, @@ -539,35 +710,33 @@ public class ActivityStackTests extends WindowTestsBase { @Test public void testGetVisibility_MultiLevel() { - final Task homeStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, - true /* onTop */); - final Task splitPrimary = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, - ACTIVITY_TYPE_UNDEFINED, true /* onTop */); - final Task splitSecondary = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - ACTIVITY_TYPE_UNDEFINED, true /* onTop */); - - doReturn(false).when(homeStack).isTranslucent(any()); + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, true /* onTop */); + final Task splitPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); + final Task splitSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); + + doReturn(false).when(homeRootTask).isTranslucent(any()); doReturn(false).when(splitPrimary).isTranslucent(any()); doReturn(false).when(splitSecondary).isTranslucent(any()); // Re-parent home to split secondary. - homeStack.reparent(splitSecondary, POSITION_TOP); + homeRootTask.reparent(splitSecondary, POSITION_TOP); // Current tasks should be visible. assertEquals(TASK_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */)); // Home task should still be visible even though it is a child of another visible task. - assertEquals(TASK_VISIBILITY_VISIBLE, homeStack.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_VISIBLE, homeRootTask.getVisibility(null /* starting */)); // Add fullscreen translucent task that partially occludes split tasks - final Task translucentStack = createStandardStackForVisibilityTest( + final Task translucentRootTask = createStandardRootTaskForVisibilityTest( WINDOWING_MODE_FULLSCREEN, true /* translucent */); // Fullscreen translucent task should be visible - assertEquals(TASK_VISIBILITY_VISIBLE, translucentStack.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_VISIBLE, + translucentRootTask.getVisibility(null /* starting */)); // Split tasks should be visible behind translucent assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitPrimary.getVisibility(null /* starting */)); @@ -576,415 +745,400 @@ public class ActivityStackTests extends WindowTestsBase { // Home task should be visible behind translucent since its parent is visible behind // translucent. assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - homeStack.getVisibility(null /* starting */)); + homeRootTask.getVisibility(null /* starting */)); // Hide split-secondary splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); // Home split secondary and home task should be invisible. assertEquals(TASK_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_INVISIBLE, homeRootTask.getVisibility(null /* starting */)); } @Test public void testGetVisibility_FullscreenBehindTranslucent() { - final Task bottomStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task bottomRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final Task translucentStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task translucentRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - bottomStack.getVisibility(null /* starting */)); + bottomRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, - translucentStack.getVisibility(null /* starting */)); + translucentRootTask.getVisibility(null /* starting */)); } @Test public void testGetVisibility_FullscreenBehindTranslucentAndOpaque() { - final Task bottomStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task bottomRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final Task translucentStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task translucentRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final Task opaqueStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task opaqueRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - assertEquals(TASK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_INVISIBLE, - translucentStack.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */)); + translucentRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */)); } @Test public void testGetVisibility_FullscreenBehindOpaqueAndTranslucent() { - final Task bottomStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task bottomRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final Task opaqueStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task opaqueRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final Task translucentStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task translucentRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - assertEquals(TASK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - opaqueStack.getVisibility(null /* starting */)); + opaqueRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, - translucentStack.getVisibility(null /* starting */)); + translucentRootTask.getVisibility(null /* starting */)); } @Test public void testGetVisibility_FullscreenTranslucentBehindTranslucent() { - final Task bottomTranslucentStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task bottomTranslucentRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final Task translucentStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task translucentRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - bottomTranslucentStack.getVisibility(null /* starting */)); + bottomTranslucentRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, - translucentStack.getVisibility(null /* starting */)); + translucentRootTask.getVisibility(null /* starting */)); } @Test public void testGetVisibility_FullscreenTranslucentBehindOpaque() { - final Task bottomTranslucentStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task bottomTranslucentRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final Task opaqueStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task opaqueRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); assertEquals(TASK_VISIBILITY_INVISIBLE, - bottomTranslucentStack.getVisibility(null /* starting */)); - assertEquals(TASK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */)); + bottomTranslucentRootTask.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */)); } @Test public void testGetVisibility_FullscreenBehindTranslucentAndPip() { - final Task bottomStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task bottomRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, false /* translucent */); - final Task translucentStack = - createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN, + final Task translucentRootTask = + createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN, true /* translucent */); - final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - bottomStack.getVisibility(null /* starting */)); + bottomRootTask.getVisibility(null /* starting */)); assertEquals(TASK_VISIBILITY_VISIBLE, - translucentStack.getVisibility(null /* starting */)); - // Add an activity to the pinned stack so it isn't considered empty for visibility check. + translucentRootTask.getVisibility(null /* starting */)); + // Add an activity to the pinned root task so it isn't considered empty for visibility + // check. final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm) - .setTask(pinnedStack) + .setTask(pinnedRootTask) .build(); - assertEquals(TASK_VISIBILITY_VISIBLE, pinnedStack.getVisibility(null /* starting */)); + assertEquals(TASK_VISIBILITY_VISIBLE, pinnedRootTask.getVisibility(null /* starting */)); } @Test public void testShouldBeVisible_Finishing() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity(); + ActivityRecord topRunningHomeActivity = homeRootTask.topRunningActivity(); if (topRunningHomeActivity == null) { topRunningHomeActivity = new ActivityBuilder(mAtm) - .setTask(homeStack) + .setTask(homeRootTask) .build(); } - final Task translucentStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - doReturn(true).when(translucentStack).isTranslucent(any()); + final Task translucentRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + doReturn(true).when(translucentRootTask).isTranslucent(any()); - assertTrue(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(translucentStack.shouldBeVisible(null /* starting */)); + assertTrue(homeRootTask.shouldBeVisible(null /* starting */)); + assertTrue(translucentRootTask.shouldBeVisible(null /* starting */)); topRunningHomeActivity.finishing = true; final ActivityRecord topRunningTranslucentActivity = - translucentStack.topRunningActivity(); + translucentRootTask.topRunningActivity(); topRunningTranslucentActivity.finishing = true; - // Home stack should be visible even there are no running activities. - assertTrue(homeStack.shouldBeVisible(null /* starting */)); + // Home root task should be visible even there are no running activities. + assertTrue(homeRootTask.shouldBeVisible(null /* starting */)); // Home should be visible if we are starting an activity within it. - assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */)); - // The translucent stack shouldn't be visible since its activity marked as finishing. - assertFalse(translucentStack.shouldBeVisible(null /* starting */)); + assertTrue(homeRootTask.shouldBeVisible(topRunningHomeActivity /* starting */)); + // The translucent root task shouldn't be visible since its activity marked as finishing. + assertFalse(translucentRootTask.shouldBeVisible(null /* starting */)); } @Test - public void testShouldBeVisible_FullscreenBehindTranslucentInHomeStack() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testShouldBeVisible_FullscreenBehindTranslucentInHomeRootTask() { + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mAtm) - .setParentTask(homeStack) - .setCreateTask(true) - .build(); + .setParentTask(homeRootTask) + .setCreateTask(true) + .build(); final Task task = firstActivity.getTask(); final ActivityRecord secondActivity = new ActivityBuilder(mAtm) .setTask(task) .build(); doReturn(false).when(secondActivity).occludesParent(); - homeStack.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, + homeRootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, false /* preserveWindows */); assertTrue(firstActivity.shouldBeVisible()); } @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeBehindFullscreen() { + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final Task fullscreenStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isTranslucent(any()); - doReturn(false).when(fullscreenStack).isTranslucent(any()); + doReturn(false).when(homeRootTask).isTranslucent(any()); + doReturn(false).when(fullscreenRootTask).isTranslucent(any()); - // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack); - assertEquals(fullscreenStack, getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack); - assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack)); + // Ensure that we don't move the home root task if it is already behind the top fullscreen + // root task. + int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask); + assertEquals(fullscreenRootTask, getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask); + assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask)); } @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeBehindTranslucent() { + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final Task fullscreenStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, + final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isTranslucent(any()); - doReturn(true).when(fullscreenStack).isTranslucent(any()); + doReturn(false).when(homeRootTask).isTranslucent(any()); + doReturn(true).when(fullscreenRootTask).isTranslucent(any()); - // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack); - assertEquals(fullscreenStack, getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack); - assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack)); + // Ensure that we don't move the home root task if it is already behind the top fullscreen + // root task. + int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask); + assertEquals(fullscreenRootTask, getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask); + assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask)); } @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() { - final Task fullscreenStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeOnTop() { + final Task fullscreenRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - doReturn(false).when(homeStack).isTranslucent(any()); - doReturn(false).when(fullscreenStack).isTranslucent(any()); + doReturn(false).when(homeRootTask).isTranslucent(any()); + doReturn(false).when(fullscreenRootTask).isTranslucent(any()); - // Ensure we don't move the home stack if it is already on top - int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack); - assertNull(getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack); - assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack)); + // Ensure we don't move the home root task if it is already on top + int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask); + assertNull(getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask); + assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask)); } @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindBottomMostVisible_MoveHomeBehindFullscreen() { + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final Task fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isTranslucent(any()); - doReturn(false).when(fullscreenStack1).isTranslucent(any()); - doReturn(false).when(fullscreenStack2).isTranslucent(any()); + doReturn(false).when(homeRootTask).isTranslucent(any()); + doReturn(false).when(fullscreenRootTask1).isTranslucent(any()); + doReturn(false).when(fullscreenRootTask2).isTranslucent(any()); - // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the - // pinned stack - assertEquals(fullscreenStack1, getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack); - assertEquals(fullscreenStack2, getRootTaskAbove(homeStack)); + // Ensure that we move the home root task behind the bottom most fullscreen root task, + // ignoring the pinned root task. + assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask); + assertEquals(fullscreenRootTask2, getRootTaskAbove(homeRootTask)); } @Test public void - testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + testMoveHomeRootTaskBehindBottomMostVisible_MoveHomeBehindFullscreenAndTranslucent() { + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final Task fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); + final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(false).when(homeStack).isTranslucent(any()); - doReturn(false).when(fullscreenStack1).isTranslucent(any()); - doReturn(true).when(fullscreenStack2).isTranslucent(any()); + doReturn(false).when(homeRootTask).isTranslucent(any()); + doReturn(false).when(fullscreenRootTask1).isTranslucent(any()); + doReturn(true).when(fullscreenRootTask2).isTranslucent(any()); - // Ensure that we move the home stack behind the bottom most non-translucent fullscreen - // stack - assertEquals(fullscreenStack1, getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeStack); - assertEquals(fullscreenStack1, getRootTaskAbove(homeStack)); + // Ensure that we move the home root task behind the bottom most non-translucent fullscreen + // root task. + assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(homeRootTask); + assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask)); } @Test - public void testMoveHomeStackBehindStack_BehindHomeStack() { - final Task fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindRootTask_BehindHomeRootTask() { + final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - doReturn(false).when(homeStack).isTranslucent(any()); - doReturn(false).when(fullscreenStack1).isTranslucent(any()); - doReturn(false).when(fullscreenStack2).isTranslucent(any()); + doReturn(false).when(homeRootTask).isTranslucent(any()); + doReturn(false).when(fullscreenRootTask1).isTranslucent(any()); + doReturn(false).when(fullscreenRootTask2).isTranslucent(any()); - // Ensure we don't move the home stack behind itself - int homeStackIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeStack); - mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, homeStack); - assertEquals(homeStackIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeStack)); + // Ensure we don't move the home root task behind itself + int homeRootTaskIndex = getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask); + mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, homeRootTask); + assertEquals(homeRootTaskIndex, getTaskIndexOf(mDefaultTaskDisplayArea, homeRootTask)); } @Test - public void testMoveHomeStackBehindStack() { - final Task fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task fullscreenStack3 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task fullscreenStack4 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testMoveHomeRootTaskBehindRootTask() { + final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task fullscreenRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task fullscreenRootTask3 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task fullscreenRootTask4 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack1); - assertEquals(fullscreenStack1, getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack2); - assertEquals(fullscreenStack2, getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack4); - assertEquals(fullscreenStack4, getRootTaskAbove(homeStack)); - mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeStack, fullscreenStack2); - assertEquals(fullscreenStack2, getRootTaskAbove(homeStack)); + mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask1); + assertEquals(fullscreenRootTask1, getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask2); + assertEquals(fullscreenRootTask2, getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask4); + assertEquals(fullscreenRootTask4, getRootTaskAbove(homeRootTask)); + mDefaultTaskDisplayArea.moveRootTaskBehindRootTask(homeRootTask, fullscreenRootTask2); + assertEquals(fullscreenRootTask2, getRootTaskAbove(homeRootTask)); } @Test public void testSetAlwaysOnTop() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - assertEquals(pinnedStack, getRootTaskAbove(homeStack)); + assertEquals(pinnedRootTask, getRootTaskAbove(homeRootTask)); - final Task alwaysOnTopStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - alwaysOnTopStack.setAlwaysOnTop(true); - assertTrue(alwaysOnTopStack.isAlwaysOnTop()); - // Ensure (non-pinned) always on top stack is put below pinned stack. - assertEquals(pinnedStack, getRootTaskAbove(alwaysOnTopStack)); + final Task alwaysOnTopRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); + alwaysOnTopRootTask.setAlwaysOnTop(true); + assertTrue(alwaysOnTopRootTask.isAlwaysOnTop()); + // Ensure (non-pinned) always on top root task is put below pinned root task. + assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask)); - final Task nonAlwaysOnTopStack = createStackForShouldBeVisibleTest( + final Task nonAlwaysOnTopRootTask = createTaskForShouldBeVisibleTest( mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - // Ensure non always on top stack is put below always on top stacks. - assertEquals(alwaysOnTopStack, getRootTaskAbove(nonAlwaysOnTopStack)); - - final Task alwaysOnTopStack2 = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - alwaysOnTopStack2.setAlwaysOnTop(true); - assertTrue(alwaysOnTopStack2.isAlwaysOnTop()); - // Ensure newly created always on top stack is placed above other all always on top stacks. - assertEquals(pinnedStack, getRootTaskAbove(alwaysOnTopStack2)); - - alwaysOnTopStack2.setAlwaysOnTop(false); - // Ensure, when always on top is turned off for a stack, the stack is put just below all - // other always on top stacks. - assertEquals(alwaysOnTopStack, getRootTaskAbove(alwaysOnTopStack2)); - alwaysOnTopStack2.setAlwaysOnTop(true); + // Ensure non always on top root task is put below always on top root tasks. + assertEquals(alwaysOnTopRootTask, getRootTaskAbove(nonAlwaysOnTopRootTask)); + + final Task alwaysOnTopRootTask2 = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); + alwaysOnTopRootTask2.setAlwaysOnTop(true); + assertTrue(alwaysOnTopRootTask2.isAlwaysOnTop()); + // Ensure newly created always on top root task is placed above other all always on top + // root tasks. + assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask2)); + + alwaysOnTopRootTask2.setAlwaysOnTop(false); + // Ensure, when always on top is turned off for a root task, the root task is put just below + // all other always on top root tasks. + assertEquals(alwaysOnTopRootTask, getRootTaskAbove(alwaysOnTopRootTask2)); + alwaysOnTopRootTask2.setAlwaysOnTop(true); // Ensure always on top state changes properly when windowing mode changes. - alwaysOnTopStack2.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertFalse(alwaysOnTopStack2.isAlwaysOnTop()); - assertEquals(alwaysOnTopStack, getRootTaskAbove(alwaysOnTopStack2)); - alwaysOnTopStack2.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertTrue(alwaysOnTopStack2.isAlwaysOnTop()); - assertEquals(pinnedStack, getRootTaskAbove(alwaysOnTopStack2)); + alwaysOnTopRootTask2.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertFalse(alwaysOnTopRootTask2.isAlwaysOnTop()); + assertEquals(alwaysOnTopRootTask, getRootTaskAbove(alwaysOnTopRootTask2)); + alwaysOnTopRootTask2.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertTrue(alwaysOnTopRootTask2.isAlwaysOnTop()); + assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask2)); } @Test public void testSplitScreenMoveToFront() { - final Task splitScreenPrimary = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, - ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task splitScreenSecondary = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task assistantStack = createStackForShouldBeVisibleTest( - mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, - true /* onTop */); + final Task splitScreenPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task splitScreenSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); doReturn(false).when(splitScreenPrimary).isTranslucent(any()); doReturn(false).when(splitScreenSecondary).isTranslucent(any()); - doReturn(false).when(assistantStack).isTranslucent(any()); + doReturn(false).when(assistantRootTask).isTranslucent(any()); assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); splitScreenSecondary.moveToFront("testSplitScreenMoveToFront"); if (isAssistantOnTop()) { assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); } else { assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertFalse(assistantStack.shouldBeVisible(null /* starting */)); + assertFalse(assistantRootTask.shouldBeVisible(null /* starting */)); } } - private Task createStandardStackForVisibilityTest(int windowingMode, + private Task createStandardRootTaskForVisibilityTest(int windowingMode, boolean translucent) { - final Task stack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + final Task rootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, windowingMode, ACTIVITY_TYPE_STANDARD, true /* onTop */); - doReturn(translucent).when(stack).isTranslucent(any()); - return stack; + doReturn(translucent).when(rootTask).isTranslucent(any()); + return rootTask; } - private Task createStackForShouldBeVisibleTest( + private Task createTaskForShouldBeVisibleTest( TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, boolean onTop) { - return createStackForShouldBeVisibleTest(taskDisplayArea, + return createTaskForShouldBeVisibleTest(taskDisplayArea, windowingMode, activityType, onTop, false /* twoLevelTask */); } @SuppressWarnings("TypeParameterUnusedInFormals") - private Task createStackForShouldBeVisibleTest(TaskDisplayArea taskDisplayArea, + private Task createTaskForShouldBeVisibleTest(TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, boolean onTop, boolean twoLevelTask) { final Task task; if (activityType == ACTIVITY_TYPE_HOME) { @@ -1157,20 +1311,20 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testWontFinishHomeStackImmediately() { - final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea, + public void testWontFinishHomeRootTaskImmediately() { + final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - ActivityRecord activity = homeStack.topRunningActivity(); + ActivityRecord activity = homeRootTask.topRunningActivity(); if (activity == null) { activity = new ActivityBuilder(mAtm) - .setParentTask(homeStack) + .setParentTask(homeRootTask) .setCreateTask(true) .build(); } - // Home stack should not be destroyed immediately. - final ActivityRecord activity1 = finishTopActivity(homeStack); + // Home root task should not be destroyed immediately. + final ActivityRecord activity1 = finishTopActivity(homeRootTask); assertEquals(FINISHING, activity1.getState()); } @@ -1178,30 +1332,28 @@ public class ActivityStackTests extends WindowTestsBase { public void testFinishCurrentActivity() { // Create 2 activities on a new display. final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP); - final Task stack1 = createStackForShouldBeVisibleTest( - display.getDefaultTaskDisplayArea(), + final Task rootTask1 = createTaskForShouldBeVisibleTest(display.getDefaultTaskDisplayArea(), WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task stack2 = createStackForShouldBeVisibleTest( - display.getDefaultTaskDisplayArea(), + final Task rootTask2 = createTaskForShouldBeVisibleTest(display.getDefaultTaskDisplayArea(), WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - // There is still an activity1 in stack1 so the activity2 should be added to finishing list - // that will be destroyed until idle. - stack2.getTopNonFinishingActivity().mVisibleRequested = true; - final ActivityRecord activity2 = finishTopActivity(stack2); + // There is still an activity1 in rootTask1 so the activity2 should be added to finishing + // list that will be destroyed until idle. + rootTask2.getTopNonFinishingActivity().mVisibleRequested = true; + final ActivityRecord activity2 = finishTopActivity(rootTask2); assertEquals(STOPPING, activity2.getState()); assertThat(mSupervisor.mStoppingActivities).contains(activity2); // The display becomes empty. Since there is no next activity to be idle, the activity // should be destroyed immediately with updating configuration to restore original state. - final ActivityRecord activity1 = finishTopActivity(stack1); + final ActivityRecord activity1 = finishTopActivity(rootTask1); assertEquals(DESTROYING, activity1.getState()); verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */, eq(display.mDisplayId), anyBoolean(), anyBoolean()); } - private ActivityRecord finishTopActivity(Task stack) { - final ActivityRecord activity = stack.topRunningActivity(); + private ActivityRecord finishTopActivity(Task task) { + final ActivityRecord activity = task.topRunningActivity(); assertNotNull(activity); activity.setState(STOPPED, "finishTopActivity"); activity.makeFinishingLocked(); @@ -1214,24 +1366,24 @@ public class ActivityStackTests extends WindowTestsBase { // When focused activity and keyguard is going away, we should not sleep regardless // of the display state, but keyguard-going-away should only take effects on default // display since there is no keyguard on secondary displays (yet). - verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/, + verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/, true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */); - verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/, + verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/, true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */); - // When not the focused stack, defer to display sleeping state. - verifyShouldSleepActivities(false /* focusedStack */, true /*keyguardGoingAway*/, + // When not the focused root task, defer to display sleeping state. + verifyShouldSleepActivities(false /* focusedRootTask */, true /*keyguardGoingAway*/, true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */); // If keyguard is going away, defer to the display sleeping state. - verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/, + verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/, true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */); - verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/, + verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/, false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */); } @Test - public void testStackOrderChangedOnRemoveStack() { + public void testRootTaskOrderChangedOnRemoveRootTask() { final Task task = new TaskBuilder(mSupervisor).build(); RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener(); mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener); @@ -1244,7 +1396,7 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testStackOrderChangedOnAddPositionStack() { + public void testRootTaskOrderChangedOnAddPositionRootTask() { final Task task = new TaskBuilder(mSupervisor).build(); mDefaultTaskDisplayArea.removeRootTask(task); @@ -1260,14 +1412,14 @@ public class ActivityStackTests extends WindowTestsBase { } @Test - public void testStackOrderChangedOnPositionStack() { + public void testRootTaskOrderChangedOnPositionRootTask() { RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener(); try { - final Task fullscreenStack1 = createStackForShouldBeVisibleTest( + final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest( mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener); - mDefaultTaskDisplayArea.positionChildAt(POSITION_BOTTOM, fullscreenStack1, + mDefaultTaskDisplayArea.positionChildAt(POSITION_BOTTOM, fullscreenRootTask1, false /*includingParents*/); } finally { mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener); @@ -1443,7 +1595,7 @@ public class ActivityStackTests extends WindowTestsBase { com.android.internal.R.bool.config_assistantOnTopOfDream); } - private void verifyShouldSleepActivities(boolean focusedStack, + private void verifyShouldSleepActivities(boolean focusedRootTask, boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay, boolean expected) { final Task task = new TaskBuilder(mSupervisor).build(); @@ -1454,13 +1606,13 @@ public class ActivityStackTests extends WindowTestsBase { task.mDisplayContent = display; doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway(); doReturn(displaySleeping).when(display).isSleeping(); - doReturn(focusedStack).when(task).isFocusedRootTaskOnDisplay(); + doReturn(focusedRootTask).when(task).isFocusedRootTaskOnDisplay(); assertEquals(expected, task.shouldSleepActivities()); } private static class RootTaskOrderChangedListener - implements OnRootTaskOrderChangedListener { + implements TaskDisplayArea.OnRootTaskOrderChangedListener { public boolean mChanged = false; @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 1b114c194bfb..20bced252764 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -16,42 +16,93 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.TYPE_VIRTUAL; +import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; +import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE; import static com.android.server.wm.Task.ActivityState.FINISHING; import static com.android.server.wm.Task.ActivityState.PAUSED; import static com.android.server.wm.Task.ActivityState.PAUSING; import static com.android.server.wm.Task.ActivityState.STOPPED; import static com.android.server.wm.Task.ActivityState.STOPPING; +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.refEq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.content.ComponentName; +import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.content.res.Resources; import android.platform.test.annotations.Presubmit; +import android.util.MergedConfiguration; +import android.util.Pair; -import androidx.test.filters.SmallTest; +import androidx.test.filters.MediumTest; +import com.android.internal.app.ResolverActivity; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + /** - * Tests for RootWindowContainer. + * Tests for {@link RootWindowContainer}. * * Build/Install/Run: * atest WmTests:RootWindowContainerTests */ -@SmallTest +@MediumTest @Presubmit @RunWith(WindowTestRunner.class) public class RootWindowContainerTests extends WindowTestsBase { + @Before + public void setUp() throws Exception { + doNothing().when(mAtm).updateSleepIfNeededLocked(); + } + @Test public void testUpdateDefaultDisplayWindowingModeOnSettingsRetrieved() { assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, @@ -166,5 +217,860 @@ public class RootWindowContainerTests extends WindowTestsBase { assertFalse(task.hasChild()); assertFalse(wpc.hasActivities()); } + + /** + * This test ensures that we do not try to restore a task based off an invalid task id. We + * should expect {@code null} to be returned in this case. + */ + @Test + public void testRestoringInvalidTask() { + mRootWindowContainer.getDefaultDisplay().removeAllTasks(); + Task task = mRootWindowContainer.anyTaskForId(0 /*taskId*/, + MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */); + assertNull(task); + } + + /** + * This test ensures that an existing task in the pinned root task is moved to the fullscreen + * activity root task when a new task is added. + */ + @Test + public void testReplacingTaskInPinnedRootTask() { + Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm) + .setTask(fullscreenTask).build(); + final ActivityRecord secondActivity = new ActivityBuilder(mAtm) + .setTask(fullscreenTask).build(); + + fullscreenTask.moveToFront("testReplacingTaskInPinnedRootTask"); + + // Ensure full screen root task has both tasks. + ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity); + + // Move first activity to pinned root task. + mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove"); + + final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea(); + Task pinnedRootTask = taskDisplayArea.getRootPinnedTask(); + // Ensure a task has moved over. + ensureTaskPlacement(pinnedRootTask, firstActivity); + ensureTaskPlacement(fullscreenTask, secondActivity); + + // Move second activity to pinned root task. + mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove"); + + // Need to get root tasks again as a new instance might have been created. + pinnedRootTask = taskDisplayArea.getRootPinnedTask(); + fullscreenTask = taskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); + // Ensure root tasks have swapped tasks. + ensureTaskPlacement(pinnedRootTask, secondActivity); + ensureTaskPlacement(fullscreenTask, firstActivity); + } + + @Test + public void testMovingBottomMostRootTaskActivityToPinnedRootTask() { + final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm) + .setTask(fullscreenTask).build(); + final Task task = firstActivity.getTask(); + + final ActivityRecord secondActivity = new ActivityBuilder(mAtm) + .setTask(fullscreenTask).build(); + + fullscreenTask.moveTaskToBack(task); + + // Ensure full screen task has both tasks. + ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity); + assertEquals(task.getTopMostActivity(), secondActivity); + firstActivity.setState(STOPPED, "testMovingBottomMostRootTaskActivityToPinnedRootTask"); + + + // Move first activity to pinned root task. + mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove"); + + assertTrue(firstActivity.mRequestForceTransition); + } + + private static void ensureTaskPlacement(Task task, ActivityRecord... activities) { + final ArrayList<ActivityRecord> taskActivities = new ArrayList<>(); + + task.forAllActivities((Consumer<ActivityRecord>) taskActivities::add, false); + + assertEquals("Expecting " + Arrays.deepToString(activities) + " got " + taskActivities, + taskActivities.size(), activities != null ? activities.length : 0); + + if (activities == null) { + return; + } + + for (ActivityRecord activity : activities) { + assertTrue(taskActivities.contains(activity)); + } + } + + @Test + public void testApplySleepTokens() { + final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); + final KeyguardController keyguard = mSupervisor.getKeyguardController(); + final Task task = new TaskBuilder(mSupervisor) + .setDisplay(display) + .setOnTop(false) + .build(); + + // Make sure we wake and resume in the case the display is turning on and the keyguard is + // not showing. + verifySleepTokenBehavior(display, keyguard, task, true /*displaySleeping*/, + false /* displayShouldSleep */, true /* isFocusedTask */, + false /* keyguardShowing */, true /* expectWakeFromSleep */, + true /* expectResumeTopActivity */); + + // Make sure we wake and don't resume when the display is turning on and the keyguard is + // showing. + verifySleepTokenBehavior(display, keyguard, task, true /*displaySleeping*/, + false /* displayShouldSleep */, true /* isFocusedTask */, + true /* keyguardShowing */, true /* expectWakeFromSleep */, + false /* expectResumeTopActivity */); + + // Make sure we wake and don't resume when the display is turning on and the keyguard is + // not showing as unfocused. + verifySleepTokenBehavior(display, keyguard, task, true /*displaySleeping*/, + false /* displayShouldSleep */, false /* isFocusedTask */, + false /* keyguardShowing */, true /* expectWakeFromSleep */, + false /* expectResumeTopActivity */); + + // Should not do anything if the display state hasn't changed. + verifySleepTokenBehavior(display, keyguard, task, false /*displaySleeping*/, + false /* displayShouldSleep */, true /* isFocusedTask */, + false /* keyguardShowing */, false /* expectWakeFromSleep */, + false /* expectResumeTopActivity */); + } + + private void verifySleepTokenBehavior(DisplayContent display, KeyguardController keyguard, + Task task, boolean displaySleeping, boolean displayShouldSleep, + boolean isFocusedTask, boolean keyguardShowing, boolean expectWakeFromSleep, + boolean expectResumeTopActivity) { + reset(task); + + doReturn(displayShouldSleep).when(display).shouldSleep(); + doReturn(displaySleeping).when(display).isSleeping(); + doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt()); + + doReturn(isFocusedTask).when(task).isFocusedRootTaskOnDisplay(); + doReturn(isFocusedTask ? task : null).when(display).getFocusedRootTask(); + TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea(); + doReturn(isFocusedTask ? task : null).when(defaultTaskDisplayArea).getFocusedRootTask(); + mRootWindowContainer.applySleepTokens(true); + verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); + verify(task, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked( + null /* target */, null /* targetOptions */); + } + + @Test + public void testAwakeFromSleepingWithAppConfiguration() { + final DisplayContent display = mRootWindowContainer.getDefaultDisplay(); + final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); + activity.moveFocusableActivityToTop("test"); + assertTrue(activity.getRootTask().isFocusedRootTaskOnDisplay()); + ActivityRecordTests.setRotatedScreenOrientationSilently(activity); + + final Configuration rotatedConfig = new Configuration(); + display.computeScreenConfiguration(rotatedConfig, display.getDisplayRotation() + .rotationForOrientation(activity.getOrientation(), display.getRotation())); + assertNotEquals(activity.getConfiguration().orientation, rotatedConfig.orientation); + // Assume the activity was shown in different orientation. For example, the top activity is + // landscape and the portrait lockscreen is shown. + activity.setLastReportedConfiguration( + new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig)); + activity.setState(Task.ActivityState.STOPPED, "sleep"); + + display.setIsSleeping(true); + doReturn(false).when(display).shouldSleep(); + // Allow to resume when awaking. + setBooted(mAtm); + mRootWindowContainer.applySleepTokens(true); + + // The display orientation should be changed by the activity so there is no relaunch. + verify(activity, never()).relaunchActivityLocked(anyBoolean()); + assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation); + } + + /** + * Verifies that removal of activity with task and root task is done correctly. + */ + @Test + public void testRemovingRootTaskOnAppCrash() { + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + final int originalRootTaskCount = defaultTaskDisplayArea.getRootTaskCount(); + final Task rootTask = defaultTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(rootTask).build(); + + assertEquals(originalRootTaskCount + 1, defaultTaskDisplayArea.getRootTaskCount()); + + // Let's pretend that the app has crashed. + firstActivity.app.setThread(null); + mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test"); + + // Verify that the root task was removed. + assertEquals(originalRootTaskCount, defaultTaskDisplayArea.getRootTaskCount()); + } + + /** + * Verifies that removal of activities with task and root task is done correctly when there are + * several task display areas. + */ + @Test + public void testRemovingRootTaskOnAppCrash_multipleDisplayAreas() { + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + final int originalRootTaskCount = defaultTaskDisplayArea.getRootTaskCount(); + final Task rootTask = defaultTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(rootTask).build(); + assertEquals(originalRootTaskCount + 1, defaultTaskDisplayArea.getRootTaskCount()); + + final DisplayContent dc = defaultTaskDisplayArea.getDisplayContent(); + final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( + dc, mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST); + final Task secondRootTask = secondTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + new ActivityBuilder(mAtm).setTask(secondRootTask).setUseProcess(firstActivity.app).build(); + assertEquals(1, secondTaskDisplayArea.getRootTaskCount()); + + // Let's pretend that the app has crashed. + firstActivity.app.setThread(null); + mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test"); + + // Verify that the root tasks were removed. + assertEquals(originalRootTaskCount, defaultTaskDisplayArea.getRootTaskCount()); + assertEquals(0, secondTaskDisplayArea.getRootTaskCount()); + } + + @Test + public void testFocusability() { + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + final Task task = defaultTaskDisplayArea.createRootTask( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); + + // Created tasks are focusable by default. + assertTrue(task.isTopActivityFocusable()); + assertTrue(activity.isFocusable()); + + // If the task is made unfocusable, its activities should inherit that. + task.setFocusable(false); + assertFalse(task.isTopActivityFocusable()); + assertFalse(activity.isFocusable()); + + final Task pinnedTask = defaultTaskDisplayArea.createRootTask( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm) + .setTask(pinnedTask).build(); + + // We should not be focusable when in pinned mode + assertFalse(pinnedTask.isTopActivityFocusable()); + assertFalse(pinnedActivity.isFocusable()); + + // Add flag forcing focusability. + pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE; + + // Task with FLAG_ALWAYS_FOCUSABLE should be focusable. + assertTrue(pinnedTask.isTopActivityFocusable()); + assertTrue(pinnedActivity.isFocusable()); + } + + /** + * Verify that split-screen primary root task will be chosen if activity is launched that + * targets split-screen secondary, but a matching existing instance is found on top of + * split-screen primary root task. + */ + @Test + public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() { + // Create primary split-screen root task with a task and an activity. + final Task primaryRootTask = mRootWindowContainer.getDefaultTaskDisplayArea() + .createRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final Task task = new TaskBuilder(mSupervisor).setParentTask(primaryRootTask).build(); + final ActivityRecord r = new ActivityBuilder(mAtm).setTask(task).build(); + + // Find a launch root task for the top activity in split-screen primary, while requesting + // split-screen secondary. + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); + final Task result = + mRootWindowContainer.getLaunchRootTask(r, options, task, true /* onTop */); + + // Assert that the primary root task is returned. + assertEquals(primaryRootTask, result); + } + + /** + * Verify that home root task would be moved to front when the top activity is Recents. + */ + @Test + public void testFindTaskToMoveToFrontWhenRecentsOnTop() { + // Create root task/task on default display. + final Task targetRootTask = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setOnTop(false) + .build(); + final Task targetTask = targetRootTask.getBottomMostTask(); + + // Create Recents on top of the display. + final Task rootTask = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setActivityType(ACTIVITY_TYPE_RECENTS) + .build(); + + final String reason = "findTaskToMoveToFront"; + mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, + false); + + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + verify(taskDisplayArea).moveHomeRootTaskToFront(contains(reason)); + } + + /** + * Verify that home root task won't be moved to front if the top activity on other display is + * Recents. + */ + @Test + public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() { + // Create tasks on default display. + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task targetRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, false /* onTop */); + final Task targetTask = new TaskBuilder(mSupervisor).setParentTask(targetRootTask).build(); + + // Create Recents on secondary display. + final TestDisplayContent secondDisplay = addNewDisplayContentAt( + DisplayContent.POSITION_TOP); + final Task rootTask = secondDisplay.getDefaultTaskDisplayArea() + .createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); + new ActivityBuilder(mAtm).setTask(rootTask).build(); + + final String reason = "findTaskToMoveToFront"; + mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, + false); + + verify(taskDisplayArea, never()).moveHomeRootTaskToFront(contains(reason)); + } + + /** + * Verify if a root task is not at the topmost position, it should be able to resume its + * activity if the root task is the top focused. + */ + @Test + public void testResumeActivityWhenNonTopmostRootTaskIsTopFocused() { + // Create a root task at bottom. + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, false /* onTop */)); + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build(); + taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/); + + // Assume the task is not at the topmost position (e.g. behind always-on-top root tasks) + // but it is the current top focused task. + assertFalse(rootTask.isTopRootTaskInDisplayArea()); + doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); + + // Use the task as target to resume. + mRootWindowContainer.resumeFocusedTasksTopActivities( + rootTask, activity, null /* targetOptions */); + + // Verify the target task should resume its activity. + verify(rootTask, times(1)).resumeTopActivityUncheckedLocked( + eq(activity), eq(null /* targetOptions */)); + } + + /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedRootTasksStartsHomeActivity_NoActivities() { + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + taskDisplayArea.getRootHomeTask().removeIfPossible(); + taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any()); + + mAtm.setBooted(true); + + // Trigger resume on all displays + mRootWindowContainer.resumeFocusedTasksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea)); + } + + /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedRootTasksStartsHomeActivity_ActivityOnSecondaryScreen() { + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + taskDisplayArea.getRootHomeTask().removeIfPossible(); + taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + // Create an activity on secondary display. + final TestDisplayContent secondDisplay = addNewDisplayContentAt( + DisplayContent.POSITION_TOP); + final Task rootTask = secondDisplay.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + new ActivityBuilder(mAtm).setTask(rootTask).build(); + + doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any()); + + mAtm.setBooted(true); + + // Trigger resume on all displays + mRootWindowContainer.resumeFocusedTasksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootWindowContainer).resumeHomeActivity(any(), any(), eq(taskDisplayArea)); + } + + /** + * Verify that a lingering transition is being executed in case the activity to be resumed is + * already resumed + */ + @Test + public void testResumeActivityLingeringTransition() { + // Create a root task at top. + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, false /* onTop */)); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(rootTask).setOnTop(true).build(); + activity.setState(Task.ActivityState.RESUMED, "test"); + + // Assume the task is at the topmost position + assertTrue(rootTask.isTopRootTaskInDisplayArea()); + + // Use the task as target to resume. + mRootWindowContainer.resumeFocusedTasksTopActivities(); + + // Verify the lingering app transition is being executed because it's already resumed + verify(rootTask, times(1)).executeAppTransition(any()); + } + + @Test + public void testResumeActivityLingeringTransition_notExecuted() { + // Create a root task at bottom. + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task rootTask = spy(taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, false /* onTop */)); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(rootTask).setOnTop(true).build(); + activity.setState(Task.ActivityState.RESUMED, "test"); + taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/); + + // Assume the task is at the topmost position + assertFalse(rootTask.isTopRootTaskInDisplayArea()); + doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); + + // Use the task as target to resume. + mRootWindowContainer.resumeFocusedTasksTopActivities(); + + // Verify the lingering app transition is being executed because it's already resumed + verify(rootTask, never()).executeAppTransition(any()); + } + + /** + * Tests that home activities can be started on the displays that supports system decorations. + */ + @Test + public void testStartHomeOnAllDisplays() { + mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); + mockResolveSecondaryHomeActivity(); + + // Create secondary displays. + final TestDisplayContent secondDisplay = + new TestDisplayContent.Builder(mAtm, 1000, 1500) + .setSystemDecorations(true).build(); + + doReturn(true).when(mRootWindowContainer) + .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean()); + doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), + anyBoolean()); + + mRootWindowContainer.startHomeOnAllDisplays(0, "testStartHome"); + + assertTrue(mRootWindowContainer.getDefaultDisplay().getTopRootTask().isActivityTypeHome()); + assertNotNull(secondDisplay.getTopRootTask()); + assertTrue(secondDisplay.getTopRootTask().isActivityTypeHome()); + } + + /** + * Tests that home activities won't be started before booting when display added. + */ + @Test + public void testNotStartHomeBeforeBoot() { + final int displayId = 1; + final boolean isBooting = mAtm.mAmInternal.isBooting(); + final boolean isBooted = mAtm.mAmInternal.isBooted(); + try { + mAtm.mAmInternal.setBooting(false); + mAtm.mAmInternal.setBooted(false); + mRootWindowContainer.onDisplayAdded(displayId); + verify(mRootWindowContainer, never()).startHomeOnDisplay(anyInt(), any(), anyInt()); + } finally { + mAtm.mAmInternal.setBooting(isBooting); + mAtm.mAmInternal.setBooted(isBooted); + } + } + + /** + * Tests whether home can be started if being instrumented. + */ + @Test + public void testCanStartHomeWhenInstrumented() { + final ActivityInfo info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + final WindowProcessController app = mock(WindowProcessController.class); + doReturn(app).when(mAtm).getProcessController(any(), anyInt()); + + // Can not start home if we don't want to start home while home is being instrumented. + doReturn(true).when(app).isInstrumenting(); + final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer + .getDefaultTaskDisplayArea(); + assertFalse(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, + false /* allowInstrumenting*/)); + + // Can start home for other cases. + assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, + true /* allowInstrumenting*/)); + + doReturn(false).when(app).isInstrumenting(); + assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, + false /* allowInstrumenting*/)); + assertTrue(mRootWindowContainer.canStartHomeOnDisplayArea(info, defaultTaskDisplayArea, + true /* allowInstrumenting*/)); + } + + /** + * Tests that secondary home activity should not be resolved if device is still locked. + */ + @Test + public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() { + // Create secondary displays. + final TestDisplayContent secondDisplay = + new TestDisplayContent.Builder(mAtm, 1000, 1500) + .setSystemDecorations(true).build(); + + // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false. + final int currentUser = mRootWindowContainer.mCurrentUser; + mRootWindowContainer.mCurrentUser = -1; + + mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome", + secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */); + + try { + verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any()); + } finally { + mRootWindowContainer.mCurrentUser = currentUser; + } + } + + /** + * Tests that secondary home activity should not be resolved if display does not support system + * decorations. + */ + @Test + public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() { + // Create secondary displays. + final TestDisplayContent secondDisplay = + new TestDisplayContent.Builder(mAtm, 1000, 1500) + .setSystemDecorations(false).build(); + + mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome", + secondDisplay.mDisplayId, true /* allowInstrumenting */, true /* fromHomeKey */); + + verify(mRootWindowContainer, never()).resolveSecondaryHomeActivity(anyInt(), any()); + } + + /** + * Tests that when starting {@link #ResolverActivity} for home, it should use the standard + * activity type (in a new root task) so the order of back stack won't be broken. + */ + @Test + public void testStartResolverActivityForHome() { + final ActivityInfo info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + info.applicationInfo.packageName = "android"; + info.name = ResolverActivity.class.getName(); + doReturn(info).when(mRootWindowContainer).resolveHomeActivity(anyInt(), any()); + + mRootWindowContainer.startHomeOnDisplay(0 /* userId */, "test", DEFAULT_DISPLAY); + final ActivityRecord resolverActivity = mRootWindowContainer.topRunningActivity(); + + assertEquals(info, resolverActivity.info); + assertEquals(ACTIVITY_TYPE_STANDARD, resolverActivity.getRootTask().getActivityType()); + } + + /** + * Tests that secondary home should be selected if primary home not set. + */ + @Test + public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSet() { + // Setup: primary home not set. + final Intent primaryHomeIntent = mAtm.getHomeIntent(); + final ActivityInfo aInfoPrimary = new ActivityInfo(); + aInfoPrimary.name = ResolverActivity.class.getName(); + doReturn(aInfoPrimary).when(mRootWindowContainer).resolveHomeActivity(anyInt(), + refEq(primaryHomeIntent)); + // Setup: set secondary home. + mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */); + + // Run the test. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer + .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); + final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/); + assertEquals(aInfoSecondary.name, resolvedInfo.first.name); + assertEquals(aInfoSecondary.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + } + + /** + * Tests that the default secondary home activity is always picked when it is in forced by + * config_useSystemProvidedLauncherForSecondary. + */ + @Test + public void testResolveSecondaryHomeActivityForced() { + // SetUp: set primary home. + mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); + // SetUp: set secondary home and force it. + mockResolveHomeActivity(false /* primaryHome */, true /* forceSystemProvided */); + final Intent secondaryHomeIntent = + mAtm.getSecondaryHomeIntent(null /* preferredPackage */); + final List<ResolveInfo> resolutions = new ArrayList<>(); + final ResolveInfo resolveInfo = new ResolveInfo(); + final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/); + resolveInfo.activityInfo = aInfoSecondary; + resolutions.add(resolveInfo); + doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), + refEq(secondaryHomeIntent)); + doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), + anyBoolean()); + + // Run the test. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer + .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); + assertEquals(aInfoSecondary.name, resolvedInfo.first.name); + assertEquals(aInfoSecondary.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + } + + /** + * Tests that secondary home should be selected if primary home not support secondary displays + * or there is no matched activity in the same package as selected primary home. + */ + @Test + public void testResolveSecondaryHomeActivityWhenPrimaryHomeNotSupportMultiDisplay() { + // Setup: there is no matched activity in the same package as selected primary home. + mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); + final List<ResolveInfo> resolutions = new ArrayList<>(); + doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any()); + // Setup: set secondary home. + mockResolveHomeActivity(false /* primaryHome */, false /* forceSystemProvided */); + + // Run the test. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer + .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); + final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false /* primaryHome*/); + assertEquals(aInfoSecondary.name, resolvedInfo.first.name); + assertEquals(aInfoSecondary.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + } + /** + * Tests that primary home activity should be selected if it already support secondary displays. + */ + @Test + public void testResolveSecondaryHomeActivityWhenPrimaryHomeSupportMultiDisplay() { + // SetUp: set primary home. + mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); + // SetUp: put primary home info on 2nd item + final List<ResolveInfo> resolutions = new ArrayList<>(); + final ResolveInfo infoFake1 = new ResolveInfo(); + infoFake1.activityInfo = new ActivityInfo(); + infoFake1.activityInfo.name = "fakeActivity1"; + infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; + final ResolveInfo infoFake2 = new ResolveInfo(); + final ActivityInfo aInfoPrimary = getFakeHomeActivityInfo(true /* primaryHome */); + infoFake2.activityInfo = aInfoPrimary; + resolutions.add(infoFake1); + resolutions.add(infoFake2); + doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any()); + + doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), + anyBoolean()); + + // Run the test. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer + .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); + assertEquals(aInfoPrimary.name, resolvedInfo.first.name); + assertEquals(aInfoPrimary.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + } + + /** + * Tests that the first one that matches should be selected if there are multiple activities. + */ + @Test + public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() { + // SetUp: set primary home. + mockResolveHomeActivity(true /* primaryHome */, false /* forceSystemProvided */); + // Setup: prepare two eligible activity info. + final List<ResolveInfo> resolutions = new ArrayList<>(); + final ResolveInfo infoFake1 = new ResolveInfo(); + infoFake1.activityInfo = new ActivityInfo(); + infoFake1.activityInfo.name = "fakeActivity1"; + infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; + final ResolveInfo infoFake2 = new ResolveInfo(); + infoFake2.activityInfo = new ActivityInfo(); + infoFake2.activityInfo.name = "fakeActivity2"; + infoFake2.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake2.activityInfo.applicationInfo.packageName = "fakePackage2"; + resolutions.add(infoFake1); + resolutions.add(infoFake2); + doReturn(resolutions).when(mRootWindowContainer).resolveActivities(anyInt(), any()); + + doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), + anyBoolean()); + + // Use the first one of matched activities in the same package as selected primary home. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootWindowContainer + .resolveSecondaryHomeActivity(0 /* userId */, mock(TaskDisplayArea.class)); + + assertEquals(infoFake1.activityInfo.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name); + } + + /** + * Test that {@link RootWindowContainer#getLaunchRootTask} with the real caller id will get the + * expected root task when requesting the activity launch on the secondary display. + */ + @Test + public void testGetLaunchRootTaskWithRealCallerId() { + // Create a non-system owned virtual display. + final TestDisplayContent secondaryDisplay = + new TestDisplayContent.Builder(mAtm, 1000, 1500) + .setType(TYPE_VIRTUAL).setOwnerUid(100).build(); + + // Create an activity with specify the original launch pid / uid. + final ActivityRecord r = new ActivityBuilder(mAtm).setLaunchedFromPid(200) + .setLaunchedFromUid(200).build(); + + // Simulate ActivityStarter to find a launch root task for requesting the activity to launch + // on the secondary display with realCallerId. + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(secondaryDisplay.mDisplayId); + options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); + doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId, + 300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info); + final Task result = mRootWindowContainer.getLaunchRootTask(r, options, + null /* task */, true /* onTop */, null, 300 /* test realCallerPid */, + 300 /* test realCallerUid */); + + // Assert that the root task is returned as expected. + assertNotNull(result); + assertEquals("The display ID of the root task should same as secondary display ", + secondaryDisplay.mDisplayId, result.getDisplayId()); + } + + @Test + public void testGetValidLaunchRootTaskOnDisplayWithCandidateRootTask() { + // Create a root task with an activity on secondary display. + final TestDisplayContent secondaryDisplay = new TestDisplayContent.Builder(mAtm, 300, + 600).build(); + final Task task = new TaskBuilder(mSupervisor) + .setDisplay(secondaryDisplay).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); + + // Make sure the root task is valid and can be reused on default display. + final Task rootTask = mRootWindowContainer.getValidLaunchRootTaskInTaskDisplayArea( + mRootWindowContainer.getDefaultTaskDisplayArea(), activity, task, + null /* options */, null /* launchParams */); + assertEquals(task, rootTask); + } + + @Test + public void testSwitchUser_missingHomeRootTask() { + final Task fullscreenTask = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + doReturn(fullscreenTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask(); + + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + Task rootHomeTask = taskDisplayArea.getRootHomeTask(); + if (rootHomeTask != null) { + rootHomeTask.removeImmediately(); + } + assertNull(taskDisplayArea.getRootHomeTask()); + + int currentUser = mRootWindowContainer.mCurrentUser; + int otherUser = currentUser + 1; + + mRootWindowContainer.switchUser(otherUser, null); + + assertNotNull(taskDisplayArea.getRootHomeTask()); + assertEquals(taskDisplayArea.getTopRootTask(), taskDisplayArea.getRootHomeTask()); + } + + /** + * Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity + * info for test cases. + * + * @param primaryHome Indicate to use primary home intent as parameter, otherwise, use + * secondary home intent. + * @param forceSystemProvided Indicate to force using system provided home activity. + */ + private void mockResolveHomeActivity(boolean primaryHome, boolean forceSystemProvided) { + ActivityInfo targetActivityInfo = getFakeHomeActivityInfo(primaryHome); + Intent targetIntent; + if (primaryHome) { + targetIntent = mAtm.getHomeIntent(); + } else { + Resources resources = mContext.getResources(); + spyOn(resources); + doReturn(targetActivityInfo.applicationInfo.packageName).when(resources).getString( + com.android.internal.R.string.config_secondaryHomePackage); + doReturn(forceSystemProvided).when(resources).getBoolean( + com.android.internal.R.bool.config_useSystemProvidedLauncherForSecondary); + targetIntent = mAtm.getSecondaryHomeIntent(null /* preferredPackage */); + } + doReturn(targetActivityInfo).when(mRootWindowContainer).resolveHomeActivity(anyInt(), + refEq(targetIntent)); + } + + /** + * Mock {@link RootWindowContainer#resolveSecondaryHomeActivity} for returning consistent + * activity info for test cases. + */ + private void mockResolveSecondaryHomeActivity() { + final Intent secondaryHomeIntent = mAtm + .getSecondaryHomeIntent(null /* preferredPackage */); + final ActivityInfo aInfoSecondary = getFakeHomeActivityInfo(false); + doReturn(Pair.create(aInfoSecondary, secondaryHomeIntent)).when(mRootWindowContainer) + .resolveSecondaryHomeActivity(anyInt(), any()); + } + + private ActivityInfo getFakeHomeActivityInfo(boolean primaryHome) { + final ActivityInfo aInfo = new ActivityInfo(); + aInfo.name = primaryHome ? "fakeHomeActivity" : "fakeSecondaryHomeActivity"; + aInfo.applicationInfo = new ApplicationInfo(); + aInfo.applicationInfo.packageName = + primaryHome ? "fakeHomePackage" : "fakeSecondaryHomePackage"; + return aInfo; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index eba5634e4355..92d4edec85f4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -28,6 +28,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -35,13 +36,16 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.Task.ActivityState.RESUMED; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; @@ -56,8 +60,6 @@ import androidx.test.filters.SmallTest; import com.android.server.wm.LaunchParamsController.LaunchParams; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,35 +74,17 @@ import org.junit.runner.RunWith; @RunWith(WindowTestRunner.class) public class TaskDisplayAreaTests extends WindowTestsBase { - private Task mPinnedTask; - - @Before - public void setUp() throws Exception { - mPinnedTask = createTaskStackOnDisplay( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent); - // Stack should contain visible app window to be considered visible. - assertFalse(mPinnedTask.isVisible()); - final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent); - mPinnedTask.addChild(pinnedApp, 0 /* addPos */); - assertTrue(mPinnedTask.isVisible()); - } - - @After - public void tearDown() throws Exception { - mPinnedTask.removeImmediately(); - } - @Test public void getOrCreateLaunchRootRespectsResolvedWindowingMode() { - final Task rootTask = createTaskStackOnDisplay( - WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task rootTask = createTask( + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); rootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); taskDisplayArea.setLaunchRootTask( rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD}); - final Task candidateRootTask = createTaskStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task candidateRootTask = createTask( + mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent); final LaunchParams launchParams = new LaunchParams(); launchParams.mWindowingMode = WINDOWING_MODE_FREEFORM; @@ -113,15 +97,15 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test public void getOrCreateLaunchRootUsesActivityOptionsWindowingMode() { - final Task rootTask = createTaskStackOnDisplay( - WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task rootTask = createTask( + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); rootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); taskDisplayArea.setLaunchRootTask( rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD}); - final Task candidateRootTask = createTaskStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task candidateRootTask = createTask( + mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent); final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); @@ -134,9 +118,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test public void testActivityWithZBoost_taskDisplayAreaDoesNotMoveUp() { - final Task stack = createTaskStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent); task.addChild(activity, 0 /* addPos */); final TaskDisplayArea taskDisplayArea = activity.getDisplayArea(); @@ -152,87 +135,103 @@ public class TaskDisplayAreaTests extends WindowTestsBase { } @Test - public void testStackPositionChildAt() { - // Test that always-on-top stack can't be moved to position other than top. - final Task stack1 = createTaskStackOnDisplay(mDisplayContent); - final Task stack2 = createTaskStackOnDisplay(mDisplayContent); - - final WindowContainer taskStackContainer = stack1.getParent(); - - final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1); - final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2); - final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask); - assertThat(pinnedStackPos).isGreaterThan(stack2Pos); - assertThat(stack2Pos).isGreaterThan(stack1Pos); - - taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedTask, false); - assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); - assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask); - - taskStackContainer.positionChildAt(1, mPinnedTask, false); - assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); - assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask); + public void testRootTaskPositionChildAt() { + Task pinnedTask = createTask( + mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); + // Root task should contain visible app window to be considered visible. + assertFalse(pinnedTask.isVisible()); + final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent); + pinnedTask.addChild(pinnedApp, 0 /* addPos */); + assertTrue(pinnedTask.isVisible()); + + // Test that always-on-top root task can't be moved to position other than top. + final Task rootTask1 = createTask(mDisplayContent); + final Task rootTask2 = createTask(mDisplayContent); + + final WindowContainer taskContainer = rootTask1.getParent(); + + final int rootTask1Pos = taskContainer.mChildren.indexOf(rootTask1); + final int rootTask2Pos = taskContainer.mChildren.indexOf(rootTask2); + final int pinnedTaskPos = taskContainer.mChildren.indexOf(pinnedTask); + assertThat(pinnedTaskPos).isGreaterThan(rootTask2Pos); + assertThat(rootTask2Pos).isGreaterThan(rootTask1Pos); + + taskContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedTask, false); + assertEquals(taskContainer.mChildren.get(rootTask1Pos), rootTask1); + assertEquals(taskContainer.mChildren.get(rootTask2Pos), rootTask2); + assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask); + + taskContainer.positionChildAt(1, pinnedTask, false); + assertEquals(taskContainer.mChildren.get(rootTask1Pos), rootTask1); + assertEquals(taskContainer.mChildren.get(rootTask2Pos), rootTask2); + assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask); } @Test - public void testStackPositionBelowPinnedStack() { - // Test that no stack can be above pinned stack. - final Task stack1 = createTaskStackOnDisplay(mDisplayContent); + public void testRootTaskPositionBelowPinnedRootTask() { + Task pinnedTask = createTask( + mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); + // Root task should contain visible app window to be considered visible. + assertFalse(pinnedTask.isVisible()); + final ActivityRecord pinnedApp = createNonAttachedActivityRecord(mDisplayContent); + pinnedTask.addChild(pinnedApp, 0 /* addPos */); + assertTrue(pinnedTask.isVisible()); + + // Test that no root task can be above pinned root task. + final Task rootTask1 = createTask(mDisplayContent); - final WindowContainer taskStackContainer = stack1.getParent(); + final WindowContainer taskContainer = rootTask1.getParent(); - final int stackPos = taskStackContainer.mChildren.indexOf(stack1); - final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask); - assertThat(pinnedStackPos).isGreaterThan(stackPos); + final int rootTaskPos = taskContainer.mChildren.indexOf(rootTask1); + final int pinnedTaskPos = taskContainer.mChildren.indexOf(pinnedTask); + assertThat(pinnedTaskPos).isGreaterThan(rootTaskPos); - taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false); - assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask); + taskContainer.positionChildAt(WindowContainer.POSITION_TOP, rootTask1, false); + assertEquals(taskContainer.mChildren.get(rootTaskPos), rootTask1); + assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask); - taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false); - assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask); + taskContainer.positionChildAt(taskContainer.mChildren.size() - 1, rootTask1, false); + assertEquals(taskContainer.mChildren.get(rootTaskPos), rootTask1); + assertEquals(taskContainer.mChildren.get(pinnedTaskPos), pinnedTask); } @Test - public void testDisplayPositionWithPinnedStack() { - // Make sure the display is trusted display which capable to move the stack to top. + public void testDisplayPositionWithPinnedRootTask() { + // Make sure the display is trusted display which capable to move the root task to top. spyOn(mDisplayContent); doReturn(true).when(mDisplayContent).isTrusted(); - // Allow child stack to move to top. + // Allow child root task to move to top. mDisplayContent.mDontMoveToTop = false; - // The display contains pinned stack that was added in {@link #setUp}. - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); + // The display contains pinned root task that was added in {@link #setUp}. + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); // Add another display at top. mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(), false /* includingParents */); // Move the task of {@code mDisplayContent} to top. - stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */); - final int indexOfDisplayWithPinnedStack = mWm.mRoot.mChildren.indexOf(mDisplayContent); + rootTask.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */); + final int indexOfDisplayWithPinnedRootTask = mWm.mRoot.mChildren.indexOf(mDisplayContent); assertEquals("The testing DisplayContent should be moved to top with task", - mWm.mRoot.getChildCount() - 1, indexOfDisplayWithPinnedStack); + mWm.mRoot.getChildCount() - 1, indexOfDisplayWithPinnedRootTask); } @Test public void testMovingChildTaskOnTop() { - // Make sure the display is trusted display which capable to move the stack to top. + // Make sure the display is trusted display which capable to move the root task to top. spyOn(mDisplayContent); doReturn(true).when(mDisplayContent).isTrusted(); - // Allow child stack to move to top. + // Allow child root task to move to top. mDisplayContent.mDontMoveToTop = false; - // The display contains pinned stack that was added in {@link #setUp}. - Task stack = createTaskStackOnDisplay(mDisplayContent); - Task task = createTaskInStack(stack, 0 /* userId */); + // The display contains pinned root task that was added in {@link #setUp}. + Task rootTask = createTask(mDisplayContent); + Task task = createTaskInRootTask(rootTask, 0 /* userId */); // Add another display at top. mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(), @@ -243,7 +242,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mWm.mRoot.getChildCount() - 2, mWm.mRoot.mChildren.indexOf(mDisplayContent)); // Move the task of {@code mDisplayContent} to top. - stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */); + rootTask.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */); // Ensure that original display ({@code mDisplayContent}) is now on the top. assertEquals("The testing DisplayContent should be moved to top with task", @@ -252,16 +251,16 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test public void testDontMovingChildTaskOnTop() { - // Make sure the display is trusted display which capable to move the stack to top. + // Make sure the display is trusted display which capable to move the root task to top. spyOn(mDisplayContent); doReturn(true).when(mDisplayContent).isTrusted(); - // Allow child stack to move to top. + // Allow child root task to move to top. mDisplayContent.mDontMoveToTop = true; - // The display contains pinned stack that was added in {@link #setUp}. - Task stack = createTaskStackOnDisplay(mDisplayContent); - Task task = createTaskInStack(stack, 0 /* userId */); + // The display contains pinned root task that was added in {@link #setUp}. + Task rootTask = createTask(mDisplayContent); + Task task = createTaskInRootTask(rootTask, 0 /* userId */); // Add another display at top. mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(), @@ -272,7 +271,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mWm.mRoot.getChildCount() - 2, mWm.mRoot.mChildren.indexOf(mDisplayContent)); // Try moving the task of {@code mDisplayContent} to top. - stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */); + rootTask.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */); // Ensure that original display ({@code mDisplayContent}) hasn't moved and is not // on the top. @@ -282,8 +281,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test public void testReuseTaskAsRootTask() { - final Task candidateTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task candidateTask = createTask(mDisplayContent); final int type = ACTIVITY_TYPE_STANDARD; assertGetOrCreateRootTask(WINDOWING_MODE_FULLSCREEN, type, candidateTask, true /* reuseCandidate */); @@ -312,7 +310,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { } @Test - public void testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange() { + public void testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange() { final RootWindowContainer rootWindowContainer = mWm.mAtmService.mRootWindowContainer; final TaskDisplayArea defaultTaskDisplayArea = rootWindowContainer.getDefaultTaskDisplayArea(); @@ -330,7 +328,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { ActivityRecord primarySplitActivity = primarySplitTask.getTopNonFinishingActivity(); assertNotNull(primarySplitActivity); primarySplitActivity.setState(RESUMED, - "testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange"); + "testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange"); ActivityRecord homeActivity = rootHomeTask.getTopNonFinishingActivity(); if (homeActivity == null) { @@ -350,14 +348,14 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST); - final Task firstStack = firstTaskDisplayArea.createRootTask( + final Task firstRootTask = firstTaskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final Task secondStack = secondTaskDisplayArea.createRootTask( + final Task secondRootTask = secondTaskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mAtm) - .setTask(firstStack).build(); + .setTask(firstRootTask).build(); final ActivityRecord secondActivity = new ActivityBuilder(mAtm) - .setTask(secondStack).build(); + .setTask(secondRootTask).build(); // Activity on TDA1 is focused mDisplayContent.setFocusedApp(firstActivity); @@ -384,14 +382,14 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", FEATURE_VENDOR_FIRST); - final Task firstStack = firstTaskDisplayArea.createRootTask( + final Task firstRootTask = firstTaskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final Task secondStack = secondTaskDisplayArea.createRootTask( + final Task secondRootTask = secondTaskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mAtm) - .setTask(firstStack).build(); + .setTask(firstRootTask).build(); final ActivityRecord secondActivity = new ActivityBuilder(mAtm) - .setTask(secondStack).build(); + .setTask(secondRootTask).build(); firstTaskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); secondTaskDisplayArea.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); @@ -411,9 +409,9 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test public void testIgnoreOrientationRequest() { final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final Task stack = taskDisplayArea.createRootTask( + final Task task = taskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); mDisplayContent.setFocusedApp(activity); activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); @@ -428,7 +426,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test @UseTestDisplay public void testRemove_reparentToDefault() { - final Task task = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTask(mDisplayContent); final TaskDisplayArea displayArea = task.getDisplayArea(); displayArea.remove(); assertTrue(displayArea.isRemoved()); @@ -442,8 +440,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test @UseTestDisplay - public void testRemove_stackCreatedByOrganizer() { - final Task task = createTaskStackOnDisplay(mDisplayContent); + public void testRemove_rootTaskCreatedByOrganizer() { + final Task task = createTask(mDisplayContent); task.mCreatedByOrganizer = true; final TaskDisplayArea displayArea = task.getDisplayArea(); displayArea.remove(); @@ -464,4 +462,221 @@ public class TaskDisplayAreaTests extends WindowTestsBase { null /* activityOptions */); assertEquals(reuseCandidate, rootTask == candidateTask); } + + @Test + public void testGetOrCreateRootHomeTask_defaultDisplay() { + TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea(); + + // Remove the current home root task if it exists so a new one can be created below. + Task homeTask = defaultTaskDisplayArea.getRootHomeTask(); + if (homeTask != null) { + defaultTaskDisplayArea.removeChild(homeTask); + } + assertNull(defaultTaskDisplayArea.getRootHomeTask()); + + assertNotNull(defaultTaskDisplayArea.getOrCreateRootHomeTask()); + } + + @Test + public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() { + DisplayContent display = createNewDisplay(); + doReturn(true).when(display).supportsSystemDecorations(); + + // Remove the current home root task if it exists so a new one can be created below. + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); + Task homeTask = taskDisplayArea.getRootHomeTask(); + if (homeTask != null) { + taskDisplayArea.removeChild(homeTask); + } + assertNull(taskDisplayArea.getRootHomeTask()); + + assertNotNull(taskDisplayArea.getOrCreateRootHomeTask()); + } + + @Test + public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() { + DisplayContent display = createNewDisplay(); + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); + doReturn(false).when(display).supportsSystemDecorations(); + + assertNull(taskDisplayArea.getRootHomeTask()); + assertNull(taskDisplayArea.getOrCreateRootHomeTask()); + } + + @Test + public void testGetOrCreateRootHomeTask_untrustedDisplay() { + DisplayContent display = createNewDisplay(); + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); + doReturn(false).when(display).isTrusted(); + + assertNull(taskDisplayArea.getRootHomeTask()); + assertNull(taskDisplayArea.getOrCreateRootHomeTask()); + } + + @Test + public void testGetOrCreateRootHomeTask_dontMoveToTop() { + DisplayContent display = createNewDisplay(); + display.mDontMoveToTop = true; + TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); + + assertNull(taskDisplayArea.getRootHomeTask()); + assertNull(taskDisplayArea.getOrCreateRootHomeTask()); + } + + @Test + public void testLastFocusedRootTaskIsUpdatedWhenMovingRootTask() { + // Create a root task at bottom. + final TaskDisplayArea taskDisplayAreas = + mRootWindowContainer.getDefaultDisplay().getDefaultTaskDisplayArea(); + final Task rootTask = + new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build(); + final Task prevFocusedRootTask = taskDisplayAreas.getFocusedRootTask(); + + rootTask.moveToFront("moveRootTaskToFront"); + // After moving the root task to front, the previous focused should be the last focused. + assertTrue(rootTask.isFocusedRootTaskOnDisplay()); + assertEquals(prevFocusedRootTask, taskDisplayAreas.getLastFocusedRootTask()); + + rootTask.moveToBack("moveRootTaskToBack", null /* task */); + // After moving the root task to back, the root task should be the last focused. + assertEquals(rootTask, taskDisplayAreas.getLastFocusedRootTask()); + } + + /** + * This test simulates the picture-in-picture menu activity launches an activity to fullscreen + * root task. The fullscreen root task should be the top focused for resuming correctly. + */ + @Test + public void testFullscreenRootTaskCanBeFocusedWhenFocusablePinnedRootTaskExists() { + // Create a pinned root task and move to front. + final Task pinnedRootTask = mRootWindowContainer.getDefaultTaskDisplayArea() + .createRootTask(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP); + final Task pinnedTask = new TaskBuilder(mAtm.mTaskSupervisor) + .setParentTask(pinnedRootTask).build(); + new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE) + .setTask(pinnedTask).build(); + pinnedRootTask.moveToFront("movePinnedRootTaskToFront"); + + // The focused root task should be the pinned root task. + assertTrue(pinnedRootTask.isFocusedRootTaskOnDisplay()); + + // Create a fullscreen root task and move to front. + final Task fullscreenRootTask = createTaskWithActivity( + mRootWindowContainer.getDefaultTaskDisplayArea(), + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true); + fullscreenRootTask.moveToFront("moveFullscreenRootTaskToFront"); + + // The focused root task should be the fullscreen root task. + assertTrue(fullscreenRootTask.isFocusedRootTaskOnDisplay()); + } + + /** + * Test {@link TaskDisplayArea#mPreferredTopFocusableRootTask} will be cleared when + * the root task is removed or moved to back, and the focused root task will be according to + * z-order. + */ + @Test + public void testRootTaskShouldNotBeFocusedAfterMovingToBackOrRemoving() { + // Create a display which only contains 2 root task. + final DisplayContent display = addNewDisplayContentAt(POSITION_TOP); + final Task rootTask1 = createTaskWithActivity(display.getDefaultTaskDisplayArea(), + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true /* twoLevelTask */); + final Task rootTask2 = createTaskWithActivity(display.getDefaultTaskDisplayArea(), + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true /* twoLevelTask */); + + // Put rootTask1 and rootTask2 on top. + rootTask1.moveToFront("moveRootTask1ToFront"); + rootTask2.moveToFront("moveRootTask2ToFront"); + assertTrue(rootTask2.isFocusedRootTaskOnDisplay()); + + // rootTask1 should be focused after moving rootTask2 to back. + rootTask2.moveToBack("moveRootTask2ToBack", null /* task */); + assertTrue(rootTask1.isFocusedRootTaskOnDisplay()); + + // rootTask2 should be focused after removing rootTask1. + rootTask1.getDisplayArea().removeRootTask(rootTask1); + assertTrue(rootTask2.isFocusedRootTaskOnDisplay()); + } + + /** + * This test enforces that alwaysOnTop root task is placed at proper position. + */ + @Test + public void testAlwaysOnTopRootTaskLocation() { + final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); + final Task alwaysOnTopRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(alwaysOnTopRootTask).build(); + alwaysOnTopRootTask.setAlwaysOnTop(true); + taskDisplayArea.positionChildAt(POSITION_TOP, alwaysOnTopRootTask, + false /* includingParents */); + assertTrue(alwaysOnTopRootTask.isAlwaysOnTop()); + // Ensure always on top state is synced to the children of the root task. + assertTrue(alwaysOnTopRootTask.getTopNonFinishingActivity().isAlwaysOnTop()); + assertEquals(alwaysOnTopRootTask, taskDisplayArea.getTopRootTask()); + + final Task pinnedRootTask = taskDisplayArea.createRootTask( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + assertEquals(pinnedRootTask, taskDisplayArea.getRootPinnedTask()); + assertEquals(pinnedRootTask, taskDisplayArea.getTopRootTask()); + + final Task anotherAlwaysOnTopRootTask = taskDisplayArea.createRootTask( + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); + anotherAlwaysOnTopRootTask.setAlwaysOnTop(true); + taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopRootTask, + false /* includingParents */); + assertTrue(anotherAlwaysOnTopRootTask.isAlwaysOnTop()); + int topPosition = taskDisplayArea.getRootTaskCount() - 1; + // Ensure the new alwaysOnTop root task is put below the pinned root task, but on top of the + // existing alwaysOnTop root task. + assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask)); + + final Task nonAlwaysOnTopRootTask = taskDisplayArea.createRootTask( + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); + assertEquals(taskDisplayArea, nonAlwaysOnTopRootTask.getDisplayArea()); + topPosition = taskDisplayArea.getRootTaskCount() - 1; + // Ensure the non-alwaysOnTop root task is put below the three alwaysOnTop root tasks, but + // above the existing other non-alwaysOnTop root tasks. + assertEquals(topPosition - 3, getTaskIndexOf(taskDisplayArea, nonAlwaysOnTopRootTask)); + + anotherAlwaysOnTopRootTask.setAlwaysOnTop(false); + taskDisplayArea.positionChildAt(POSITION_TOP, anotherAlwaysOnTopRootTask, + false /* includingParents */); + assertFalse(anotherAlwaysOnTopRootTask.isAlwaysOnTop()); + // Ensure, when always on top is turned off for a root task, the root task is put just below + // all other always on top root tasks. + assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask)); + anotherAlwaysOnTopRootTask.setAlwaysOnTop(true); + + // Ensure always on top state changes properly when windowing mode changes. + anotherAlwaysOnTopRootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertFalse(anotherAlwaysOnTopRootTask.isAlwaysOnTop()); + assertEquals(topPosition - 2, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask)); + anotherAlwaysOnTopRootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertTrue(anotherAlwaysOnTopRootTask.isAlwaysOnTop()); + assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, anotherAlwaysOnTopRootTask)); + + final Task dreamRootTask = taskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, true /* onTop */); + assertEquals(taskDisplayArea, dreamRootTask.getDisplayArea()); + assertTrue(dreamRootTask.isAlwaysOnTop()); + topPosition = taskDisplayArea.getRootTaskCount() - 1; + // Ensure dream shows above all activities, including PiP + assertEquals(dreamRootTask, taskDisplayArea.getTopRootTask()); + assertEquals(topPosition - 1, getTaskIndexOf(taskDisplayArea, pinnedRootTask)); + + final Task assistRootTask = taskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); + assertEquals(taskDisplayArea, assistRootTask.getDisplayArea()); + assertFalse(assistRootTask.isAlwaysOnTop()); + topPosition = taskDisplayArea.getRootTaskCount() - 1; + + // Ensure Assistant shows as a non-always-on-top activity when config_assistantOnTopOfDream + // is false and on top of everything when true. + final boolean isAssistantOnTop = mContext.getResources() + .getBoolean(com.android.internal.R.bool.config_assistantOnTopOfDream); + assertEquals(isAssistantOnTop ? topPosition : topPosition - 4, + getTaskIndexOf(taskDisplayArea, assistRootTask)); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index ed5729400a39..de4c40df1c79 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -403,8 +403,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { public void testOverridesDisplayAreaWithStandardTypeAndFullscreenMode() { final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay, mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); - final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); launchRoot.mCreatedByOrganizer = true; secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FULLSCREEN }, @@ -419,8 +419,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { public void testOverridesDisplayAreaWithHomeTypeAndFullscreenMode() { final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay, mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); - final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); launchRoot.mCreatedByOrganizer = true; mActivity.setActivityType(ACTIVITY_TYPE_HOME); @@ -438,8 +438,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { WINDOWING_MODE_FREEFORM); final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(freeformDisplay, mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); - final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); launchRoot.mCreatedByOrganizer = true; secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM }, @@ -455,8 +455,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayArea() { final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay, mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); - final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); launchRoot.mCreatedByOrganizer = true; secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FULLSCREEN }, @@ -481,8 +481,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0, DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height()); - final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); launchRoot.mCreatedByOrganizer = true; secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM }, new int[] { ACTIVITY_TYPE_STANDARD }); @@ -512,8 +512,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST); secondaryDisplayArea.setBounds(DISPLAY_BOUNDS.width() / 2, 0, DISPLAY_BOUNDS.width(), DISPLAY_BOUNDS.height()); - final Task launchRoot = createTaskStackOnTaskDisplayArea(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, secondaryDisplayArea); + final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); launchRoot.mCreatedByOrganizer = true; secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FREEFORM }, new int[] { ACTIVITY_TYPE_STANDARD }); @@ -1687,16 +1687,16 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } private ActivityRecord createSourceActivity(TestDisplayContent display) { - final Task stack = display.getDefaultTaskDisplayArea() + final Task rootTask = display.getDefaultTaskDisplayArea() .createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); - return new ActivityBuilder(mAtm).setTask(stack).build(); + return new ActivityBuilder(mAtm).setTask(rootTask).build(); } private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) { - final Task stack = display.getDefaultTaskDisplayArea() + final Task rootTask = display.getDefaultTaskDisplayArea() .createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true); - stack.setWindowingMode(WINDOWING_MODE_FREEFORM); - final Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build(); + rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); // Just work around the unnecessary adjustments for bounds. task.getWindowConfiguration().setBounds(bounds); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java deleted file mode 100644 index d853b930af11..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ /dev/null @@ -1,1161 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; -import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.util.DisplayMetrics.DENSITY_DEFAULT; -import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_90; -import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.sameInstance; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.same; - -import android.app.ActivityManager; -import android.app.TaskInfo; -import android.app.WindowConfiguration; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.IBinder; -import android.platform.test.annotations.Presubmit; -import android.util.DisplayMetrics; -import android.util.TypedXmlPullParser; -import android.util.TypedXmlSerializer; -import android.util.Xml; -import android.view.DisplayInfo; - -import androidx.test.filters.MediumTest; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; - -/** - * Tests for exercising {@link Task}. - * - * Build/Install/Run: - * atest WmTests:TaskRecordTests - */ -@MediumTest -@Presubmit -@RunWith(WindowTestRunner.class) -public class TaskRecordTests extends WindowTestsBase { - - private static final String TASK_TAG = "task"; - - private Rect mParentBounds; - - @Before - public void setUp() throws Exception { - mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/); - removeGlobalMinSizeRestriction(); - } - - @Test - public void testRestoreWindowedTask() throws Exception { - final Task expected = createTask(64); - expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100); - - final byte[] serializedBytes = serializeToBytes(expected); - final Task actual = restoreFromBytes(serializedBytes); - assertEquals(expected.mTaskId, actual.mTaskId); - assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds); - } - - /** Ensure we have no chance to modify the original intent. */ - @Test - public void testCopyBaseIntentForTaskInfo() { - final Task task = createTask(1); - task.setTaskDescription(new ActivityManager.TaskDescription()); - final TaskInfo info = task.getTaskInfo(); - - // The intent of info should be a copy so assert that they are different instances. - assertThat(info.baseIntent, not(sameInstance(task.getBaseIntent()))); - } - - @Test - public void testReturnsToHomeStack() throws Exception { - final Task task = createTask(1); - spyOn(task); - doReturn(true).when(task).hasChild(); - assertFalse(task.returnsToHomeRootTask()); - task.intent = null; - assertFalse(task.returnsToHomeRootTask()); - task.intent = new Intent(); - assertFalse(task.returnsToHomeRootTask()); - task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME); - assertTrue(task.returnsToHomeRootTask()); - } - - /** Ensures that empty bounds cause appBounds to inherit from parent. */ - @Test - public void testAppBounds_EmptyBounds() { - final Rect emptyBounds = new Rect(); - testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds, - mParentBounds); - } - - /** Ensures that bounds on freeform stacks are not clipped. */ - @Test - public void testAppBounds_FreeFormBounds() { - final Rect freeFormBounds = new Rect(mParentBounds); - freeFormBounds.offset(10, 10); - testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds, - freeFormBounds); - } - - /** Ensures that fully contained bounds are not clipped. */ - @Test - public void testAppBounds_ContainedBounds() { - final Rect insetBounds = new Rect(mParentBounds); - insetBounds.inset(5, 5, 5, 5); - testStackBoundsConfiguration( - WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds); - } - - @Test - public void testFitWithinBounds() { - final Rect parentBounds = new Rect(10, 10, 200, 200); - TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); - Task stack = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM, - ACTIVITY_TYPE_STANDARD, true /* onTop */); - Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build(); - final Configuration parentConfig = stack.getConfiguration(); - parentConfig.windowConfiguration.setBounds(parentBounds); - parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT; - - // check top and left - Rect reqBounds = new Rect(-190, -190, 0, 0); - task.setBounds(reqBounds); - // Make sure part of it is exposed - assertTrue(task.getBounds().right > parentBounds.left); - assertTrue(task.getBounds().bottom > parentBounds.top); - // Should still be more-or-less in that corner - assertTrue(task.getBounds().left <= parentBounds.left); - assertTrue(task.getBounds().top <= parentBounds.top); - - assertEquals(reqBounds.width(), task.getBounds().width()); - assertEquals(reqBounds.height(), task.getBounds().height()); - - // check bottom and right - reqBounds = new Rect(210, 210, 400, 400); - task.setBounds(reqBounds); - // Make sure part of it is exposed - assertTrue(task.getBounds().left < parentBounds.right); - assertTrue(task.getBounds().top < parentBounds.bottom); - // Should still be more-or-less in that corner - assertTrue(task.getBounds().right >= parentBounds.right); - assertTrue(task.getBounds().bottom >= parentBounds.bottom); - - assertEquals(reqBounds.width(), task.getBounds().width()); - assertEquals(reqBounds.height(), task.getBounds().height()); - } - - /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */ - @Test - public void testBoundsOnModeChangeFreeformToFullscreen() { - DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay(); - Task stack = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true) - .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - Task task = stack.getBottomMostTask(); - task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); - DisplayInfo info = new DisplayInfo(); - display.mDisplay.getDisplayInfo(info); - final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight); - final Rect freeformBounds = new Rect(fullScreenBounds); - freeformBounds.inset((int) (freeformBounds.width() * 0.2), - (int) (freeformBounds.height() * 0.2)); - task.setBounds(freeformBounds); - - assertEquals(freeformBounds, task.getBounds()); - - // FULLSCREEN inherits bounds - stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertEquals(fullScreenBounds, task.getBounds()); - assertEquals(freeformBounds, task.mLastNonFullscreenBounds); - - // FREEFORM restores bounds - stack.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertEquals(freeformBounds, task.getBounds()); - } - - /** - * Tests that a task with forced orientation has orientation-consistent bounds within the - * parent. - */ - @Test - public void testFullscreenBoundsForcedOrientation() { - final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); - final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); - final DisplayContent display = new TestDisplayContent.Builder(mAtm, - fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build(); - assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId)); - // Fix the display orientation to landscape which is the natural rotation (0) for the test - // display. - final DisplayRotation dr = display.mDisplayContent.getDisplayRotation(); - dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED); - dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0); - - final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true) - .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); - final Task task = stack.getBottomMostTask(); - final ActivityRecord root = task.getTopNonFinishingActivity(); - - assertEquals(fullScreenBounds, task.getBounds()); - - // Setting app to fixed portrait fits within parent - root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - assertEquals(root, task.getRootActivity()); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); - // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds. - assertThat(task.getBounds().height()).isLessThan(task.getBounds().width()); - assertEquals(fullScreenBounds, task.getBounds()); - - // Top activity gets used - final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(stack) - .build(); - assertEquals(top, task.getTopNonFinishingActivity()); - top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); - assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); - assertEquals(task.getBounds().width(), fullScreenBounds.width()); - - // Setting app to unspecified restores - top.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); - assertEquals(fullScreenBounds, task.getBounds()); - - // Setting app to fixed landscape and changing display - top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); - // Fix the display orientation to portrait which is 90 degrees for the test display. - dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90); - - // Fixed orientation request should be resolved on activity level. Task fills display - // bounds. - assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); - assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); - assertEquals(fullScreenBoundsPort, task.getBounds()); - - // in FREEFORM, no constraint - final Rect freeformBounds = new Rect(display.getBounds()); - freeformBounds.inset((int) (freeformBounds.width() * 0.2), - (int) (freeformBounds.height() * 0.2)); - stack.setWindowingMode(WINDOWING_MODE_FREEFORM); - task.setBounds(freeformBounds); - assertEquals(freeformBounds, task.getBounds()); - - // FULLSCREEN letterboxes bounds on activity level, no constraint on task level. - stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); - assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); - assertEquals(fullScreenBoundsPort, task.getBounds()); - - // FREEFORM restores bounds as before - stack.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertEquals(freeformBounds, task.getBounds()); - } - - @Test - public void testReportsOrientationRequestInLetterboxForOrientation() { - final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); - final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); - final DisplayContent display = new TestDisplayContent.Builder(mAtm, - fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build(); - assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId)); - // Fix the display orientation to landscape which is the natural rotation (0) for the test - // display. - final DisplayRotation dr = display.mDisplayContent.getDisplayRotation(); - dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED); - dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0); - - final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true) - .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); - final Task task = stack.getBottomMostTask(); - ActivityRecord root = task.getTopNonFinishingActivity(); - - assertEquals(fullScreenBounds, task.getBounds()); - - // Setting app to fixed portrait fits within parent on activity level. Task fills parent. - root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - assertThat(root.getBounds().width()).isLessThan(root.getBounds().height()); - assertEquals(task.getBounds(), fullScreenBounds); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation()); - } - - @Test - public void testIgnoresForcedOrientationWhenParentHandles() { - final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); - DisplayContent display = new TestDisplayContent.Builder( - mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); - - display.getRequestedOverrideConfiguration().orientation = - Configuration.ORIENTATION_LANDSCAPE; - display.onRequestedOverrideConfigurationChanged( - display.getRequestedOverrideConfiguration()); - Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true) - .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); - Task task = stack.getBottomMostTask(); - ActivityRecord root = task.getTopNonFinishingActivity(); - - final WindowContainer parentWindowContainer = - new WindowContainer(mSystemServicesTestRule.getWindowManagerService()); - spyOn(parentWindowContainer); - parentWindowContainer.setBounds(fullScreenBounds); - doReturn(parentWindowContainer).when(task).getParent(); - doReturn(display.getDefaultTaskDisplayArea()).when(task).getDisplayArea(); - doReturn(stack).when(task).getRootTask(); - doReturn(true).when(parentWindowContainer).handlesOrientationChangeFromDescendant(); - - // Setting app to fixed portrait fits within parent, but Task shouldn't adjust the - // bounds because its parent says it will handle it at a later time. - root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - assertEquals(root, task.getRootActivity()); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); - assertEquals(fullScreenBounds, task.getBounds()); - } - - @Test - public void testComputeConfigResourceOverrides() { - final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920); - TestDisplayContent display = new TestDisplayContent.Builder( - mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); - final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); - final Configuration inOutConfig = new Configuration(); - final Configuration parentConfig = new Configuration(); - final int longSide = 1200; - final int shortSide = 600; - final Rect parentBounds = new Rect(0, 0, 250, 500); - final Rect parentAppBounds = new Rect(0, 0, 250, 480); - parentConfig.windowConfiguration.setBounds(parentBounds); - parentConfig.windowConfiguration.setAppBounds(parentAppBounds); - parentConfig.densityDpi = 400; - parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200 - parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100 - parentConfig.windowConfiguration.setRotation(ROTATION_0); - - // By default, the input bounds will fill parent. - task.computeConfigResourceOverrides(inOutConfig, parentConfig); - - assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp); - assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp); - assertEquals(parentAppBounds, inOutConfig.windowConfiguration.getAppBounds()); - assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation); - - // If bounds are overridden, config properties should be made to match. Surface hierarchy - // will crop for policy. - inOutConfig.setToDefaults(); - final Rect largerPortraitBounds = new Rect(0, 0, shortSide, longSide); - inOutConfig.windowConfiguration.setBounds(largerPortraitBounds); - task.computeConfigResourceOverrides(inOutConfig, parentConfig); - // The override bounds are beyond the parent, the out appBounds should not be intersected - // by parent appBounds. - assertEquals(largerPortraitBounds, inOutConfig.windowConfiguration.getAppBounds()); - assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160); - assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160); - - inOutConfig.setToDefaults(); - // Landscape bounds. - final Rect largerLandscapeBounds = new Rect(0, 0, longSide, shortSide); - inOutConfig.windowConfiguration.setBounds(largerLandscapeBounds); - - // Setup the display with a top stable inset. The later assertion will ensure the inset is - // excluded from screenHeightDp. - final int statusBarHeight = 100; - final DisplayPolicy policy = display.getDisplayPolicy(); - doAnswer(invocationOnMock -> { - final Rect insets = invocationOnMock.<Rect>getArgument(0); - insets.top = statusBarHeight; - return null; - }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0)); - - // Without limiting to be inside the parent bounds, the out screen size should keep relative - // to the input bounds. - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); - final ActivityRecord.CompatDisplayInsets compatIntsets = - new ActivityRecord.CompatDisplayInsets( - display, activity, /* fixedOrientationBounds= */ null); - task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets); - - assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); - assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi, - inOutConfig.screenHeightDp); - assertEquals(longSide * DENSITY_DEFAULT / parentConfig.densityDpi, - inOutConfig.screenWidthDp); - assertEquals(Configuration.ORIENTATION_LANDSCAPE, inOutConfig.orientation); - } - - @Test - public void testComputeConfigResourceLayoutOverrides() { - final Rect fullScreenBounds = new Rect(0, 0, 1000, 2500); - TestDisplayContent display = new TestDisplayContent.Builder( - mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); - final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); - final Configuration inOutConfig = new Configuration(); - final Configuration parentConfig = new Configuration(); - final Rect nonLongBounds = new Rect(0, 0, 1000, 1250); - parentConfig.windowConfiguration.setBounds(fullScreenBounds); - parentConfig.windowConfiguration.setAppBounds(fullScreenBounds); - parentConfig.densityDpi = 400; - parentConfig.screenHeightDp = (fullScreenBounds.bottom * 160) / parentConfig.densityDpi; - parentConfig.screenWidthDp = (fullScreenBounds.right * 160) / parentConfig.densityDpi; - parentConfig.windowConfiguration.setRotation(ROTATION_0); - - // Set BOTH screenW/H to an override value - inOutConfig.screenWidthDp = nonLongBounds.width() * 160 / parentConfig.densityDpi; - inOutConfig.screenHeightDp = nonLongBounds.height() * 160 / parentConfig.densityDpi; - task.computeConfigResourceOverrides(inOutConfig, parentConfig); - - // screenLayout should honor override when both screenW/H are set. - assertTrue((inOutConfig.screenLayout & Configuration.SCREENLAYOUT_LONG_NO) != 0); - } - - @Test - public void testComputeNestedConfigResourceOverrides() { - final Task task = new TaskBuilder(mSupervisor).build(); - assertTrue(task.getResolvedOverrideBounds().isEmpty()); - int origScreenH = task.getConfiguration().screenHeightDp; - Configuration stackConfig = new Configuration(); - stackConfig.setTo(task.getRootTask().getRequestedOverrideConfiguration()); - stackConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - - // Set bounds on stack (not task) and verify that the task resource configuration changes - // despite it's override bounds being empty. - Rect bounds = new Rect(task.getRootTask().getBounds()); - bounds.bottom = (int) (bounds.bottom * 0.6f); - stackConfig.windowConfiguration.setBounds(bounds); - task.getRootTask().onRequestedOverrideConfigurationChanged(stackConfig); - assertNotEquals(origScreenH, task.getConfiguration().screenHeightDp); - } - - @Test - public void testFullScreenTaskNotAdjustedByMinimalSize() { - final Task fullscreenTask = new TaskBuilder(mSupervisor).build(); - final Rect originalTaskBounds = new Rect(fullscreenTask.getBounds()); - final ActivityInfo aInfo = new ActivityInfo(); - aInfo.windowLayout = new ActivityInfo.WindowLayout(0 /* width */, 0 /* widthFraction */, - 0 /* height */, 0 /* heightFraction */, 0 /* gravity */, - originalTaskBounds.width() * 2 /* minWidth */, - originalTaskBounds.height() * 2 /* minHeight */); - fullscreenTask.setMinDimensions(aInfo); - fullscreenTask.onConfigurationChanged(fullscreenTask.getParent().getConfiguration()); - - assertEquals(originalTaskBounds, fullscreenTask.getBounds()); - } - - @Test - public void testInsetDisregardedWhenFreeformOverlapsNavBar() { - TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); - Task stack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, true /* onTop */); - DisplayInfo displayInfo = new DisplayInfo(); - mAtm.mContext.getDisplay().getDisplayInfo(displayInfo); - final int displayHeight = displayInfo.logicalHeight; - final Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build(); - final Configuration inOutConfig = new Configuration(); - final Configuration parentConfig = new Configuration(); - final int longSide = 1200; - final int shortSide = 600; - parentConfig.densityDpi = 400; - parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px - parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px - parentConfig.windowConfiguration.setRotation(ROTATION_0); - - final int longSideDp = 480; // longSide / density = 1200 / 400 * 160 - final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160 - final int screenLayout = parentConfig.screenLayout - & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); - final int reducedScreenLayout = - Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp); - - // Portrait bounds overlapping with navigation bar, without insets. - final Rect freeformBounds = new Rect(0, - displayHeight - 10 - longSide, - shortSide, - displayHeight - 10); - inOutConfig.windowConfiguration.setBounds(freeformBounds); - // Set to freeform mode to verify bug fix. - inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - - task.computeConfigResourceOverrides(inOutConfig, parentConfig); - - // screenW/H should not be effected by parent since overridden and freeform - assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi, - inOutConfig.screenWidthDp); - assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi, - inOutConfig.screenHeightDp); - assertEquals(reducedScreenLayout, inOutConfig.screenLayout); - - inOutConfig.setToDefaults(); - // Landscape bounds overlapping with navigtion bar, without insets. - freeformBounds.set(0, - displayHeight - 10 - shortSide, - longSide, - displayHeight - 10); - inOutConfig.windowConfiguration.setBounds(freeformBounds); - inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - - task.computeConfigResourceOverrides(inOutConfig, parentConfig); - - assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi, - inOutConfig.screenWidthDp); - assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi, - inOutConfig.screenHeightDp); - assertEquals(reducedScreenLayout, inOutConfig.screenLayout); - } - - /** Ensures that the alias intent won't have target component resolved. */ - @Test - public void testTaskIntentActivityAlias() { - final String aliasClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".aliasActivity"; - final String targetClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".targetActivity"; - final ComponentName aliasComponent = - new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasClassName); - final ComponentName targetComponent = - new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, targetClassName); - - final Intent intent = new Intent(); - intent.setComponent(aliasComponent); - final ActivityInfo info = new ActivityInfo(); - info.applicationInfo = new ApplicationInfo(); - info.packageName = DEFAULT_COMPONENT_PACKAGE_NAME; - info.targetActivity = targetClassName; - - final Task task = new Task.Builder(mAtm) - .setTaskId(1) - .setActivityInfo(info) - .setIntent(intent) - .build(); - assertEquals("The alias activity component should be saved in task intent.", aliasClassName, - task.intent.getComponent().getClassName()); - - ActivityRecord aliasActivity = new ActivityBuilder(mAtm).setComponent( - aliasComponent).setTargetActivity(targetClassName).build(); - assertEquals("Should be the same intent filter.", true, - task.isSameIntentFilter(aliasActivity)); - - ActivityRecord targetActivity = new ActivityBuilder(mAtm).setComponent( - targetComponent).build(); - assertEquals("Should be the same intent filter.", true, - task.isSameIntentFilter(targetActivity)); - - ActivityRecord defaultActivity = new ActivityBuilder(mAtm).build(); - assertEquals("Should not be the same intent filter.", false, - task.isSameIntentFilter(defaultActivity)); - } - - /** Test that root activity index is reported correctly for several activities in the task. */ - @Test - public void testFindRootIndex() { - final Task task = getTestTask(); - // Add an extra activity on top of the root one - new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals("The root activity in the task must be reported.", task.getChildAt(0), - task.getRootActivity( - true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); - } - - /** - * Test that root activity index is reported correctly for several activities in the task when - * the activities on the bottom are finishing. - */ - @Test - public void testFindRootIndex_finishing() { - final Task task = getTestTask(); - // Add extra two activities and mark the two on the bottom as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.finishing = true; - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - activity1.finishing = true; - new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals("The first non-finishing activity in the task must be reported.", - task.getChildAt(2), task.getRootActivity( - true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); - } - - /** - * Test that root activity index is reported correctly for several activities in the task when - * looking for the 'effective root'. - */ - @Test - public void testFindRootIndex_effectiveRoot() { - final Task task = getTestTask(); - // Add an extra activity on top of the root one - new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals("The root activity in the task must be reported.", - task.getChildAt(0), task.getRootActivity( - false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); - } - - /** - * Test that root activity index is reported correctly when looking for the 'effective root' in - * case when bottom activities are relinquishing task identity or finishing. - */ - @Test - public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() { - final Task task = getTestTask(); - // Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and - // one above as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - activity1.finishing = true; - new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals("The first non-finishing activity and non-relinquishing task identity " - + "must be reported.", task.getChildAt(2), task.getRootActivity( - false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); - } - - /** - * Test that root activity index is reported correctly when looking for the 'effective root' - * for the case when there is only a single activity that also has relinquishTaskIdentity set. - */ - @Test - public void testFindRootIndex_effectiveRoot_relinquishingAndSingleActivity() { - final Task task = getTestTask(); - // Set relinquishTaskIdentity for the only activity in the task - task.getBottomMostActivity().info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - - assertEquals("The root activity in the task must be reported.", - task.getChildAt(0), task.getRootActivity( - false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); - } - - /** - * Test that the topmost activity index is reported correctly when looking for the - * 'effective root' for the case when all activities have relinquishTaskIdentity set. - */ - @Test - public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() { - final Task task = getTestTask(); - // Set relinquishTaskIdentity for all activities in the task - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - - assertEquals("The topmost activity in the task must be reported.", - task.getChildAt(task.getChildCount() - 1), task.getRootActivity( - false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); - } - - /** Test that bottom-most activity is reported in {@link Task#getRootActivity()}. */ - @Test - public void testGetRootActivity() { - final Task task = getTestTask(); - // Add an extra activity on top of the root one - new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals("The root activity in the task must be reported.", - task.getBottomMostActivity(), task.getRootActivity()); - } - - /** - * Test that first non-finishing activity is reported in {@link Task#getRootActivity()}. - */ - @Test - public void testGetRootActivity_finishing() { - final Task task = getTestTask(); - // Add an extra activity on top of the root one - new ActivityBuilder(mAtm).setTask(task).build(); - // Mark the root as finishing - task.getBottomMostActivity().finishing = true; - - assertEquals("The first non-finishing activity in the task must be reported.", - task.getChildAt(1), task.getRootActivity()); - } - - /** - * Test that relinquishTaskIdentity flag is ignored in {@link Task#getRootActivity()}. - */ - @Test - public void testGetRootActivity_relinquishTaskIdentity() { - final Task task = getTestTask(); - // Mark the bottom-most activity with FLAG_RELINQUISH_TASK_IDENTITY. - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - // Add an extra activity on top of the root one. - new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals("The root activity in the task must be reported.", - task.getBottomMostActivity(), task.getRootActivity()); - } - - /** - * Test that no activity is reported in {@link Task#getRootActivity()} when all activities - * in the task are finishing. - */ - @Test - public void testGetRootActivity_allFinishing() { - final Task task = getTestTask(); - // Mark the bottom-most activity as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.finishing = true; - // Add an extra activity on top of the root one and mark it as finishing - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - activity1.finishing = true; - - assertNull("No activity must be reported if all are finishing", task.getRootActivity()); - } - - /** - * Test that first non-finishing activity is the root of task. - */ - @Test - public void testIsRootActivity() { - final Task task = getTestTask(); - // Mark the bottom-most activity as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.finishing = true; - // Add an extra activity on top of the root one. - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - - assertFalse("Finishing activity must not be the root of task", activity0.isRootOfTask()); - assertTrue("Non-finishing activity must be the root of task", activity1.isRootOfTask()); - } - - /** - * Test that if all activities in the task are finishing, then the one on the bottom is the - * root of task. - */ - @Test - public void testIsRootActivity_allFinishing() { - final Task task = getTestTask(); - // Mark the bottom-most activity as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.finishing = true; - // Add an extra activity on top of the root one and mark it as finishing - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - activity1.finishing = true; - - assertTrue("Bottom activity must be the root of task", activity0.isRootOfTask()); - assertFalse("Finishing activity on top must not be the root of task", - activity1.isRootOfTask()); - } - - /** - * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)}. - */ - @Test - public void testGetTaskForActivity() { - final Task task0 = getTestTask(); - final ActivityRecord activity0 = task0.getBottomMostActivity(); - - final Task task1 = getTestTask(); - final ActivityRecord activity1 = task1.getBottomMostActivity(); - - assertEquals(task0.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */)); - assertEquals(task1.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */)); - } - - /** - * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with finishing - * activity. - */ - @Test - public void testGetTaskForActivity_onlyRoot_finishing() { - final Task task = getTestTask(); - // Make the current root activity finishing - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.finishing = true; - // Add an extra activity on top - this will be the new root - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - // Add one more on top - final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals(task.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */)); - assertEquals(task.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */)); - assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, - ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */)); - } - - /** - * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with activity that - * relinquishes task identity. - */ - @Test - public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() { - final Task task = getTestTask(); - // Make the current root activity relinquish task identity - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - // Add an extra activity on top - this will be the new root - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - // Add one more on top - final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals(task.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */)); - assertEquals(task.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */)); - assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, - ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */)); - } - - /** - * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} allowing non-root - * entries. - */ - @Test - public void testGetTaskForActivity_notOnlyRoot() { - final Task task = getTestTask(); - // Mark the bottom-most activity as finishing. - final ActivityRecord activity0 = task.getBottomMostActivity(); - activity0.finishing = true; - - // Add an extra activity on top of the root one and make it relinquish task identity - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; - - // Add one more activity on top - final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); - - assertEquals(task.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */)); - assertEquals(task.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */)); - assertEquals(task.mTaskId, - ActivityRecord.getTaskForActivityLocked(activity2.appToken, false /* onlyRoot */)); - } - - /** - * Test {@link Task#updateEffectiveIntent()}. - */ - @Test - public void testUpdateEffectiveIntent() { - // Test simple case with a single activity. - final Task task = getTestTask(); - final ActivityRecord activity0 = task.getBottomMostActivity(); - - spyOn(task); - task.updateEffectiveIntent(); - verify(task).setIntent(eq(activity0)); - } - - /** - * Test {@link Task#updateEffectiveIntent()} with root activity marked as finishing. This - * should make the task use the second activity when updating the intent. - */ - @Test - public void testUpdateEffectiveIntent_rootFinishing() { - // Test simple case with a single activity. - final Task task = getTestTask(); - final ActivityRecord activity0 = task.getBottomMostActivity(); - // Mark the bottom-most activity as finishing. - activity0.finishing = true; - // Add an extra activity on top of the root one - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - - spyOn(task); - task.updateEffectiveIntent(); - verify(task).setIntent(eq(activity1)); - } - - /** - * Test {@link Task#updateEffectiveIntent()} when all activities are finishing or - * relinquishing task identity. In this case the root activity should still be used when - * updating the intent (legacy behavior). - */ - @Test - public void testUpdateEffectiveIntent_allFinishing() { - // Test simple case with a single activity. - final Task task = getTestTask(); - final ActivityRecord activity0 = task.getBottomMostActivity(); - // Mark the bottom-most activity as finishing. - activity0.finishing = true; - // Add an extra activity on top of the root one and make it relinquish task identity - final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); - activity1.finishing = true; - - // Task must still update the intent using the root activity (preserving legacy behavior). - spyOn(task); - task.updateEffectiveIntent(); - verify(task).setIntent(eq(activity0)); - } - - @Test - public void testSaveLaunchingStateWhenConfigurationChanged() { - LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; - spyOn(persister); - - final Task task = getTestTask(); - task.setHasBeenVisible(false); - task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); - task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - task.setHasBeenVisible(true); - task.onConfigurationChanged(task.getParent().getConfiguration()); - - verify(persister).saveTask(task, task.getDisplayContent()); - } - - @Test - public void testSaveLaunchingStateWhenClearingParent() { - LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; - spyOn(persister); - - final Task task = getTestTask(); - task.setHasBeenVisible(false); - task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); - task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); - final DisplayContent oldDisplay = task.getDisplayContent(); - - LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams(); - params.mWindowingMode = WINDOWING_MODE_UNDEFINED; - persister.getLaunchParams(task, null, params); - assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode); - - task.setHasBeenVisible(true); - task.removeImmediately(); - - verify(persister).saveTask(task, oldDisplay); - - persister.getLaunchParams(task, null, params); - assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode); - } - - @Test - public void testNotSaveLaunchingStateNonFreeformDisplay() { - LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; - spyOn(persister); - - final Task task = getTestTask(); - task.setHasBeenVisible(false); - task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - task.setHasBeenVisible(true); - task.onConfigurationChanged(task.getParent().getConfiguration()); - - verify(persister, never()).saveTask(same(task), any()); - } - - @Test - public void testNotSaveLaunchingStateWhenNotFullscreenOrFreeformWindow() { - LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; - spyOn(persister); - - final Task task = getTestTask(); - task.setHasBeenVisible(false); - task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); - task.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED); - - task.setHasBeenVisible(true); - task.onConfigurationChanged(task.getParent().getConfiguration()); - - verify(persister, never()).saveTask(same(task), any()); - } - - @Test - public void testNotSaveLaunchingStateForNonLeafTask() { - LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; - spyOn(persister); - - final Task task = getTestTask(); - task.setHasBeenVisible(false); - task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); - task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); - - final Task leafTask = createTaskInStack(task, 0 /* userId */); - - leafTask.setHasBeenVisible(true); - task.setHasBeenVisible(true); - task.onConfigurationChanged(task.getParent().getConfiguration()); - - verify(persister, never()).saveTask(same(task), any()); - verify(persister).saveTask(same(leafTask), any()); - } - - @Test - public void testNotSpecifyOrientationByFloatingTask() { - final Task task = new TaskBuilder(mSupervisor) - .setCreateActivity(true).setCreateParentTask(true).build(); - final ActivityRecord activity = task.getTopMostActivity(); - final WindowContainer<?> parentContainer = task.getParent(); - final TaskDisplayArea taskDisplayArea = task.getDisplayArea(); - activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); - - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parentContainer.getOrientation()); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation()); - - task.setWindowingMode(WINDOWING_MODE_PINNED); - - // TDA returns the last orientation when child returns UNSET - assertEquals(SCREEN_ORIENTATION_UNSET, parentContainer.getOrientation()); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation()); - } - - @Test - public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() { - final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( - mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", - FEATURE_VENDOR_FIRST); - final Task firstStack = firstTaskDisplayArea.createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final Task secondStack = secondTaskDisplayArea.createRootTask( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord firstActivity = new ActivityBuilder(mAtm) - .setTask(firstStack).build(); - final ActivityRecord secondActivity = new ActivityBuilder(mAtm) - .setTask(secondStack).build(); - firstActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); - secondActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - - // Activity on TDA1 is focused - mDisplayContent.setFocusedApp(firstActivity); - - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation()); - assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation()); - - // No focused app, TDA1 is still recorded as last focused. - mDisplayContent.setFocusedApp(null); - - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation()); - assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation()); - - // Activity on TDA2 is focused - mDisplayContent.setFocusedApp(secondActivity); - - assertEquals(SCREEN_ORIENTATION_UNSET, firstTaskDisplayArea.getOrientation()); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, secondTaskDisplayArea.getOrientation()); - } - - @Test - public void testNotifyOrientationChangeCausedByConfigurationChange() { - final Task task = getTestTask(); - final ActivityRecord activity = task.getTopMostActivity(); - final DisplayContent display = task.getDisplayContent(); - display.setWindowingMode(WINDOWING_MODE_FREEFORM); - - activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); - assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation()); - verify(display).onDescendantOrientationChanged(same(task)); - reset(display); - - display.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, task.getOrientation()); - verify(display).onDescendantOrientationChanged(same(task)); - } - - private Task getTestTask() { - final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); - return stack.getBottomMostTask(); - } - - private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, - Rect expectedConfigBounds) { - - TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); - Task stack = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - Task task = new TaskBuilder(mSupervisor).setParentTask(stack).build(); - - final Configuration parentConfig = stack.getConfiguration(); - parentConfig.windowConfiguration.setAppBounds(parentBounds); - task.setBounds(bounds); - - task.resolveOverrideConfiguration(parentConfig); - // Assert that both expected and actual are null or are equal to each other - assertEquals(expectedConfigBounds, - task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()); - } - - private byte[] serializeToBytes(Task r) throws Exception { - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - final TypedXmlSerializer serializer = Xml.newFastSerializer(); - serializer.setOutput(os, "UTF-8"); - serializer.startDocument(null, true); - serializer.startTag(null, TASK_TAG); - r.saveToXml(serializer); - serializer.endTag(null, TASK_TAG); - serializer.endDocument(); - - os.flush(); - return os.toByteArray(); - } - } - - private Task restoreFromBytes(byte[] in) throws IOException, XmlPullParserException { - try (Reader reader = new InputStreamReader(new ByteArrayInputStream(in))) { - final TypedXmlPullParser parser = Xml.newFastPullParser(); - parser.setInput(reader); - assertEquals(XmlPullParser.START_TAG, parser.next()); - assertEquals(TASK_TAG, parser.getName()); - return Task.restoreFromXml(parser, mAtm.mTaskSupervisor); - } - } - - private Task createTask(int taskId) { - return new Task.Builder(mAtm) - .setTaskId(taskId) - .setIntent(new Intent()) - .setRealActivity(ActivityBuilder.getDefaultComponent()) - .setEffectiveUid(10050) - .buildInner(); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java deleted file mode 100644 index e58c162ceadd..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; -import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.clearInvocations; - -import android.app.WindowConfiguration; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link Task} class. - * - * Build/Install/Run: - * atest WmTests:TaskStackTests - */ -@SmallTest -@Presubmit -@RunWith(WindowTestRunner.class) -public class TaskStackTests extends WindowTestsBase { - - @Test - public void testStackPositionChildAt() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task1 = createTaskInStack(stack, 0 /* userId */); - final Task task2 = createTaskInStack(stack, 1 /* userId */); - - // Current user task should be moved to top. - stack.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */); - assertEquals(stack.mChildren.get(0), task2); - assertEquals(stack.mChildren.get(1), task1); - - // Non-current user won't be moved to top. - stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */); - assertEquals(stack.mChildren.get(0), task2); - assertEquals(stack.mChildren.get(1), task1); - - // Non-leaf task should be moved to top regardless of the user id. - createTaskInStack(task2, 0 /* userId */); - createTaskInStack(task2, 1 /* userId */); - stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */); - assertEquals(stack.mChildren.get(0), task1); - assertEquals(stack.mChildren.get(1), task2); - } - - @Test - public void testClosingAppDifferentTaskOrientation() { - final ActivityRecord activity1 = createActivityRecord(mDisplayContent); - activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - - final ActivityRecord activity2 = createActivityRecord(mDisplayContent); - activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); - - final WindowContainer parent = activity1.getTask().getParent(); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation()); - mDisplayContent.mClosingApps.add(activity2); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parent.getOrientation()); - } - - @Test - public void testMoveTaskToBackDifferentTaskOrientation() { - final ActivityRecord activity1 = createActivityRecord(mDisplayContent); - activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - - final ActivityRecord activity2 = createActivityRecord(mDisplayContent); - activity2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); - - final WindowContainer parent = activity1.getTask().getParent(); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation()); - } - - @Test - public void testStackRemoveImmediately() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); - assertEquals(stack, task.getRootTask()); - - // Remove stack and check if its child is also removed. - stack.removeImmediately(); - assertNull(stack.getDisplayContent()); - assertNull(task.getParent()); - } - - @Test - public void testRemoveContainer() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); - - assertNotNull(stack); - assertNotNull(task); - stack.removeIfPossible(); - // Assert that the container was removed. - assertNull(stack.getParent()); - assertEquals(0, stack.getChildCount()); - assertNull(stack.getDisplayContent()); - assertNull(task.getDisplayContent()); - assertNull(task.getParent()); - } - - @Test - public void testRemoveContainer_deferRemoval() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); - - // Stack removal is deferred if one of its child is animating. - doReturn(true).when(stack).hasWindowsAlive(); - doReturn(stack).when(task).getAnimatingContainer( - eq(TRANSITION | CHILDREN), anyInt()); - - stack.removeIfPossible(); - // For the case of deferred removal the task controller will still be connected to the its - // container until the stack window container is removed. - assertNotNull(stack.getParent()); - assertNotEquals(0, stack.getChildCount()); - assertNotNull(task); - - stack.removeImmediately(); - // After removing, the task will be isolated. - assertNull(task.getParent()); - assertEquals(0, task.getChildCount()); - } - - @Test - public void testReparent() { - // Create first stack on primary display. - final Task stack1 = createTaskStackOnDisplay(mDisplayContent); - final Task task1 = createTaskInStack(stack1, 0 /* userId */); - - // Create second display and put second stack on it. - final DisplayContent dc = createNewDisplay(); - final Task stack2 = createTaskStackOnDisplay(dc); - - // Reparent - clearInvocations(task1); // reset the number of onDisplayChanged for task. - stack1.reparent(dc.getDefaultTaskDisplayArea(), true /* onTop */); - assertEquals(dc, stack1.getDisplayContent()); - final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); - final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); - assertEquals(stack1PositionInParent, stack2PositionInParent + 1); - verify(task1, times(1)).onDisplayChanged(any()); - } - - @Test - public void testStackOutset() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final int stackOutset = 10; - spyOn(stack); - doReturn(stackOutset).when(stack).getTaskOutset(); - doReturn(true).when(stack).inMultiWindowMode(); - - // Mock the resolved override windowing mode to non-fullscreen - final WindowConfiguration windowConfiguration = - stack.getResolvedOverrideConfiguration().windowConfiguration; - spyOn(windowConfiguration); - doReturn(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) - .when(windowConfiguration).getWindowingMode(); - - // Prevent adjust task dimensions - doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any(), any()); - - final Rect stackBounds = new Rect(200, 200, 800, 1000); - // Update surface position and size by the given bounds. - stack.setBounds(stackBounds); - - assertEquals(stackBounds.width() + 2 * stackOutset, stack.getLastSurfaceSize().x); - assertEquals(stackBounds.height() + 2 * stackOutset, stack.getLastSurfaceSize().y); - assertEquals(stackBounds.left - stackOutset, stack.getLastSurfacePosition().x); - assertEquals(stackBounds.top - stackOutset, stack.getLastSurfacePosition().y); - } - - @Test - public void testActivityAndTaskGetsProperType() { - final Task task1 = new TaskBuilder(mSupervisor).build(); - ActivityRecord activity1 = createNonAttachedActivityRecord(mDisplayContent); - - // First activity should become standard - task1.addChild(activity1, 0); - assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity1.getActivityType()); - assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType()); - - // Second activity should also become standard - ActivityRecord activity2 = createNonAttachedActivityRecord(mDisplayContent); - task1.addChild(activity2, WindowContainer.POSITION_TOP); - assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, activity2.getActivityType()); - assertEquals(WindowConfiguration.ACTIVITY_TYPE_STANDARD, task1.getActivityType()); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index dca6b089d66b..2389d2d6e8d6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -16,20 +16,39 @@ package com.android.server.wm; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; +import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.util.DisplayMetrics.DENSITY_DEFAULT; +import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_90; +import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.google.common.truth.Truth.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -39,18 +58,45 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import android.app.ActivityManager; +import android.app.TaskInfo; import android.app.WindowConfiguration; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; +import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.util.DisplayMetrics; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; +import android.view.DisplayInfo; -import androidx.test.filters.SmallTest; +import androidx.test.filters.MediumTest; +import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; /** * Test class for {@link Task}. @@ -58,15 +104,25 @@ import org.junit.runner.RunWith; * Build/Install/Run: * atest WmTests:TaskTests */ -@SmallTest +@MediumTest @Presubmit @RunWith(WindowTestRunner.class) public class TaskTests extends WindowTestsBase { + private static final String TASK_TAG = "task"; + + private Rect mParentBounds; + + @Before + public void setUp() throws Exception { + mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/); + removeGlobalMinSizeRestriction(); + } + @Test public void testRemoveContainer() { - final Task stackController1 = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stackController1, 0 /* userId */); + final Task taskController1 = createTask(mDisplayContent); + final Task task = createTaskInRootTask(taskController1, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); task.removeIfPossible(); @@ -78,8 +134,8 @@ public class TaskTests extends WindowTestsBase { @Test public void testRemoveContainer_deferRemoval() { - final Task stackController1 = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stackController1, 0 /* userId */); + final Task taskController1 = createTask(mDisplayContent); + final Task task = createTaskInRootTask(taskController1, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); doReturn(true).when(task).shouldDeferRemoval(); @@ -99,14 +155,14 @@ public class TaskTests extends WindowTestsBase { @Test public void testReparent() { - final Task stackController1 = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stackController1, 0 /* userId */); - final Task stackController2 = createTaskStackOnDisplay(mDisplayContent); - final Task task2 = createTaskInStack(stackController2, 0 /* userId */); + final Task taskController1 = createTask(mDisplayContent); + final Task task = createTaskInRootTask(taskController1, 0 /* userId */); + final Task taskController2 = createTask(mDisplayContent); + final Task task2 = createTaskInRootTask(taskController2, 0 /* userId */); boolean gotException = false; try { - task.reparent(stackController1, 0, false/* moveParents */, "testReparent"); + task.reparent(taskController1, 0, false/* moveParents */, "testReparent"); } catch (IllegalArgumentException e) { gotException = true; } @@ -118,29 +174,29 @@ public class TaskTests extends WindowTestsBase { } catch (Exception e) { gotException = true; } - assertTrue("Should not be able to reparent to a stack that doesn't exist", gotException); + assertTrue("Should not be able to reparent to a task that doesn't exist", gotException); - task.reparent(stackController2, 0, false/* moveParents */, "testReparent"); - assertEquals(stackController2, task.getParent()); + task.reparent(taskController2, 0, false/* moveParents */, "testReparent"); + assertEquals(taskController2, task.getParent()); assertEquals(0, task.getParent().mChildren.indexOf(task)); assertEquals(1, task2.getParent().mChildren.indexOf(task2)); } @Test public void testReparent_BetweenDisplays() { - // Create first stack on primary display. - final Task stack1 = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack1, 0 /* userId */); - assertEquals(mDisplayContent, stack1.getDisplayContent()); + // Create first task on primary display. + final Task rootTask1 = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask1, 0 /* userId */); + assertEquals(mDisplayContent, rootTask1.getDisplayContent()); - // Create second display and put second stack on it. + // Create second display and put second task on it. final DisplayContent dc = createNewDisplay(); - final Task stack2 = createTaskStackOnDisplay(dc); - final Task task2 = createTaskInStack(stack2, 0 /* userId */); + final Task rootTask2 = createTask(dc); + final Task task2 = createTaskInRootTask(rootTask2, 0 /* userId */); // Reparent and check state clearInvocations(task); // reset the number of onDisplayChanged for task. - task.reparent(stack2, 0, false /* moveParents */, "testReparent_BetweenDisplays"); - assertEquals(stack2, task.getParent()); + task.reparent(rootTask2, 0, false /* moveParents */, "testReparent_BetweenDisplays"); + assertEquals(rootTask2, task.getParent()); assertEquals(0, task.getParent().mChildren.indexOf(task)); assertEquals(1, task2.getParent().mChildren.indexOf(task2)); verify(task, times(1)).onDisplayChanged(any()); @@ -148,8 +204,8 @@ public class TaskTests extends WindowTestsBase { @Test public void testBounds() { - final Task stack1 = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack1, 0 /* userId */); + final Task rootTask1 = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask1, 0 /* userId */); // Check that setting bounds also updates surface position task.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -159,9 +215,9 @@ public class TaskTests extends WindowTestsBase { } @Test - public void testIsInStack() { - final Task task1 = createTaskStackOnDisplay(mDisplayContent); - final Task task2 = createTaskStackOnDisplay(mDisplayContent); + public void testIsInTask() { + final Task task1 = createTask(mDisplayContent); + final Task task2 = createTask(mDisplayContent); final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task1); final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task2); assertEquals(activity1, task1.isInTask(activity1)); @@ -170,7 +226,7 @@ public class TaskTests extends WindowTestsBase { @Test public void testRemoveChildForOverlayTask() { - final Task task = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTask(mDisplayContent); final int taskId = task.mTaskId; final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task); final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task); @@ -193,10 +249,10 @@ public class TaskTests extends WindowTestsBase { @Test public void testSwitchUser() { - final Task rootTask = createTaskStackOnDisplay(mDisplayContent); - final Task childTask = createTaskInStack(rootTask, 0 /* userId */); - final Task leafTask1 = createTaskInStack(childTask, 10 /* userId */); - final Task leafTask2 = createTaskInStack(childTask, 0 /* userId */); + final Task rootTask = createTask(mDisplayContent); + final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */); + final Task leafTask1 = createTaskInRootTask(childTask, 10 /* userId */); + final Task leafTask2 = createTaskInRootTask(childTask, 0 /* userId */); assertEquals(1, rootTask.getChildCount()); assertEquals(leafTask2, childTask.getTopChild()); @@ -208,9 +264,9 @@ public class TaskTests extends WindowTestsBase { @Test public void testEnsureActivitiesVisible() { - final Task rootTask = createTaskStackOnDisplay(mDisplayContent); - final Task leafTask1 = createTaskInStack(rootTask, 0 /* userId */); - final Task leafTask2 = createTaskInStack(rootTask, 0 /* userId */); + final Task rootTask = createTask(mDisplayContent); + final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */); + final Task leafTask2 = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity1 = createActivityRecord(mDisplayContent, leafTask1); final ActivityRecord activity2 = createActivityRecord(mDisplayContent, leafTask2); @@ -233,7 +289,7 @@ public class TaskTests extends WindowTestsBase { @Test public void testResolveNonResizableTaskWindowingMode() { - final Task task = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTask(mDisplayContent); Configuration parentConfig = task.getParent().getConfiguration(); parentConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); doReturn(false).when(task).isResizeable(); @@ -264,10 +320,10 @@ public class TaskTests extends WindowTestsBase { @Test public void testHandlesOrientationChangeFromDescendant() { - final Task rootTask = createTaskStackOnDisplay(WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task leafTask1 = createTaskInStack(rootTask, 0 /* userId */); - final Task leafTask2 = createTaskInStack(rootTask, 0 /* userId */); + final Task rootTask = createTask(mDisplayContent, + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */); + final Task leafTask2 = createTaskInRootTask(rootTask, 0 /* userId */); leafTask1.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_HOME); leafTask2.getWindowConfiguration().setActivityType(ACTIVITY_TYPE_STANDARD); @@ -281,7 +337,7 @@ public class TaskTests extends WindowTestsBase { @Test public void testAlwaysOnTop() { - final Task task = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTask(mDisplayContent); task.setAlwaysOnTop(true); task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); assertTrue(task.isAlwaysOnTop()); @@ -289,4 +345,1053 @@ public class TaskTests extends WindowTestsBase { task.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); assertFalse(task.isAlwaysOnTop()); } + + @Test + public void testRestoreWindowedTask() throws Exception { + final Task expected = createTask(64); + expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100); + + final byte[] serializedBytes = serializeToBytes(expected); + final Task actual = restoreFromBytes(serializedBytes); + assertEquals(expected.mTaskId, actual.mTaskId); + assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds); + } + + /** Ensure we have no chance to modify the original intent. */ + @Test + public void testCopyBaseIntentForTaskInfo() { + final Task task = createTask(1); + task.setTaskDescription(new ActivityManager.TaskDescription()); + final TaskInfo info = task.getTaskInfo(); + + // The intent of info should be a copy so assert that they are different instances. + Assert.assertThat(info.baseIntent, not(sameInstance(task.getBaseIntent()))); + } + + @Test + public void testReturnsToHomeRootTask() throws Exception { + final Task task = createTask(1); + spyOn(task); + doReturn(true).when(task).hasChild(); + assertFalse(task.returnsToHomeRootTask()); + task.intent = null; + assertFalse(task.returnsToHomeRootTask()); + task.intent = new Intent(); + assertFalse(task.returnsToHomeRootTask()); + task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME); + assertTrue(task.returnsToHomeRootTask()); + } + + /** Ensures that empty bounds cause appBounds to inherit from parent. */ + @Test + public void testAppBounds_EmptyBounds() { + final Rect emptyBounds = new Rect(); + testRootTaskBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds, + mParentBounds); + } + + /** Ensures that bounds on freeform root tasks are not clipped. */ + @Test + public void testAppBounds_FreeFormBounds() { + final Rect freeFormBounds = new Rect(mParentBounds); + freeFormBounds.offset(10, 10); + testRootTaskBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds, + freeFormBounds); + } + + /** Ensures that fully contained bounds are not clipped. */ + @Test + public void testAppBounds_ContainedBounds() { + final Rect insetBounds = new Rect(mParentBounds); + insetBounds.inset(5, 5, 5, 5); + testRootTaskBoundsConfiguration( + WINDOWING_MODE_FREEFORM, mParentBounds, insetBounds, insetBounds); + } + + @Test + public void testFitWithinBounds() { + final Rect parentBounds = new Rect(10, 10, 200, 200); + TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); + Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + final Configuration parentConfig = rootTask.getConfiguration(); + parentConfig.windowConfiguration.setBounds(parentBounds); + parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT; + + // check top and left + Rect reqBounds = new Rect(-190, -190, 0, 0); + task.setBounds(reqBounds); + // Make sure part of it is exposed + assertTrue(task.getBounds().right > parentBounds.left); + assertTrue(task.getBounds().bottom > parentBounds.top); + // Should still be more-or-less in that corner + assertTrue(task.getBounds().left <= parentBounds.left); + assertTrue(task.getBounds().top <= parentBounds.top); + + assertEquals(reqBounds.width(), task.getBounds().width()); + assertEquals(reqBounds.height(), task.getBounds().height()); + + // check bottom and right + reqBounds = new Rect(210, 210, 400, 400); + task.setBounds(reqBounds); + // Make sure part of it is exposed + assertTrue(task.getBounds().left < parentBounds.right); + assertTrue(task.getBounds().top < parentBounds.bottom); + // Should still be more-or-less in that corner + assertTrue(task.getBounds().right >= parentBounds.right); + assertTrue(task.getBounds().bottom >= parentBounds.bottom); + + assertEquals(reqBounds.width(), task.getBounds().width()); + assertEquals(reqBounds.height(), task.getBounds().height()); + } + + /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */ + @Test + public void testBoundsOnModeChangeFreeformToFullscreen() { + DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay(); + Task rootTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + Task task = rootTask.getBottomMostTask(); + task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + DisplayInfo info = new DisplayInfo(); + display.mDisplay.getDisplayInfo(info); + final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight); + final Rect freeformBounds = new Rect(fullScreenBounds); + freeformBounds.inset((int) (freeformBounds.width() * 0.2), + (int) (freeformBounds.height() * 0.2)); + task.setBounds(freeformBounds); + + assertEquals(freeformBounds, task.getBounds()); + + // FULLSCREEN inherits bounds + rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertEquals(fullScreenBounds, task.getBounds()); + assertEquals(freeformBounds, task.mLastNonFullscreenBounds); + + // FREEFORM restores bounds + rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(freeformBounds, task.getBounds()); + } + + /** + * Tests that a task with forced orientation has orientation-consistent bounds within the + * parent. + */ + @Test + public void testFullscreenBoundsForcedOrientation() { + final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); + final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); + final DisplayContent display = new TestDisplayContent.Builder(mAtm, + fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build(); + assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId)); + // Fix the display orientation to landscape which is the natural rotation (0) for the test + // display. + final DisplayRotation dr = display.mDisplayContent.getDisplayRotation(); + dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED); + dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0); + + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); + final Task task = rootTask.getBottomMostTask(); + final ActivityRecord root = task.getTopNonFinishingActivity(); + + assertEquals(fullScreenBounds, task.getBounds()); + + // Setting app to fixed portrait fits within parent + root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + assertEquals(root, task.getRootActivity()); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); + // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds. + assertThat(task.getBounds().height()).isLessThan(task.getBounds().width()); + assertEquals(fullScreenBounds, task.getBounds()); + + // Top activity gets used + final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(rootTask) + .build(); + assertEquals(top, task.getTopNonFinishingActivity()); + top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); + assertEquals(task.getBounds().width(), fullScreenBounds.width()); + + // Setting app to unspecified restores + top.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + assertEquals(fullScreenBounds, task.getBounds()); + + // Setting app to fixed landscape and changing display + top.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + // Fix the display orientation to portrait which is 90 degrees for the test display. + dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90); + + // Fixed orientation request should be resolved on activity level. Task fills display + // bounds. + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); + + // in FREEFORM, no constraint + final Rect freeformBounds = new Rect(display.getBounds()); + freeformBounds.inset((int) (freeformBounds.width() * 0.2), + (int) (freeformBounds.height() * 0.2)); + rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + task.setBounds(freeformBounds); + assertEquals(freeformBounds, task.getBounds()); + + // FULLSCREEN letterboxes bounds on activity level, no constraint on task level. + rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); + + // FREEFORM restores bounds as before + rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(freeformBounds, task.getBounds()); + } + + @Test + public void testReportsOrientationRequestInLetterboxForOrientation() { + final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); + final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920); + final DisplayContent display = new TestDisplayContent.Builder(mAtm, + fullScreenBounds.width(), fullScreenBounds.height()).setCanRotate(false).build(); + assertNotNull(mRootWindowContainer.getDisplayContent(display.mDisplayId)); + // Fix the display orientation to landscape which is the natural rotation (0) for the test + // display. + final DisplayRotation dr = display.mDisplayContent.getDisplayRotation(); + dr.setFixedToUserRotation(FIXED_TO_USER_ROTATION_ENABLED); + dr.setUserRotation(USER_ROTATION_FREE, ROTATION_0); + + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); + final Task task = rootTask.getBottomMostTask(); + ActivityRecord root = task.getTopNonFinishingActivity(); + + assertEquals(fullScreenBounds, task.getBounds()); + + // Setting app to fixed portrait fits within parent on activity level. Task fills parent. + root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + assertThat(root.getBounds().width()).isLessThan(root.getBounds().height()); + assertEquals(task.getBounds(), fullScreenBounds); + + assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation()); + } + + @Test + public void testIgnoresForcedOrientationWhenParentHandles() { + final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080); + DisplayContent display = new TestDisplayContent.Builder( + mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); + + display.getRequestedOverrideConfiguration().orientation = + Configuration.ORIENTATION_LANDSCAPE; + display.onRequestedOverrideConfigurationChanged( + display.getRequestedOverrideConfiguration()); + Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); + Task task = rootTask.getBottomMostTask(); + ActivityRecord root = task.getTopNonFinishingActivity(); + + final WindowContainer parentWindowContainer = + new WindowContainer(mSystemServicesTestRule.getWindowManagerService()); + spyOn(parentWindowContainer); + parentWindowContainer.setBounds(fullScreenBounds); + doReturn(parentWindowContainer).when(task).getParent(); + doReturn(display.getDefaultTaskDisplayArea()).when(task).getDisplayArea(); + doReturn(rootTask).when(task).getRootTask(); + doReturn(true).when(parentWindowContainer).handlesOrientationChangeFromDescendant(); + + // Setting app to fixed portrait fits within parent, but Task shouldn't adjust the + // bounds because its parent says it will handle it at a later time. + root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + assertEquals(root, task.getRootActivity()); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); + assertEquals(fullScreenBounds, task.getBounds()); + } + + @Test + public void testComputeConfigResourceOverrides() { + final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920); + TestDisplayContent display = new TestDisplayContent.Builder( + mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); + final Configuration inOutConfig = new Configuration(); + final Configuration parentConfig = new Configuration(); + final int longSide = 1200; + final int shortSide = 600; + final Rect parentBounds = new Rect(0, 0, 250, 500); + final Rect parentAppBounds = new Rect(0, 0, 250, 480); + parentConfig.windowConfiguration.setBounds(parentBounds); + parentConfig.windowConfiguration.setAppBounds(parentAppBounds); + parentConfig.densityDpi = 400; + parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200 + parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100 + parentConfig.windowConfiguration.setRotation(ROTATION_0); + + // By default, the input bounds will fill parent. + task.computeConfigResourceOverrides(inOutConfig, parentConfig); + + assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp); + assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp); + assertEquals(parentAppBounds, inOutConfig.windowConfiguration.getAppBounds()); + assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation); + + // If bounds are overridden, config properties should be made to match. Surface hierarchy + // will crop for policy. + inOutConfig.setToDefaults(); + final Rect largerPortraitBounds = new Rect(0, 0, shortSide, longSide); + inOutConfig.windowConfiguration.setBounds(largerPortraitBounds); + task.computeConfigResourceOverrides(inOutConfig, parentConfig); + // The override bounds are beyond the parent, the out appBounds should not be intersected + // by parent appBounds. + assertEquals(largerPortraitBounds, inOutConfig.windowConfiguration.getAppBounds()); + assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160); + assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160); + + inOutConfig.setToDefaults(); + // Landscape bounds. + final Rect largerLandscapeBounds = new Rect(0, 0, longSide, shortSide); + inOutConfig.windowConfiguration.setBounds(largerLandscapeBounds); + + // Setup the display with a top stable inset. The later assertion will ensure the inset is + // excluded from screenHeightDp. + final int statusBarHeight = 100; + final DisplayPolicy policy = display.getDisplayPolicy(); + doAnswer(invocationOnMock -> { + final Rect insets = invocationOnMock.<Rect>getArgument(0); + insets.top = statusBarHeight; + return null; + }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0)); + + // Without limiting to be inside the parent bounds, the out screen size should keep relative + // to the input bounds. + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); + final ActivityRecord.CompatDisplayInsets compatIntsets = + new ActivityRecord.CompatDisplayInsets( + display, activity, /* fixedOrientationBounds= */ null); + task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets); + + assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); + assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi, + inOutConfig.screenHeightDp); + assertEquals(longSide * DENSITY_DEFAULT / parentConfig.densityDpi, + inOutConfig.screenWidthDp); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, inOutConfig.orientation); + } + + @Test + public void testComputeConfigResourceLayoutOverrides() { + final Rect fullScreenBounds = new Rect(0, 0, 1000, 2500); + TestDisplayContent display = new TestDisplayContent.Builder( + mAtm, fullScreenBounds.width(), fullScreenBounds.height()).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); + final Configuration inOutConfig = new Configuration(); + final Configuration parentConfig = new Configuration(); + final Rect nonLongBounds = new Rect(0, 0, 1000, 1250); + parentConfig.windowConfiguration.setBounds(fullScreenBounds); + parentConfig.windowConfiguration.setAppBounds(fullScreenBounds); + parentConfig.densityDpi = 400; + parentConfig.screenHeightDp = (fullScreenBounds.bottom * 160) / parentConfig.densityDpi; + parentConfig.screenWidthDp = (fullScreenBounds.right * 160) / parentConfig.densityDpi; + parentConfig.windowConfiguration.setRotation(ROTATION_0); + + // Set BOTH screenW/H to an override value + inOutConfig.screenWidthDp = nonLongBounds.width() * 160 / parentConfig.densityDpi; + inOutConfig.screenHeightDp = nonLongBounds.height() * 160 / parentConfig.densityDpi; + task.computeConfigResourceOverrides(inOutConfig, parentConfig); + + // screenLayout should honor override when both screenW/H are set. + assertTrue((inOutConfig.screenLayout & Configuration.SCREENLAYOUT_LONG_NO) != 0); + } + + @Test + public void testComputeNestedConfigResourceOverrides() { + final Task task = new TaskBuilder(mSupervisor).build(); + assertTrue(task.getResolvedOverrideBounds().isEmpty()); + int origScreenH = task.getConfiguration().screenHeightDp; + Configuration rootTaskConfig = new Configuration(); + rootTaskConfig.setTo(task.getRootTask().getRequestedOverrideConfiguration()); + rootTaskConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + + // Set bounds on root task (not task) and verify that the task resource configuration + // changes despite it's override bounds being empty. + Rect bounds = new Rect(task.getRootTask().getBounds()); + bounds.bottom = (int) (bounds.bottom * 0.6f); + rootTaskConfig.windowConfiguration.setBounds(bounds); + task.getRootTask().onRequestedOverrideConfigurationChanged(rootTaskConfig); + assertNotEquals(origScreenH, task.getConfiguration().screenHeightDp); + } + + @Test + public void testFullScreenTaskNotAdjustedByMinimalSize() { + final Task fullscreenTask = new TaskBuilder(mSupervisor).build(); + final Rect originalTaskBounds = new Rect(fullscreenTask.getBounds()); + final ActivityInfo aInfo = new ActivityInfo(); + aInfo.windowLayout = new ActivityInfo.WindowLayout(0 /* width */, 0 /* widthFraction */, + 0 /* height */, 0 /* heightFraction */, 0 /* gravity */, + originalTaskBounds.width() * 2 /* minWidth */, + originalTaskBounds.height() * 2 /* minHeight */); + fullscreenTask.setMinDimensions(aInfo); + fullscreenTask.onConfigurationChanged(fullscreenTask.getParent().getConfiguration()); + + assertEquals(originalTaskBounds, fullscreenTask.getBounds()); + } + + @Test + public void testInsetDisregardedWhenFreeformOverlapsNavBar() { + TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); + Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + DisplayInfo displayInfo = new DisplayInfo(); + mAtm.mContext.getDisplay().getDisplayInfo(displayInfo); + final int displayHeight = displayInfo.logicalHeight; + final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + final Configuration inOutConfig = new Configuration(); + final Configuration parentConfig = new Configuration(); + final int longSide = 1200; + final int shortSide = 600; + parentConfig.densityDpi = 400; + parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px + parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px + parentConfig.windowConfiguration.setRotation(ROTATION_0); + + final int longSideDp = 480; // longSide / density = 1200 / 400 * 160 + final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160 + final int screenLayout = parentConfig.screenLayout + & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK); + final int reducedScreenLayout = + Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp); + + // Portrait bounds overlapping with navigation bar, without insets. + final Rect freeformBounds = new Rect(0, + displayHeight - 10 - longSide, + shortSide, + displayHeight - 10); + inOutConfig.windowConfiguration.setBounds(freeformBounds); + // Set to freeform mode to verify bug fix. + inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + + task.computeConfigResourceOverrides(inOutConfig, parentConfig); + + // screenW/H should not be effected by parent since overridden and freeform + assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi, + inOutConfig.screenWidthDp); + assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi, + inOutConfig.screenHeightDp); + assertEquals(reducedScreenLayout, inOutConfig.screenLayout); + + inOutConfig.setToDefaults(); + // Landscape bounds overlapping with navigtion bar, without insets. + freeformBounds.set(0, + displayHeight - 10 - shortSide, + longSide, + displayHeight - 10); + inOutConfig.windowConfiguration.setBounds(freeformBounds); + inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + + task.computeConfigResourceOverrides(inOutConfig, parentConfig); + + assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi, + inOutConfig.screenWidthDp); + assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi, + inOutConfig.screenHeightDp); + assertEquals(reducedScreenLayout, inOutConfig.screenLayout); + } + + /** Ensures that the alias intent won't have target component resolved. */ + @Test + public void testTaskIntentActivityAlias() { + final String aliasClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".aliasActivity"; + final String targetClassName = DEFAULT_COMPONENT_PACKAGE_NAME + ".targetActivity"; + final ComponentName aliasComponent = + new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, aliasClassName); + final ComponentName targetComponent = + new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME, targetClassName); + + final Intent intent = new Intent(); + intent.setComponent(aliasComponent); + final ActivityInfo info = new ActivityInfo(); + info.applicationInfo = new ApplicationInfo(); + info.packageName = DEFAULT_COMPONENT_PACKAGE_NAME; + info.targetActivity = targetClassName; + + final Task task = new Task.Builder(mAtm) + .setTaskId(1) + .setActivityInfo(info) + .setIntent(intent) + .build(); + assertEquals("The alias activity component should be saved in task intent.", aliasClassName, + task.intent.getComponent().getClassName()); + + ActivityRecord aliasActivity = new ActivityBuilder(mAtm).setComponent( + aliasComponent).setTargetActivity(targetClassName).build(); + assertEquals("Should be the same intent filter.", true, + task.isSameIntentFilter(aliasActivity)); + + ActivityRecord targetActivity = new ActivityBuilder(mAtm).setComponent( + targetComponent).build(); + assertEquals("Should be the same intent filter.", true, + task.isSameIntentFilter(targetActivity)); + + ActivityRecord defaultActivity = new ActivityBuilder(mAtm).build(); + assertEquals("Should not be the same intent filter.", false, + task.isSameIntentFilter(defaultActivity)); + } + + /** Test that root activity index is reported correctly for several activities in the task. */ + @Test + public void testFindRootIndex() { + final Task task = getTestTask(); + // Add an extra activity on top of the root one + new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals("The root activity in the task must be reported.", task.getChildAt(0), + task.getRootActivity( + true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); + } + + /** + * Test that root activity index is reported correctly for several activities in the task when + * the activities on the bottom are finishing. + */ + @Test + public void testFindRootIndex_finishing() { + final Task task = getTestTask(); + // Add extra two activities and mark the two on the bottom as finishing. + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.finishing = true; + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.finishing = true; + new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals("The first non-finishing activity in the task must be reported.", + task.getChildAt(2), task.getRootActivity( + true /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); + } + + /** + * Test that root activity index is reported correctly for several activities in the task when + * looking for the 'effective root'. + */ + @Test + public void testFindRootIndex_effectiveRoot() { + final Task task = getTestTask(); + // Add an extra activity on top of the root one + new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals("The root activity in the task must be reported.", + task.getChildAt(0), task.getRootActivity( + false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); + } + + /** + * Test that root activity index is reported correctly when looking for the 'effective root' in + * case when bottom activities are relinquishing task identity or finishing. + */ + @Test + public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() { + final Task task = getTestTask(); + // Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and + // one above as finishing. + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.finishing = true; + new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals("The first non-finishing activity and non-relinquishing task identity " + + "must be reported.", task.getChildAt(2), task.getRootActivity( + false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); + } + + /** + * Test that root activity index is reported correctly when looking for the 'effective root' + * for the case when there is only a single activity that also has relinquishTaskIdentity set. + */ + @Test + public void testFindRootIndex_effectiveRoot_relinquishingAndSingleActivity() { + final Task task = getTestTask(); + // Set relinquishTaskIdentity for the only activity in the task + task.getBottomMostActivity().info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + + assertEquals("The root activity in the task must be reported.", + task.getChildAt(0), task.getRootActivity( + false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); + } + + /** + * Test that the topmost activity index is reported correctly when looking for the + * 'effective root' for the case when all activities have relinquishTaskIdentity set. + */ + @Test + public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() { + final Task task = getTestTask(); + // Set relinquishTaskIdentity for all activities in the task + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + + assertEquals("The topmost activity in the task must be reported.", + task.getChildAt(task.getChildCount() - 1), task.getRootActivity( + false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)); + } + + /** Test that bottom-most activity is reported in {@link Task#getRootActivity()}. */ + @Test + public void testGetRootActivity() { + final Task task = getTestTask(); + // Add an extra activity on top of the root one + new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals("The root activity in the task must be reported.", + task.getBottomMostActivity(), task.getRootActivity()); + } + + /** + * Test that first non-finishing activity is reported in {@link Task#getRootActivity()}. + */ + @Test + public void testGetRootActivity_finishing() { + final Task task = getTestTask(); + // Add an extra activity on top of the root one + new ActivityBuilder(mAtm).setTask(task).build(); + // Mark the root as finishing + task.getBottomMostActivity().finishing = true; + + assertEquals("The first non-finishing activity in the task must be reported.", + task.getChildAt(1), task.getRootActivity()); + } + + /** + * Test that relinquishTaskIdentity flag is ignored in {@link Task#getRootActivity()}. + */ + @Test + public void testGetRootActivity_relinquishTaskIdentity() { + final Task task = getTestTask(); + // Mark the bottom-most activity with FLAG_RELINQUISH_TASK_IDENTITY. + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + // Add an extra activity on top of the root one. + new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals("The root activity in the task must be reported.", + task.getBottomMostActivity(), task.getRootActivity()); + } + + /** + * Test that no activity is reported in {@link Task#getRootActivity()} when all activities + * in the task are finishing. + */ + @Test + public void testGetRootActivity_allFinishing() { + final Task task = getTestTask(); + // Mark the bottom-most activity as finishing. + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.finishing = true; + // Add an extra activity on top of the root one and mark it as finishing + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.finishing = true; + + assertNull("No activity must be reported if all are finishing", task.getRootActivity()); + } + + /** + * Test that first non-finishing activity is the root of task. + */ + @Test + public void testIsRootActivity() { + final Task task = getTestTask(); + // Mark the bottom-most activity as finishing. + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.finishing = true; + // Add an extra activity on top of the root one. + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + + assertFalse("Finishing activity must not be the root of task", activity0.isRootOfTask()); + assertTrue("Non-finishing activity must be the root of task", activity1.isRootOfTask()); + } + + /** + * Test that if all activities in the task are finishing, then the one on the bottom is the + * root of task. + */ + @Test + public void testIsRootActivity_allFinishing() { + final Task task = getTestTask(); + // Mark the bottom-most activity as finishing. + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.finishing = true; + // Add an extra activity on top of the root one and mark it as finishing + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.finishing = true; + + assertTrue("Bottom activity must be the root of task", activity0.isRootOfTask()); + assertFalse("Finishing activity on top must not be the root of task", + activity1.isRootOfTask()); + } + + /** + * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)}. + */ + @Test + public void testGetTaskForActivity() { + final Task task0 = getTestTask(); + final ActivityRecord activity0 = task0.getBottomMostActivity(); + + final Task task1 = getTestTask(); + final ActivityRecord activity1 = task1.getBottomMostActivity(); + + assertEquals(task0.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */)); + assertEquals(task1.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */)); + } + + /** + * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with finishing + * activity. + */ + @Test + public void testGetTaskForActivity_onlyRoot_finishing() { + final Task task = getTestTask(); + // Make the current root activity finishing + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.finishing = true; + // Add an extra activity on top - this will be the new root + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + // Add one more on top + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals(task.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */)); + assertEquals(task.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */)); + assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, + ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */)); + } + + /** + * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with activity that + * relinquishes task identity. + */ + @Test + public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() { + final Task task = getTestTask(); + // Make the current root activity relinquish task identity + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + // Add an extra activity on top - this will be the new root + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + // Add one more on top + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals(task.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */)); + assertEquals(task.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */)); + assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID, + ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */)); + } + + /** + * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} allowing non-root + * entries. + */ + @Test + public void testGetTaskForActivity_notOnlyRoot() { + final Task task = getTestTask(); + // Mark the bottom-most activity as finishing. + final ActivityRecord activity0 = task.getBottomMostActivity(); + activity0.finishing = true; + + // Add an extra activity on top of the root one and make it relinquish task identity + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY; + + // Add one more activity on top + final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build(); + + assertEquals(task.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity0.appToken, false /* onlyRoot */)); + assertEquals(task.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity1.appToken, false /* onlyRoot */)); + assertEquals(task.mTaskId, + ActivityRecord.getTaskForActivityLocked(activity2.appToken, false /* onlyRoot */)); + } + + /** + * Test {@link Task#updateEffectiveIntent()}. + */ + @Test + public void testUpdateEffectiveIntent() { + // Test simple case with a single activity. + final Task task = getTestTask(); + final ActivityRecord activity0 = task.getBottomMostActivity(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity0)); + } + + /** + * Test {@link Task#updateEffectiveIntent()} with root activity marked as finishing. This + * should make the task use the second activity when updating the intent. + */ + @Test + public void testUpdateEffectiveIntent_rootFinishing() { + // Test simple case with a single activity. + final Task task = getTestTask(); + final ActivityRecord activity0 = task.getBottomMostActivity(); + // Mark the bottom-most activity as finishing. + activity0.finishing = true; + // Add an extra activity on top of the root one + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity1)); + } + + /** + * Test {@link Task#updateEffectiveIntent()} when all activities are finishing or + * relinquishing task identity. In this case the root activity should still be used when + * updating the intent (legacy behavior). + */ + @Test + public void testUpdateEffectiveIntent_allFinishing() { + // Test simple case with a single activity. + final Task task = getTestTask(); + final ActivityRecord activity0 = task.getBottomMostActivity(); + // Mark the bottom-most activity as finishing. + activity0.finishing = true; + // Add an extra activity on top of the root one and make it relinquish task identity + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build(); + activity1.finishing = true; + + // Task must still update the intent using the root activity (preserving legacy behavior). + spyOn(task); + task.updateEffectiveIntent(); + verify(task).setIntent(eq(activity0)); + } + + @Test + public void testSaveLaunchingStateWhenConfigurationChanged() { + LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; + spyOn(persister); + + final Task task = getTestTask(); + task.setHasBeenVisible(false); + task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + task.setHasBeenVisible(true); + task.onConfigurationChanged(task.getParent().getConfiguration()); + + verify(persister).saveTask(task, task.getDisplayContent()); + } + + @Test + public void testSaveLaunchingStateWhenClearingParent() { + LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; + spyOn(persister); + + final Task task = getTestTask(); + task.setHasBeenVisible(false); + task.getDisplayContent().setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final DisplayContent oldDisplay = task.getDisplayContent(); + + LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams(); + params.mWindowingMode = WINDOWING_MODE_UNDEFINED; + persister.getLaunchParams(task, null, params); + assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode); + + task.setHasBeenVisible(true); + task.removeImmediately(); + + verify(persister).saveTask(task, oldDisplay); + + persister.getLaunchParams(task, null, params); + assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode); + } + + @Test + public void testNotSaveLaunchingStateNonFreeformDisplay() { + LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; + spyOn(persister); + + final Task task = getTestTask(); + task.setHasBeenVisible(false); + task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + task.setHasBeenVisible(true); + task.onConfigurationChanged(task.getParent().getConfiguration()); + + Mockito.verify(persister, never()).saveTask(same(task), any()); + } + + @Test + public void testNotSaveLaunchingStateWhenNotFullscreenOrFreeformWindow() { + LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; + spyOn(persister); + + final Task task = getTestTask(); + task.setHasBeenVisible(false); + task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + task.getRootTask().setWindowingMode(WINDOWING_MODE_PINNED); + + task.setHasBeenVisible(true); + task.onConfigurationChanged(task.getParent().getConfiguration()); + + Mockito.verify(persister, never()).saveTask(same(task), any()); + } + + @Test + public void testNotSaveLaunchingStateForNonLeafTask() { + LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister; + spyOn(persister); + + final Task task = getTestTask(); + task.setHasBeenVisible(false); + task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM); + task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + final Task leafTask = createTaskInRootTask(task, 0 /* userId */); + + leafTask.setHasBeenVisible(true); + task.setHasBeenVisible(true); + task.onConfigurationChanged(task.getParent().getConfiguration()); + + Mockito.verify(persister, never()).saveTask(same(task), any()); + verify(persister).saveTask(same(leafTask), any()); + } + + @Test + public void testNotSpecifyOrientationByFloatingTask() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true).setCreateParentTask(true).build(); + final ActivityRecord activity = task.getTopMostActivity(); + final WindowContainer<?> parentContainer = task.getParent(); + final TaskDisplayArea taskDisplayArea = task.getDisplayArea(); + activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parentContainer.getOrientation()); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation()); + + task.setWindowingMode(WINDOWING_MODE_PINNED); + + // TDA returns the last orientation when child returns UNSET + assertEquals(SCREEN_ORIENTATION_UNSET, parentContainer.getOrientation()); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation()); + } + + @Test + public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() { + final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); + final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea( + mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea", + FEATURE_VENDOR_FIRST); + final Task firstRootTask = firstTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final Task secondRootTask = secondTaskDisplayArea.createRootTask( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mAtm) + .setTask(firstRootTask).build(); + final ActivityRecord secondActivity = new ActivityBuilder(mAtm) + .setTask(secondRootTask).build(); + firstActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + secondActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + + // Activity on TDA1 is focused + mDisplayContent.setFocusedApp(firstActivity); + + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation()); + assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation()); + + // No focused app, TDA1 is still recorded as last focused. + mDisplayContent.setFocusedApp(null); + + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation()); + assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation()); + + // Activity on TDA2 is focused + mDisplayContent.setFocusedApp(secondActivity); + + assertEquals(SCREEN_ORIENTATION_UNSET, firstTaskDisplayArea.getOrientation()); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, secondTaskDisplayArea.getOrientation()); + } + + @Test + public void testNotifyOrientationChangeCausedByConfigurationChange() { + final Task task = getTestTask(); + final ActivityRecord activity = task.getTopMostActivity(); + final DisplayContent display = task.getDisplayContent(); + display.setWindowingMode(WINDOWING_MODE_FREEFORM); + + activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation()); + verify(display).onDescendantOrientationChanged(same(task)); + reset(display); + + display.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, task.getOrientation()); + verify(display).onDescendantOrientationChanged(same(task)); + } + + private Task getTestTask() { + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + return task.getBottomMostTask(); + } + + private void testRootTaskBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, + Rect expectedConfigBounds) { + + TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea(); + Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build(); + + final Configuration parentConfig = rootTask.getConfiguration(); + parentConfig.windowConfiguration.setAppBounds(parentBounds); + task.setBounds(bounds); + + task.resolveOverrideConfiguration(parentConfig); + // Assert that both expected and actual are null or are equal to each other + assertEquals(expectedConfigBounds, + task.getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()); + } + + private byte[] serializeToBytes(Task r) throws Exception { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + final TypedXmlSerializer serializer = Xml.newFastSerializer(); + serializer.setOutput(os, "UTF-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TASK_TAG); + r.saveToXml(serializer); + serializer.endTag(null, TASK_TAG); + serializer.endDocument(); + + os.flush(); + return os.toByteArray(); + } + } + + private Task restoreFromBytes(byte[] in) throws IOException, XmlPullParserException { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(in))) { + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(reader); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(TASK_TAG, parser.getName()); + return Task.restoreFromXml(parser, mAtm.mTaskSupervisor); + } + } + + private Task createTask(int taskId) { + return new Task.Builder(mAtm) + .setTaskId(taskId) + .setIntent(new Intent()) + .setRealActivity(ActivityBuilder.getDefaultComponent()) + .setEffectiveUid(10050) + .buildInner(); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 79ef8680dfec..2dfb3a1a84bc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -69,10 +69,8 @@ public class TransitionTests extends WindowTestsBase { ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; ArraySet<WindowContainer> participants = transition.mParticipants; - final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task newTask = createTask(mDisplayContent); + final Task oldTask = createTask(mDisplayContent); final ActivityRecord closing = createActivityRecord(oldTask); final ActivityRecord opening = createActivityRecord(newTask); // Start states. @@ -128,12 +126,10 @@ public class TransitionTests extends WindowTestsBase { ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; ArraySet<WindowContainer> participants = transition.mParticipants; - final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task newNestedTask = createTaskInStack(newTask, 0); - final Task newNestedTask2 = createTaskInStack(newTask, 0); - final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task newTask = createTask(mDisplayContent); + final Task newNestedTask = createTaskInRootTask(newTask, 0); + final Task newNestedTask2 = createTaskInRootTask(newTask, 0); + final Task oldTask = createTask(mDisplayContent); final ActivityRecord closing = createActivityRecord(oldTask); final ActivityRecord opening = createActivityRecord(newNestedTask); final ActivityRecord opening2 = createActivityRecord(newNestedTask2); @@ -179,11 +175,9 @@ public class TransitionTests extends WindowTestsBase { final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; ArraySet<WindowContainer> participants = transition.mParticipants; - final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task showNestedTask = createTaskInStack(showTask, 0); - final Task showTask2 = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task showTask = createTask(mDisplayContent); + final Task showNestedTask = createTaskInRootTask(showTask, 0); + final Task showTask2 = createTask(mDisplayContent); final DisplayArea tda = showTask.getDisplayArea(); final ActivityRecord showing = createActivityRecord(showNestedTask); final ActivityRecord showing2 = createActivityRecord(showTask2); @@ -231,12 +225,10 @@ public class TransitionTests extends WindowTestsBase { public void testCreateInfo_existenceChange() { final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN); - final Task openTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task openTask = createTask(mDisplayContent); final ActivityRecord opening = createActivityRecord(openTask); opening.mVisibleRequested = false; // starts invisible - final Task closeTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task closeTask = createTask(mDisplayContent); final ActivityRecord closing = createActivityRecord(closeTask); closing.mVisibleRequested = true; // starts visible @@ -268,8 +260,8 @@ public class TransitionTests extends WindowTestsBase { final Task[] tasks = new Task[taskCount]; for (int i = 0; i < taskCount; ++i) { // Each add goes on top, so at the end of this, task[9] should be on top - tasks[i] = createTaskStackOnDisplay(WINDOWING_MODE_FREEFORM, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + tasks[i] = createTask(mDisplayContent, + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); final ActivityRecord act = createActivityRecord(tasks[i]); // alternate so that the transition doesn't get promoted to the display area act.mVisibleRequested = (i % 2) == 0; // starts invisible @@ -305,8 +297,8 @@ public class TransitionTests extends WindowTestsBase { final Task[] tasks = new Task[taskCount]; for (int i = 0; i < taskCount; ++i) { // Each add goes on top, so at the end of this, task[9] should be on top - tasks[i] = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + tasks[i] = createTask(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final ActivityRecord act = createActivityRecord(tasks[i]); // alternate so that the transition doesn't get promoted to the display area act.mVisibleRequested = (i % 2) == 0; // starts invisible @@ -353,15 +345,13 @@ public class TransitionTests extends WindowTestsBase { ArraySet<WindowContainer> participants = transition.mParticipants; ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); - final Task openTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task openInOpenTask = createTaskInStack(openTask, 0); + final Task openTask = createTask(mDisplayContent); + final Task openInOpenTask = createTaskInRootTask(openTask, 0); final ActivityRecord openInOpen = createActivityRecord(openInOpenTask); - final Task changeTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task changeInChangeTask = createTaskInStack(changeTask, 0); - final Task openInChangeTask = createTaskInStack(changeTask, 0); + final Task changeTask = createTask(mDisplayContent); + final Task changeInChangeTask = createTaskInRootTask(changeTask, 0); + final Task openInChangeTask = createTaskInRootTask(changeTask, 0); final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask); final ActivityRecord openInChange = createActivityRecord(openInChangeTask); // set organizer for everything so that they all get added to transition info diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index b88173d5c1f0..6919c4cf6a7c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -811,18 +811,18 @@ public class WindowContainerTests extends WindowTestsBase { @Test public void testOnDisplayChanged() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); final DisplayContent newDc = createNewDisplay(); - stack.getDisplayArea().removeRootTask(stack); - newDc.getDefaultTaskDisplayArea().addChild(stack, POSITION_TOP); + rootTask.getDisplayArea().removeRootTask(rootTask); + newDc.getDefaultTaskDisplayArea().addChild(rootTask, POSITION_TOP); - verify(stack).onDisplayChanged(newDc); + verify(rootTask).onDisplayChanged(newDc); verify(task).onDisplayChanged(newDc); verify(activity).onDisplayChanged(newDc); - assertEquals(newDc, stack.mDisplayContent); + assertEquals(newDc, rootTask.mDisplayContent); assertEquals(newDc, task.mDisplayContent); assertEquals(newDc, activity.mDisplayContent); } @@ -854,21 +854,21 @@ public class WindowContainerTests extends WindowTestsBase { @Test public void testTaskCanApplyAnimation() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task); final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task); verifyWindowContainerApplyAnimation(task, activity1, activity2); } @Test - public void testStackCanApplyAnimation() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); + public void testRootTaskCanApplyAnimation() { + final Task rootTask = createTask(mDisplayContent); final ActivityRecord activity2 = createActivityRecord(mDisplayContent, - createTaskInStack(stack, 0 /* userId */)); + createTaskInRootTask(rootTask, 0 /* userId */)); final ActivityRecord activity1 = createActivityRecord(mDisplayContent, - createTaskInStack(stack, 0 /* userId */)); - verifyWindowContainerApplyAnimation(stack, activity1, activity2); + createTaskInRootTask(rootTask, 0 /* userId */)); + verifyWindowContainerApplyAnimation(rootTask, activity1, activity2); } @Test @@ -878,21 +878,21 @@ public class WindowContainerTests extends WindowTestsBase { assertNull(windowContainer.getDisplayArea()); - // ActivityStack > WindowContainer - final Task activityStack = createTaskStackOnDisplay(mDisplayContent); - activityStack.addChild(windowContainer, 0); - activityStack.setParent(null); + // Task > WindowContainer + final Task task = createTask(mDisplayContent); + task.addChild(windowContainer, 0); + task.setParent(null); assertNull(windowContainer.getDisplayArea()); - assertNull(activityStack.getDisplayArea()); + assertNull(task.getDisplayArea()); - // TaskDisplayArea > ActivityStack > WindowContainer + // TaskDisplayArea > Task > WindowContainer final TaskDisplayArea taskDisplayArea = new TaskDisplayArea( mDisplayContent, mWm, "TaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER); - taskDisplayArea.addChild(activityStack, 0); + taskDisplayArea.addChild(task, 0); assertEquals(taskDisplayArea, windowContainer.getDisplayArea()); - assertEquals(taskDisplayArea, activityStack.getDisplayArea()); + assertEquals(taskDisplayArea, task.getDisplayArea()); assertEquals(taskDisplayArea, taskDisplayArea.getDisplayArea()); // DisplayArea @@ -986,8 +986,8 @@ public class WindowContainerTests extends WindowTestsBase { @Test public void testFreezeInsets() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final ActivityRecord activity = createActivityRecord(mDisplayContent, stack); + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); // Set visibility to false, verify the main window of the task will be set the frozen @@ -1002,8 +1002,8 @@ public class WindowContainerTests extends WindowTestsBase { @Test public void testFreezeInsetsStateWhenAppTransition() { - final Task stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); + final Task rootTask = createTask(mDisplayContent); + final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 6d0e510ba626..baf072dbbc10 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -80,18 +80,18 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testTaskFocusChange_stackNotHomeType_focusChanges() throws RemoteException { + public void testTaskFocusChange_rootTaskNotHomeType_focusChanges() throws RemoteException { DisplayContent display = createNewDisplay(); // Current focused window - Task focusedStack = createTaskStackOnDisplay( - WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, display); - Task focusedTask = createTaskInStack(focusedStack, 0 /* userId */); + Task focusedRootTask = createTask( + display, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + Task focusedTask = createTaskInRootTask(focusedRootTask, 0 /* userId */); WindowState focusedWindow = createAppWindow(focusedTask, TYPE_APPLICATION, "App Window"); mDisplayContent.mCurrentFocus = focusedWindow; // Tapped task - Task tappedStack = createTaskStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, display); - Task tappedTask = createTaskInStack(tappedStack, 0 /* userId */); + Task tappedRootTask = createTask( + display, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */); spyOn(mWm.mAtmService); mWm.handleTaskFocusChange(tappedTask); @@ -100,19 +100,19 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testTaskFocusChange_stackHomeTypeWithSameTaskDisplayArea_focusDoesNotChange() + public void testTaskFocusChange_rootTaskHomeTypeWithSameTaskDisplayArea_focusDoesNotChange() throws RemoteException { DisplayContent display = createNewDisplay(); // Current focused window - Task focusedStack = createTaskStackOnDisplay( - WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, display); - Task focusedTask = createTaskInStack(focusedStack, 0 /* userId */); + Task focusedRootTask = createTask( + display, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + Task focusedTask = createTaskInRootTask(focusedRootTask, 0 /* userId */); WindowState focusedWindow = createAppWindow(focusedTask, TYPE_APPLICATION, "App Window"); mDisplayContent.mCurrentFocus = focusedWindow; // Tapped home task - Task tappedStack = createTaskStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, display); - Task tappedTask = createTaskInStack(tappedStack, 0 /* userId */); + Task tappedRootTask = createTask( + display, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); + Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */); spyOn(mWm.mAtmService); mWm.handleTaskFocusChange(tappedTask); @@ -121,21 +121,21 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testTaskFocusChange_stackHomeTypeWithDifferentTaskDisplayArea_focusChanges() + public void testTaskFocusChange_rootTaskHomeTypeWithDifferentTaskDisplayArea_focusChanges() throws RemoteException { final DisplayContent display = createNewDisplay(); final TaskDisplayArea secondTda = createTaskDisplayArea( display, mWm, "Tapped TDA", FEATURE_VENDOR_FIRST); // Current focused window - Task focusedStack = createTaskStackOnDisplay( - WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, display); - Task focusedTask = createTaskInStack(focusedStack, 0 /* userId */); + Task focusedRootTask = createTask( + display, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + Task focusedTask = createTaskInRootTask(focusedRootTask, 0 /* userId */); WindowState focusedWindow = createAppWindow(focusedTask, TYPE_APPLICATION, "App Window"); mDisplayContent.mCurrentFocus = focusedWindow; // Tapped home task on another task display area - Task tappedStack = createTaskStackOnTaskDisplayArea( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, secondTda); - Task tappedTask = createTaskInStack(tappedStack, 0 /* userId */); + Task tappedRootTask = createTask(secondTda, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); + Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */); spyOn(mWm.mAtmService); mWm.handleTaskFocusChange(tappedTask); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 01c503e01326..e2b58bcb4bd5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -125,8 +125,8 @@ public class WindowOrganizerTests extends WindowTestsBase { return registerMockOrganizer(null); } - Task createTask(Task stack, boolean fakeDraw) { - final Task task = createTaskInStack(stack, 0); + Task createTask(Task rootTask, boolean fakeDraw) { + final Task task = createTaskInRootTask(rootTask, 0); if (fakeDraw) { task.setHasBeenVisible(true); @@ -134,13 +134,13 @@ public class WindowOrganizerTests extends WindowTestsBase { return task; } - Task createTask(Task stack) { + Task createTask(Task rootTask) { // Fake draw notifications for most of our tests. - return createTask(stack, true); + return createTask(rootTask, true); } - Task createStack() { - return createTaskStackOnDisplay(mDisplayContent); + Task createRootTask() { + return createTask(mDisplayContent); } @Before @@ -153,14 +153,14 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testAppearVanish() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - stack.removeImmediately(); + rootTask.removeImmediately(); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer).onTaskVanished(any()); @@ -169,21 +169,21 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testAppearWaitsForVisibility() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack, false); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask, false); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, never()) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - stack.setHasBeenVisible(true); + rootTask.setHasBeenVisible(true); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); - assertTrue(stack.getHasBeenVisible()); + assertTrue(rootTask.getHasBeenVisible()); verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - stack.removeImmediately(); + rootTask.removeImmediately(); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer).onTaskVanished(any()); @@ -192,108 +192,108 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testNoVanishedIfNoAppear() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack, false /* hasBeenVisible */); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask, false /* hasBeenVisible */); // In this test we skip making the Task visible, and verify // that even though a TaskOrganizer is set remove doesn't emit // a vanish callback, because we never emitted appear. - stack.setTaskOrganizer(organizer); + rootTask.setTaskOrganizer(organizer); verify(organizer, never()) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - stack.removeImmediately(); + rootTask.removeImmediately(); verify(organizer, never()).onTaskVanished(any()); } @Test public void testTaskNoDraw() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack, false /* fakeDraw */); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask, false /* fakeDraw */); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, never()) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - assertTrue(stack.isOrganized()); + assertTrue(rootTask.isOrganized()); mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); - assertTaskVanished(organizer, false /* expectVanished */, stack); - assertFalse(stack.isOrganized()); + assertTaskVanished(organizer, false /* expectVanished */, rootTask); + assertFalse(rootTask.isOrganized()); } @Test public void testClearOrganizer() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - assertTrue(stack.isOrganized()); + assertTrue(rootTask.isOrganized()); - stack.setTaskOrganizer(null); + rootTask.setTaskOrganizer(null); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer).onTaskVanished(any()); - assertFalse(stack.isOrganized()); + assertFalse(rootTask.isOrganized()); } @Test public void testUnregisterOrganizer() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - assertTrue(stack.isOrganized()); + assertTrue(rootTask.isOrganized()); mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); - assertTaskVanished(organizer, true /* expectVanished */, stack); - assertFalse(stack.isOrganized()); + assertTaskVanished(organizer, true /* expectVanished */, rootTask); + assertFalse(rootTask.isOrganized()); } @Test public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException { - final Task stack = createStack(); - final Task task = createTask(stack); - final Task stack2 = createStack(); - final Task task2 = createTask(stack2); - final Task stack3 = createStack(); - final Task task3 = createTask(stack3); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final Task rootTask2 = createRootTask(); + final Task task2 = createTask(rootTask2); + final Task rootTask3 = createRootTask(); + final Task task3 = createTask(rootTask3); final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>(); final ITaskOrganizer organizer = registerMockOrganizer(existingTasks); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); // verify that tasks are returned and taskAppeared is not called - assertContainsTasks(existingTasks, stack, stack2, stack3); + assertContainsTasks(existingTasks, rootTask, rootTask2, rootTask3); verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); verify(organizer, times(0)).onTaskVanished(any()); - assertTrue(stack.isOrganized()); + assertTrue(rootTask.isOrganized()); // Now we replace the registration and verify the new organizer receives existing tasks final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>(); final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); - assertContainsTasks(existingTasks2, stack, stack2, stack3); + assertContainsTasks(existingTasks2, rootTask, rootTask2, rootTask3); verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); verify(organizer2, times(0)).onTaskVanished(any()); // Removed tasks from the original organizer - assertTaskVanished(organizer, true /* expectVanished */, stack, stack2, stack3); - assertTrue(stack2.isOrganized()); + assertTaskVanished(organizer, true /* expectVanished */, rootTask, rootTask2, rootTask3); + assertTrue(rootTask2.isOrganized()); // Now we unregister the second one, the first one should automatically be reregistered // so we verify that it's now seeing changes. @@ -302,18 +302,18 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(3)) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - assertTaskVanished(organizer2, true /* expectVanished */, stack, stack2, stack3); + assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3); } @Test public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException { - final Task stack = createStack(); - final Task task = createTask(stack); - final Task stack2 = createStack(); - final Task task2 = createTask(stack2); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final Task rootTask2 = createRootTask(); + final Task task2 = createTask(rootTask2); ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>(); final ITaskOrganizer organizer = registerMockOrganizer(existingTasks); - assertContainsTasks(existingTasks, stack, stack2); + assertContainsTasks(existingTasks, rootTask, rootTask2); // Verify we don't get onTaskAppeared if we are returned the tasks verify(organizer, never()) @@ -323,21 +323,21 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testTaskTransaction() { removeGlobalMinSizeRestriction(); - final Task stack = new TaskBuilder(mSupervisor) + final Task rootTask = new TaskBuilder(mSupervisor) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - final Task task = stack.getTopMostTask(); + final Task task = rootTask.getTopMostTask(); testTransaction(task); } @Test - public void testStackTransaction() { + public void testRootTaskTransaction() { removeGlobalMinSizeRestriction(); - final Task stack = new TaskBuilder(mSupervisor) + final Task rootTask = new TaskBuilder(mSupervisor) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); RootTaskInfo info = mWm.mAtmService.getRootTaskInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); - assertEquals(stack.mRemoteToken.toWindowContainerToken(), info.token); - testTransaction(stack); + assertEquals(rootTask.mRemoteToken.toWindowContainerToken(), info.token); + testTransaction(rootTask); } @Test @@ -357,9 +357,9 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testSetWindowingMode() { - final Task stack = new TaskBuilder(mSupervisor) + final Task rootTask = new TaskBuilder(mSupervisor) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - testSetWindowingMode(stack); + testSetWindowingMode(rootTask); final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea"); displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -376,30 +376,30 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testSetActivityWindowingMode() { final ActivityRecord record = makePipableActivity(); - final Task stack = record.getRootTask(); + final Task rootTask = record.getRootTask(); final WindowContainerTransaction t = new WindowContainerTransaction(); - t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED); + t.setWindowingMode(rootTask.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED); t.setActivityWindowingMode( - stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN); + rootTask.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertEquals(WINDOWING_MODE_FULLSCREEN, record.getWindowingMode()); - assertEquals(WINDOWING_MODE_PINNED, stack.getWindowingMode()); + assertEquals(WINDOWING_MODE_PINNED, rootTask.getWindowingMode()); } @Test public void testContainerFocusableChanges() { removeGlobalMinSizeRestriction(); - final Task stack = new TaskBuilder(mSupervisor) + final Task rootTask = new TaskBuilder(mSupervisor) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - final Task task = stack.getTopMostTask(); + final Task task = rootTask.getTopMostTask(); WindowContainerTransaction t = new WindowContainerTransaction(); assertTrue(task.isFocusable()); - t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), false); + t.setFocusable(rootTask.mRemoteToken.toWindowContainerToken(), false); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertFalse(task.isFocusable()); - t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(rootTask.mRemoteToken.toWindowContainerToken(), true); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertTrue(task.isFocusable()); } @@ -407,25 +407,25 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testContainerHiddenChanges() { removeGlobalMinSizeRestriction(); - final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true) + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); WindowContainerTransaction t = new WindowContainerTransaction(); - assertTrue(stack.shouldBeVisible(null)); - t.setHidden(stack.mRemoteToken.toWindowContainerToken(), true); + assertTrue(rootTask.shouldBeVisible(null)); + t.setHidden(rootTask.mRemoteToken.toWindowContainerToken(), true); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); - assertFalse(stack.shouldBeVisible(null)); - t.setHidden(stack.mRemoteToken.toWindowContainerToken(), false); + assertFalse(rootTask.shouldBeVisible(null)); + t.setHidden(rootTask.mRemoteToken.toWindowContainerToken(), false); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); - assertTrue(stack.shouldBeVisible(null)); + assertTrue(rootTask.shouldBeVisible(null)); } @Test public void testSetIgnoreOrientationRequest_taskDisplayArea() { removeGlobalMinSizeRestriction(); final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final Task stack = taskDisplayArea.createRootTask( + final Task rootTask = taskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build(); taskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mDisplayContent.setFocusedApp(activity); activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); @@ -461,9 +461,9 @@ public class WindowOrganizerTests extends WindowTestsBase { public void testSetIgnoreOrientationRequest_displayContent() { removeGlobalMinSizeRestriction(); final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); - final Task stack = taskDisplayArea.createRootTask( + final Task rootTask = taskDisplayArea.createRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(stack).build(); + final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build(); mDisplayContent.setFocusedApp(activity); activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); @@ -491,21 +491,21 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testOverrideConfigSize() { removeGlobalMinSizeRestriction(); - final Task stack = new TaskBuilder(mSupervisor) + final Task rootTask = new TaskBuilder(mSupervisor) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); - final Task task = stack.getTopMostTask(); + final Task task = rootTask.getTopMostTask(); WindowContainerTransaction t = new WindowContainerTransaction(); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); final int origScreenWDp = task.getConfiguration().screenHeightDp; final int origScreenHDp = task.getConfiguration().screenHeightDp; t = new WindowContainerTransaction(); // verify that setting config overrides on parent restricts children. - t.setScreenSizeDp(stack.mRemoteToken + t.setScreenSizeDp(rootTask.mRemoteToken .toWindowContainerToken(), origScreenWDp, origScreenHDp / 2); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertEquals(origScreenHDp / 2, task.getConfiguration().screenHeightDp); t = new WindowContainerTransaction(); - t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED, + t.setScreenSizeDp(rootTask.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp); @@ -572,14 +572,14 @@ public class WindowOrganizerTests extends WindowTestsBase { mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); RunningTaskInfo info1 = task.getTaskInfo(); - final Task stack = createTaskStackOnDisplay( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); - assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); + final Task rootTask = createTask( + mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD); + assertEquals(mDisplayContent.getWindowingMode(), rootTask.getWindowingMode()); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); + wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertEquals(info1.configuration.windowConfiguration.getWindowingMode(), - stack.getWindowingMode()); + rootTask.getWindowingMode()); // Info should reflect new membership List<Task> infos = getTasksCreatedByOrganizer(mDisplayContent); @@ -591,14 +591,14 @@ public class WindowOrganizerTests extends WindowTestsBase { Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask(); Configuration c = new Configuration(task1.getRequestedOverrideConfiguration()); c.windowConfiguration.setBounds(newSize); - doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any(), any()); + doNothing().when(rootTask).adjustForMinimalTaskDimensions(any(), any(), any()); task1.onRequestedOverrideConfigurationChanged(c); - assertEquals(newSize, stack.getBounds()); + assertEquals(newSize, rootTask.getBounds()); wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); + wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); - assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); + assertEquals(mDisplayContent.getWindowingMode(), rootTask.getWindowingMode()); infos = getTasksCreatedByOrganizer(mDisplayContent); info1 = infos.get(0).getTaskInfo(); assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); @@ -644,36 +644,39 @@ public class WindowOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); called[0] = false; - final Task stack = createTaskStackOnDisplay( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task rootTask = createTask( + mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD); Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); + wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); lastReportedTiles.clear(); called[0] = false; - final Task stack2 = createTaskStackOnDisplay( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + final Task rootTask2 = createTask( + mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME); wct = new WindowContainerTransaction(); - wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); + wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), + info1.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType); lastReportedTiles.clear(); called[0] = false; - task1.positionChildAt(POSITION_TOP, stack, false /* includingParents */); + task1.positionChildAt(POSITION_TOP, rootTask, false /* includingParents */); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); lastReportedTiles.clear(); called[0] = false; wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); - wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); + wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), + null, true /* onTop */); + wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), + null, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType); @@ -723,10 +726,10 @@ public class WindowOrganizerTests extends WindowTestsBase { final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks( mDisplayContent.mDisplayId, null /* activityTypes */).size(); - final Task stack = createTaskStackOnDisplay( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); - final Task stack2 = createTaskStackOnDisplay( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + final Task rootTask = createTask( + mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD); + final Task rootTask2 = createTask( + mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME); // Check getRootTasks works List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks( @@ -735,8 +738,10 @@ public class WindowOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); - wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info2.token, true /* onTop */); + wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), + info1.token, true /* onTop */); + wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), + info2.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertFalse(lastReportedTiles.isEmpty()); assertEquals(ACTIVITY_TYPE_STANDARD, @@ -746,7 +751,8 @@ public class WindowOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); wct = new WindowContainerTransaction(); - wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, false /* onTop */); + wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), + info1.token, false /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertFalse(lastReportedTiles.isEmpty()); // Standard should still be on top of tile 1, so no change there @@ -771,7 +777,7 @@ public class WindowOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); wct = new WindowContainerTransaction(); - wct.reorder(stack2.mRemoteToken.toWindowContainerToken(), true /* onTop */); + wct.reorder(rootTask2.mRemoteToken.toWindowContainerToken(), true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); // Home should now be on top. No change occurs in second tile, so not reported assertEquals(1, lastReportedTiles.size()); @@ -780,10 +786,10 @@ public class WindowOrganizerTests extends WindowTestsBase { // This just needs to not crash (ie. it should be possible to reparent to display twice) wct = new WindowContainerTransaction(); - wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); + wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); wct = new WindowContainerTransaction(); - wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); + wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); } @@ -799,8 +805,8 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testBLASTCallbackWithActivityChildren() { - final Task stackController1 = createStack(); - final Task task = createTask(stackController1); + final Task rootTaskController1 = createRootTask(); + final Task task = createTask(rootTaskController1); final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); w.mActivityRecord.mVisibleRequested = true; @@ -926,11 +932,11 @@ public class WindowOrganizerTests extends WindowTestsBase { ChangeSavingOrganizer o = new ChangeSavingOrganizer(); mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o); - final Task stack = createStack(); - final Task task = createTask(stack); - final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription")); waitUntilHandlersIdle(); assertEquals("TestDescription", o.mChangedInfo.taskDescription.getLabel()); @@ -939,29 +945,29 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testPreventDuplicateAppear() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack, false /* fakeDraw */); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask, false /* fakeDraw */); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - stack.setTaskOrganizer(organizer); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + rootTask.setTaskOrganizer(organizer); // setHasBeenVisible was already called once by the set-up code. - stack.setHasBeenVisible(true); + rootTask.setHasBeenVisible(true); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(1)) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - stack.setTaskOrganizer(null); + rootTask.setTaskOrganizer(null); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(1)).onTaskVanished(any()); - stack.setTaskOrganizer(organizer); + rootTask.setTaskOrganizer(organizer); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(2)) .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); - stack.removeImmediately(); + rootTask.removeImmediately(); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(2)).onTaskVanished(any()); @@ -970,15 +976,15 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testInterceptBackPressedOnTaskRoot() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); - final ActivityRecord activity = createActivityRecord(stack.mDisplayContent, task); - final Task stack2 = createStack(); - final Task task2 = createTask(stack2); - final ActivityRecord activity2 = createActivityRecord(stack.mDisplayContent, task2); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task); + final Task rootTask2 = createRootTask(); + final Task task2 = createTask(rootTask2); + final ActivityRecord activity2 = createActivityRecord(rootTask.mDisplayContent, task2); - assertTrue(stack.isOrganized()); - assertTrue(stack2.isOrganized()); + assertTrue(rootTask.isOrganized()); + assertTrue(rootTask2.isOrganized()); // Verify a back pressed does not call the organizer mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, @@ -989,7 +995,7 @@ public class WindowOrganizerTests extends WindowTestsBase { // Enable intercepting back mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot( - stack.mRemoteToken.toWindowContainerToken(), true); + rootTask.mRemoteToken.toWindowContainerToken(), true); // Verify now that the back press does call the organizer mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, @@ -1000,7 +1006,7 @@ public class WindowOrganizerTests extends WindowTestsBase { // Disable intercepting back mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot( - stack.mRemoteToken.toWindowContainerToken(), false); + rootTask.mRemoteToken.toWindowContainerToken(), false); // Verify now that the back press no longer calls the organizer mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, @@ -1012,8 +1018,8 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testBLASTCallbackWithWindows() throws Exception { - final Task stackController = createStack(); - final Task task = createTask(stackController); + final Task rootTaskController = createRootTask(); + final Task task = createTask(rootTaskController); final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1"); final WindowState w2 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 2"); makeWindowVisible(w1); @@ -1077,7 +1083,7 @@ public class WindowOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); - final Task task1 = createStack(); + final Task task1 = createRootTask(); final Task task2 = createTask(rootTask, false /* fakeDraw */); WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparent(task1.mRemoteToken.toWindowContainerToken(), @@ -1090,19 +1096,19 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testAppearDeferThenInfoChange() { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); + final Task rootTask = createRootTask(); // Assume layout defer mWm.mWindowPlacerLocked.deferLayout(); - final Task task = createTask(stack); - final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task); + final Task task = createTask(rootTask); + final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription")); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_APPEARED, pendingEvents.get(0).mEventType); assertEquals("TestDescription", @@ -1112,35 +1118,35 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testAppearDeferThenVanish() { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); + final Task rootTask = createRootTask(); // Assume layout defer mWm.mWindowPlacerLocked.deferLayout(); - final Task task = createTask(stack); + final Task task = createTask(rootTask); - stack.removeImmediately(); + rootTask.removeImmediately(); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); assertEquals(0, pendingEvents.size()); } @Test public void testInfoChangeDeferMultiple() { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); - final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task); // Assume layout defer mWm.mWindowPlacerLocked.deferLayout(); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription")); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType); assertEquals("TestDescription", @@ -1149,7 +1155,7 @@ public class WindowOrganizerTests extends WindowTestsBase { record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription2")); waitUntilHandlersIdle(); - pendingEvents = getTaskPendingEvent(stack); + pendingEvents = getTaskPendingEvent(rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType); assertEquals("TestDescription2", @@ -1159,20 +1165,20 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testInfoChangDeferThenVanish() { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); - final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task); // Assume layout defer mWm.mWindowPlacerLocked.deferLayout(); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription")); - stack.removeImmediately(); + rootTask.removeImmediately(); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType); assertEquals("TestDescription", @@ -1182,18 +1188,18 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testVanishDeferThenInfoChange() { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); - final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task); // Assume layout defer mWm.mWindowPlacerLocked.deferLayout(); - stack.removeImmediately(); - stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + rootTask.removeImmediately(); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType); } @@ -1201,19 +1207,19 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testVanishDeferThenBackOnRoot() { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task stack = createStack(); - final Task task = createTask(stack); - final ActivityRecord record = createActivityRecord(stack.mDisplayContent, task); + final Task rootTask = createRootTask(); + final Task task = createTask(rootTask); + final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task); // Assume layout defer mWm.mWindowPlacerLocked.deferLayout(); - stack.removeImmediately(); + rootTask.removeImmediately(); mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token, new IRequestFinishCallback.Default()); waitUntilHandlersIdle(); - ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack); + ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask); assertEquals(1, pendingEvents.size()); assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType); } @@ -1264,7 +1270,7 @@ public class WindowOrganizerTests extends WindowTestsBase { @Test public void testSizeCompatModeChangedOnFirstOrganizedTask() throws RemoteException { final ITaskOrganizer organizer = registerMockOrganizer(); - final Task rootTask = createStack(); + final Task rootTask = createRootTask(); final Task task = createTask(rootTask); final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task); final ArgumentCaptor<RunningTaskInfo> infoCaptor = diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 5b5b1da327bd..bfbe203fb65e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -249,21 +249,22 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(appWindow.canBeImeTarget()); assertFalse(imeWindow.canBeImeTarget()); - // Simulate the window is in split screen primary stack and the current state is - // minimized and home stack is resizable, so that we should ignore input for the stack. + // Simulate the window is in split screen primary root task and the current state is + // minimized and home root task is resizable, so that we should ignore input for the + // root task. final DockedTaskDividerController controller = mDisplayContent.getDockedDividerController(); - final Task stack = createTaskStackOnDisplay(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, - ACTIVITY_TYPE_STANDARD, mDisplayContent); + final Task rootTask = createTask(mDisplayContent, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); spyOn(appWindow); spyOn(controller); - spyOn(stack); - stack.setFocusable(false); - doReturn(stack).when(appWindow).getRootTask(); + spyOn(rootTask); + rootTask.setFocusable(false); + doReturn(rootTask).when(appWindow).getRootTask(); // Make sure canBeImeTarget is false due to shouldIgnoreInput is true; assertFalse(appWindow.canBeImeTarget()); - assertTrue(stack.shouldIgnoreInput()); + assertTrue(rootTask.shouldIgnoreInput()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index b210dfb654ba..e9e0c99c3d93 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -429,35 +429,55 @@ class WindowTestsBase extends SystemServiceTestsBase { return newTaskDisplayArea; } - /** Creates a {@link Task} and adds it to the specified {@link DisplayContent}. */ - Task createTaskStackOnDisplay(DisplayContent dc) { - return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc); + /** + * Creates a {@link Task} with a simple {@link ActivityRecord} and adds to the given + * {@link TaskDisplayArea}. + */ + Task createTaskWithActivity(TaskDisplayArea taskDisplayArea, + int windowingMode, int activityType, boolean onTop, boolean twoLevelTask) { + return createTask(taskDisplayArea, windowingMode, activityType, + onTop, true /* createActivity */, twoLevelTask); } - Task createTaskStackOnDisplay(int windowingMode, int activityType, DisplayContent dc) { - return new TaskBuilder(dc.mAtmService.mTaskSupervisor) - .setDisplay(dc) - .setWindowingMode(windowingMode) - .setActivityType(activityType) - .setIntent(new Intent()) - .build(); + /** Creates a {@link Task} and adds to the given {@link DisplayContent}. */ + Task createTask(DisplayContent dc) { + return createTask(dc.getDefaultTaskDisplayArea(), + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + } + + Task createTask(DisplayContent dc, int windowingMode, int activityType) { + return createTask(dc.getDefaultTaskDisplayArea(), windowingMode, activityType); + } + + Task createTask(TaskDisplayArea taskDisplayArea, int windowingMode, int activityType) { + return createTask(taskDisplayArea, windowingMode, activityType, + true /* onTop */, false /* createActivity */, false /* twoLevelTask */); } - Task createTaskStackOnTaskDisplayArea(int windowingMode, int activityType, - TaskDisplayArea tda) { - return new TaskBuilder(tda.mDisplayContent.mAtmService.mTaskSupervisor) - .setTaskDisplayArea(tda) + /** Creates a {@link Task} and adds to the given {@link TaskDisplayArea}. */ + Task createTask(TaskDisplayArea taskDisplayArea, int windowingMode, int activityType, + boolean onTop, boolean createActivity, boolean twoLevelTask) { + final TaskBuilder builder = new TaskBuilder(mSupervisor) + .setTaskDisplayArea(taskDisplayArea) .setWindowingMode(windowingMode) .setActivityType(activityType) - .setIntent(new Intent()) - .build(); + .setOnTop(onTop) + .setCreateActivity(createActivity); + if (twoLevelTask) { + return builder + .setCreateParentTask(true) + .build() + .getRootTask(); + } else { + return builder.build(); + } } - /** Creates a {@link Task} and adds it to the specified {@link Task}. */ - Task createTaskInStack(Task stack, int userId) { - final Task task = new TaskBuilder(stack.mTaskSupervisor) + /** Creates a {@link Task} and adds to the given root {@link Task}. */ + Task createTaskInRootTask(Task rootTask, int userId) { + final Task task = new TaskBuilder(rootTask.mTaskSupervisor) .setUserId(userId) - .setParentTask(stack) + .setParentTask(rootTask) .build(); return task; } @@ -485,7 +505,7 @@ class WindowTestsBase extends SystemServiceTestsBase { */ ActivityRecord createActivityRecord(DisplayContent dc, int windowingMode, int activityType) { - final Task task = createTaskStackOnDisplay(windowingMode, activityType, dc); + final Task task = createTask(dc, windowingMode, activityType); return createActivityRecord(dc, task); } @@ -517,7 +537,7 @@ class WindowTestsBase extends SystemServiceTestsBase { */ ActivityRecord createActivityRecordWithParentTask(DisplayContent dc, int windowingMode, int activityType) { - final Task task = createTaskStackOnDisplay(windowingMode, activityType, dc); + final Task task = createTask(dc, windowingMode, activityType); return createActivityRecordWithParentTask(task); } @@ -871,7 +891,7 @@ class WindowTestsBase extends SystemServiceTestsBase { .setParentTask(mParentTask).build(); } else if (mTask == null && mParentTask != null && DisplayContent.alwaysCreateRootTask( mParentTask.getWindowingMode(), mParentTask.getActivityType())) { - // The stack can be the task root. + // The parent task can be the task root. mTask = mParentTask; } @@ -921,9 +941,9 @@ class WindowTestsBase extends SystemServiceTestsBase { doReturn(true).when(activity).fillsParent(); mTask.addChild(activity); if (mOnTop) { - // Move the task to front after activity added. - // Or {@link TaskDisplayArea#mPreferredTopFocusableStack} could be other stacks - // (e.g. home stack). + // Move the task to front after activity is added. + // Or {@link TaskDisplayArea#mPreferredTopFocusableRootTask} could be other + // root tasks (e.g. home root task). mTask.moveToFront("createActivity"); } // Make visible by default... @@ -1127,8 +1147,8 @@ class WindowTestsBase extends SystemServiceTestsBase { .build(); if (mOnTop) { // We move the task to front again in order to regain focus after activity - // added to the stack. Or {@link TaskDisplayArea#mPreferredTopFocusableStack} - // could be other stacks (e.g. home stack). + // is added. Or {@link TaskDisplayArea#mPreferredTopFocusableRootTask} could be + // other root tasks (e.g. home root task). task.moveToFront("createActivityTask"); } else { task.moveToBack("createActivityTask", null); 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/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java index 6203ae954065..f132b49e9dd8 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerService.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java @@ -17,13 +17,19 @@ package com.android.server.translation; import static android.Manifest.permission.MANAGE_UI_TRANSLATION; +import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.content.Context.TRANSLATION_MANAGER_SERVICE; import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL; +import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS; + +import static com.android.internal.util.SyncResultReceiver.bundleFor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.os.Binder; import android.os.IBinder; @@ -31,6 +37,7 @@ import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.UserHandle; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.translation.ITranslationManager; @@ -231,6 +238,46 @@ public final class TranslationManagerService } } + @Override + public void getServiceSettingsActivity(IResultReceiver result, int userId) { + final TranslationManagerServiceImpl service; + synchronized (mLock) { + service = getServiceForUserLocked(userId); + } + if (service != null) { + final ComponentName componentName = service.getServiceSettingsActivityLocked(); + if (componentName == null) { + try { + result.send(STATUS_SYNC_CALL_SUCCESS, null); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e); + } + } + final Intent intent = new Intent(); + intent.setComponent(componentName); + final long identity = Binder.clearCallingIdentity(); + try { + final PendingIntent pendingIntent = + PendingIntent.getActivityAsUser(getContext(), 0, intent, FLAG_IMMUTABLE, + null, new UserHandle(userId)); + try { + + result.send(STATUS_SYNC_CALL_SUCCESS, bundleFor(pendingIntent)); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } else { + try { + result.send(STATUS_SYNC_CALL_FAIL, null); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to send getServiceSettingsActivity(): " + e); + } + } + } + /** * Dump the service state into the given stream. You run "adb shell dumpsys translation". */ diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index ee5ec47ec71a..2cd41ba5377c 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -62,6 +62,9 @@ final class TranslationManagerServiceImpl extends @Nullable private ServiceInfo mRemoteTranslationServiceInfo; + @GuardedBy("mLock") + private TranslationServiceInfo mTranslationServiceInfo; + private ActivityTaskManagerInternal mActivityTaskManagerInternal; protected TranslationManagerServiceImpl( @@ -76,10 +79,10 @@ final class TranslationManagerServiceImpl extends @Override // from PerUserSystemService protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) throws PackageManager.NameNotFoundException { - final TranslationServiceInfo info = new TranslationServiceInfo(getContext(), + mTranslationServiceInfo = new TranslationServiceInfo(getContext(), serviceComponent, isTemporaryServiceSetLocked(), mUserId); - mRemoteTranslationServiceInfo = info.getServiceInfo(); - return info.getServiceInfo(); + mRemoteTranslationServiceInfo = mTranslationServiceInfo.getServiceInfo(); + return mTranslationServiceInfo.getServiceInfo(); } @GuardedBy("mLock") @@ -227,4 +230,16 @@ final class TranslationManagerServiceImpl extends } private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); + + public ComponentName getServiceSettingsActivityLocked() { + if (mTranslationServiceInfo == null) { + return null; + } + final String activityName = mTranslationServiceInfo.getSettingsActivity(); + if (activityName == null) { + return null; + } + final String packageName = mTranslationServiceInfo.getServiceInfo().packageName; + return new ComponentName(packageName, activityName); + } } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index afa35fe8deba..2e692e6e68f7 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -917,20 +917,31 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser boolean prevHostConnected = mHostConnected; UsbPort port = (UsbPort) args.arg1; UsbPortStatus status = (UsbPortStatus) args.arg2; - mHostConnected = status.getCurrentDataRole() == DATA_ROLE_HOST; - mSourcePower = status.getCurrentPowerRole() == POWER_ROLE_SOURCE; - mSinkPower = status.getCurrentPowerRole() == POWER_ROLE_SINK; - mAudioAccessoryConnected = (status.getCurrentMode() == MODE_AUDIO_ACCESSORY); + + if (status != null) { + mHostConnected = status.getCurrentDataRole() == DATA_ROLE_HOST; + mSourcePower = status.getCurrentPowerRole() == POWER_ROLE_SOURCE; + mSinkPower = status.getCurrentPowerRole() == POWER_ROLE_SINK; + mAudioAccessoryConnected = (status.getCurrentMode() == MODE_AUDIO_ACCESSORY); + + // Ideally we want to see if PR_SWAP and DR_SWAP is supported. + // But, this should be suffice, since, all four combinations are only supported + // when PR_SWAP and DR_SWAP are supported. + mSupportsAllCombinations = status.isRoleCombinationSupported( + POWER_ROLE_SOURCE, DATA_ROLE_HOST) + && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST) + && status.isRoleCombinationSupported(POWER_ROLE_SOURCE, + DATA_ROLE_DEVICE) + && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE); + } else { + mHostConnected = false; + mSourcePower = false; + mSinkPower = false; + mAudioAccessoryConnected = false; + mSupportsAllCombinations = false; + } + mAudioAccessorySupported = port.isModeSupported(MODE_AUDIO_ACCESSORY); - // Ideally we want to see if PR_SWAP and DR_SWAP is supported. - // But, this should be suffice, since, all four combinations are only supported - // when PR_SWAP and DR_SWAP are supported. - mSupportsAllCombinations = status.isRoleCombinationSupported( - POWER_ROLE_SOURCE, DATA_ROLE_HOST) - && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST) - && status.isRoleCombinationSupported(POWER_ROLE_SOURCE, - DATA_ROLE_DEVICE) - && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE); args.recycle(); updateUsbNotification(false); 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/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index e089995a7b1c..11ccfd88c100 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -26,12 +26,12 @@ import android.hardware.soundtrigger.SoundTrigger; import android.media.AudioAttributes; import android.media.AudioRecord; import android.media.MediaRecorder; -import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; -import android.service.voice.AlwaysOnHotwordDetector; import android.service.voice.HotwordDetectionService; +import android.service.voice.HotwordRejectedResult; import android.service.voice.IDspHotwordDetectionCallback; import android.service.voice.IHotwordDetectionService; import android.util.Pair; @@ -77,7 +77,7 @@ final class HotwordDetectionConnection { boolean mBound; HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName, - int userId, boolean bindInstantServiceAllowed, @Nullable Bundle options, + int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { mLock = lock; mContext = context; @@ -96,10 +96,10 @@ final class HotwordDetectionConnection { mBound = connected; if (connected) { try { - service.setConfig(options, sharedMemory); + service.updateState(options, sharedMemory); } catch (RemoteException e) { // TODO: (b/181842909) Report an error to voice interactor - Slog.w(TAG, "Failed to setConfig for HotwordDetectionService", e); + Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e); } } } @@ -129,9 +129,9 @@ final class HotwordDetectionConnection { } } - void setConfigLocked(Bundle options, SharedMemory sharedMemory) { + void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) { mRemoteHotwordDetectionService.run( - service -> service.setConfig(options, sharedMemory)); + service -> service.updateState(options, sharedMemory)); } private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent, @@ -206,13 +206,12 @@ final class HotwordDetectionConnection { } @Override - public void onRejected() throws RemoteException { + public void onRejected(HotwordRejectedResult result) throws RemoteException { if (DEBUG) { Slog.d(TAG, "onRejected"); } cancelingFuture.cancel(true); - externalCallback.onRejected( - AlwaysOnHotwordDetector.HOTWORD_DETECTION_FALSE_ALERT); + externalCallback.onRejected(result); } }; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 80d4f8fd0c2f..c110b231bd0e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -56,6 +56,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; +import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -983,21 +984,19 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public void setHotwordDetectionServiceConfig(@Nullable Bundle options, + public void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION); synchronized (this) { enforceIsCurrentVoiceInteractionService(); if (mImpl == null) { - Slog.w(TAG, - "setHotwordDetectionServiceConfig without running voice" - + " interaction service"); + Slog.w(TAG, "updateState without running voice interaction service"); return; } final long caller = Binder.clearCallingIdentity(); try { - mImpl.setHotwordDetectionServiceConfigLocked(options, sharedMemory); + mImpl.updateStateLocked(options, sharedMemory); } finally { Binder.restoreCallingIdentity(caller); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index df3ca992ad75..0742cb4c0c84 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -43,6 +43,7 @@ import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; @@ -53,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; @@ -63,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; @@ -186,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); @@ -401,10 +401,10 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mInfo.getSupportsLocalInteraction(); } - public void setHotwordDetectionServiceConfigLocked(@Nullable Bundle options, + public void updateStateLocked(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { if (DEBUG) { - Slog.d(TAG, "setHotwordDetectionServiceConfigLocked"); + Slog.d(TAG, "updateStateLocked"); } if (mHotwordDetectionComponentName == null) { Slog.w(TAG, "Hotword detection service name not found"); @@ -415,7 +415,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne throw new IllegalStateException("Hotword detection service not in isolated process"); } // TODO : Need to check related permissions for hotword detection service - // TODO : Sanitize for bundle if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) { Slog.w(TAG, "Can't set sharedMemory to be read-only"); @@ -427,7 +426,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false, options, sharedMemory); } else { - mHotwordDetectionConnection.setConfigLocked(options, sharedMemory); + mHotwordDetectionConnection.updateStateLocked(options, sharedMemory); } } 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/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 4e64838d99ce..d77ab2b24b31 100755 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -687,7 +687,11 @@ public final class Call { public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000; /** - * Connection is using Cross SIM Calling. + * Connection is using cross sim technology. + * <p> + * Indicates that the {@link Connection} is using a cross sim technology which would + * register IMS over internet APN of default data subscription. + * <p> */ public static final int PROPERTY_CROSS_SIM = 0x00004000; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 04a0abac9b57..3db31ecb99cf 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4254,6 +4254,14 @@ public class CarrierConfigManager { public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = KEY_PREFIX + "non_rcs_capabilities_cache_expiration_sec_int"; + /** + * Specifies the RCS feature tag allowed for the carrier. + * + * <p>The values refer to RCC.07 2.4.4. + */ + public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = + KEY_PREFIX + "rcs_feature_tag_allowed_string_array"; + private Ims() {} private static PersistableBundle getDefaults() { @@ -4267,6 +4275,27 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true); defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60); + defaults.putStringArray(KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY, new String[]{ + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg\"", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.largemsg\"", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.deferred\"", + "+g.gsma.rcs.cpm.pager-large", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"", + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"", + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"", + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callcomposer\"", + "+g.gsma.callcomposer", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callunanswered\"", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedmap\"", + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedsketch\"", + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geopush\"", + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geosms\"", + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot\"", + "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot.sa\"", + "+g.gsma.rcs.botversion=\"#=1,#=2\"", + "+g.gsma.rcs.cpimext"}); + return defaults; } } @@ -4849,6 +4878,30 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool"; + /** + * Determines whether or not to use (IP) data connectivity as a supplemental condition to + * control the visibility of the no-calling indicator for this carrier in the System UI. Setting + * the configuration to true may make sense to a carrier which provides OTT calling. + * + * Config = true: do not show no-calling indication if (IP) data connectivity is available + * or telephony has voice registration. + * Config = false: do not show no-calling indication if telephony has voice registration. + */ + public static final String KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL = + "hide_no_calling_indicator_on_data_network_bool"; + + /** + * Determine whether or not to display a call strength indicator for this carrier in the System + * UI. Disabling the indication may be reasonable if the carrier's calling is not integrated + * into the Android telephony stack (e.g. it is OTT). + * + * true: Use telephony APIs to detect the current networking medium of calling and display a + * UI indication based on the current strength (e.g. signal level) of that medium. + * false: Do not display the call strength indicator. + */ + public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL = + "display_call_strength_indicator_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -5422,6 +5475,8 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY, new String[]{"ia", "default", "ims", "mms", "dun", "emergency"}); sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false); + sDefaults.putBoolean(KEY_HIDE_NO_CALLING_INDICATOR_ON_DATA_NETWORK_BOOL, false); + sDefaults.putBoolean(KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL, true); sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, ""); } diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java index 30480d14e453..a3aaf61a6fec 100644 --- a/telephony/java/android/telephony/PhoneCapability.java +++ b/telephony/java/android/telephony/PhoneCapability.java @@ -26,6 +26,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -47,26 +48,18 @@ public final class PhoneCapability implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "DEVICE_NR_CAPABILITY_" }, value = { - DEVICE_NR_CAPABILITY_NONE, DEVICE_NR_CAPABILITY_NSA, DEVICE_NR_CAPABILITY_SA, }) public @interface DeviceNrCapability {} /** - * Indicates DEVICE_NR_CAPABILITY_NONE determine that the device does not enable 5G NR. - * @hide - */ - @SystemApi - public static final int DEVICE_NR_CAPABILITY_NONE = 0; - - /** * Indicates DEVICE_NR_CAPABILITY_NSA determine that the device enable the non-standalone * (NSA) mode of 5G NR. * @hide */ @SystemApi - public static final int DEVICE_NR_CAPABILITY_NSA = 1 << 0; + public static final int DEVICE_NR_CAPABILITY_NSA = 1; /** * Indicates DEVICE_NR_CAPABILITY_SA determine that the device enable the standalone (SA) @@ -74,7 +67,7 @@ public final class PhoneCapability implements Parcelable { * @hide */ @SystemApi - public static final int DEVICE_NR_CAPABILITY_SA = 1 << 1; + public static final int DEVICE_NR_CAPABILITY_SA = 2; static { ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true); @@ -83,31 +76,34 @@ public final class PhoneCapability implements Parcelable { List<ModemInfo> logicalModemList = new ArrayList<>(); logicalModemList.add(modemInfo1); logicalModemList.add(modemInfo2); + int[] deviceNrCapabilities = new int[0]; + DEFAULT_DSDS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false, - DEVICE_NR_CAPABILITY_NONE); + deviceNrCapabilities); logicalModemList = new ArrayList<>(); logicalModemList.add(modemInfo1); DEFAULT_SSSS_CAPABILITY = new PhoneCapability(1, 1, logicalModemList, false, - DEVICE_NR_CAPABILITY_NONE); + deviceNrCapabilities); } /** - * MaxActivePsVoice defines the maximum number of active voice calls. For a dual sim dual - * standby (DSDS) modem it would be one, but for a dual sim dual active modem it would be 2. + * mMaxActiveVoiceSubscriptions defines the maximum subscriptions that can support + * simultaneous voice calls. For a dual sim dual standby (DSDS) device it would be one, but + * for a dual sim dual active device it would be 2. * * @hide */ - private final int mMaxActivePsVoice; + private final int mMaxActiveVoiceSubscriptions; /** - * MaxActiveInternetData defines how many logical modems can have - * PS attached simultaneously. For example, for L+L modem it - * should be 2. + * mMaxActiveDataSubscriptions defines the maximum subscriptions that can support + * simultaneous data connections. + * For example, for L+L device it should be 2. * * @hide */ - private final int mMaxActiveInternetData; + private final int mMaxActiveDataSubscriptions; /** * Whether modem supports both internet PDN up so @@ -126,42 +122,45 @@ public final class PhoneCapability implements Parcelable { * * @hide */ - private final int mDeviceNrCapability; + private final int[] mDeviceNrCapabilities; /** @hide */ - public PhoneCapability(int maxActivePsVoice, int maxActiveInternetData, + public PhoneCapability(int maxActiveVoiceSubscriptions, int maxActiveDataSubscriptions, List<ModemInfo> logicalModemList, boolean networkValidationBeforeSwitchSupported, - int deviceNrCapability) { - this.mMaxActivePsVoice = maxActivePsVoice; - this.mMaxActiveInternetData = maxActiveInternetData; + int[] deviceNrCapabilities) { + this.mMaxActiveVoiceSubscriptions = maxActiveVoiceSubscriptions; + this.mMaxActiveDataSubscriptions = maxActiveDataSubscriptions; // Make sure it's not null. this.mLogicalModemList = logicalModemList == null ? new ArrayList<>() : logicalModemList; this.mNetworkValidationBeforeSwitchSupported = networkValidationBeforeSwitchSupported; - this.mDeviceNrCapability = deviceNrCapability; + this.mDeviceNrCapabilities = deviceNrCapabilities; } @Override public String toString() { - return "mMaxActivePsVoice=" + mMaxActivePsVoice - + " mMaxActiveInternetData=" + mMaxActiveInternetData + return "mMaxActiveVoiceSubscriptions=" + mMaxActiveVoiceSubscriptions + + " mMaxActiveDataSubscriptions=" + mMaxActiveDataSubscriptions + " mNetworkValidationBeforeSwitchSupported=" + mNetworkValidationBeforeSwitchSupported - + " mDeviceNrCapability " + mDeviceNrCapability; + + " mDeviceNrCapability " + Arrays.toString(mDeviceNrCapabilities); } private PhoneCapability(Parcel in) { - mMaxActivePsVoice = in.readInt(); - mMaxActiveInternetData = in.readInt(); + mMaxActiveVoiceSubscriptions = in.readInt(); + mMaxActiveDataSubscriptions = in.readInt(); mNetworkValidationBeforeSwitchSupported = in.readBoolean(); mLogicalModemList = new ArrayList<>(); in.readList(mLogicalModemList, ModemInfo.class.getClassLoader()); - mDeviceNrCapability = in.readInt(); + mDeviceNrCapabilities = in.createIntArray(); } @Override public int hashCode() { - return Objects.hash(mMaxActivePsVoice, mMaxActiveInternetData, mLogicalModemList, - mNetworkValidationBeforeSwitchSupported, mDeviceNrCapability); + return Objects.hash(mMaxActiveVoiceSubscriptions, + mMaxActiveDataSubscriptions, + mLogicalModemList, + mNetworkValidationBeforeSwitchSupported, + Arrays.hashCode(mDeviceNrCapabilities)); } @Override @@ -176,12 +175,12 @@ public final class PhoneCapability implements Parcelable { PhoneCapability s = (PhoneCapability) o; - return (mMaxActivePsVoice == s.mMaxActivePsVoice - && mMaxActiveInternetData == s.mMaxActiveInternetData + return (mMaxActiveVoiceSubscriptions == s.mMaxActiveVoiceSubscriptions + && mMaxActiveDataSubscriptions == s.mMaxActiveDataSubscriptions && mNetworkValidationBeforeSwitchSupported == s.mNetworkValidationBeforeSwitchSupported && mLogicalModemList.equals(s.mLogicalModemList) - && mDeviceNrCapability == s.mDeviceNrCapability); + && Arrays.equals(mDeviceNrCapabilities, s.mDeviceNrCapabilities)); } /** @@ -195,11 +194,11 @@ public final class PhoneCapability implements Parcelable { * {@link Parcelable#writeToParcel} */ public void writeToParcel(@NonNull Parcel dest, @Parcelable.WriteFlags int flags) { - dest.writeInt(mMaxActivePsVoice); - dest.writeInt(mMaxActiveInternetData); + dest.writeInt(mMaxActiveVoiceSubscriptions); + dest.writeInt(mMaxActiveDataSubscriptions); dest.writeBoolean(mNetworkValidationBeforeSwitchSupported); dest.writeList(mLogicalModemList); - dest.writeInt(mDeviceNrCapability); + dest.writeIntArray(mDeviceNrCapabilities); } public static final @android.annotation.NonNull Parcelable.Creator<PhoneCapability> CREATOR = @@ -214,25 +213,24 @@ public final class PhoneCapability implements Parcelable { }; /** - * @return the maximum number of active packet-switched calls. For a dual - * sim dual standby (DSDS) modem it would be one, but for a dual sim dual active modem it + * @return the maximum subscriptions that can support simultaneous voice calls. For a dual + * sim dual standby (DSDS) device it would be one, but for a dual sim dual active device it * would be 2. * @hide */ @SystemApi - public @IntRange(from = 1) int getMaxActivePacketSwitchedVoiceCalls() { - return mMaxActivePsVoice; + public @IntRange(from = 1) int getMaxActiveVoiceSubscriptions() { + return mMaxActiveVoiceSubscriptions; } /** - * @return MaxActiveInternetData defines how many logical modems can have PS attached - * simultaneously. - * For example, for L+L modem it should be 2. + * @return the maximum subscriptions that can support simultaneous data connections. + * For example, for L+L device it should be 2. * @hide */ @SystemApi - public @IntRange(from = 1) int getMaxActiveInternetData() { - return mMaxActiveInternetData; + public @IntRange(from = 1) int getMaxActiveDataSubscriptions() { + return mMaxActiveDataSubscriptions; } /** @@ -254,13 +252,16 @@ public final class PhoneCapability implements Parcelable { } /** - * Return the device's NR capability. + * Return List of the device's NR capability. If the device doesn't support NR capability, + * then this api return empty array. + * @see DEVICE_NR_CAPABILITY_NSA + * @see DEVICE_NR_CAPABILITY_SA * - * @return {@link DeviceNrCapability} the device's NR capability. + * @return List of the device's NR capability. * @hide */ @SystemApi - public @DeviceNrCapability int getDeviceNrCapabilityBitmask() { - return mDeviceNrCapability; + public @NonNull @DeviceNrCapability int[] getDeviceNrCapabilities() { + return mDeviceNrCapabilities == null ? (new int[0]) : mDeviceNrCapabilities; } } diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java index a012498e157d..3b2861600b56 100644 --- a/telephony/java/android/telephony/PhysicalChannelConfig.java +++ b/telephony/java/android/telephony/PhysicalChannelConfig.java @@ -293,6 +293,14 @@ public final class PhysicalChannelConfig implements Parcelable { } /** + * Return a copy of this PhysicalChannelConfig object but redact all the location info. + * @hide + */ + public PhysicalChannelConfig createLocationInfoSanitizedCopy() { + return new Builder(this).setPhysicalCellId(PHYSICAL_CELL_ID_UNKNOWN).build(); + } + + /** * @return String representation of the connection status * @hide */ @@ -541,6 +549,23 @@ public final class PhysicalChannelConfig implements Parcelable { mBand = BAND_UNKNOWN; } + /** + * Builder object constructed from existing PhysicalChannelConfig object. + * @hide + */ + public Builder(PhysicalChannelConfig config) { + mNetworkType = config.getNetworkType(); + mFrequencyRange = config.getFrequencyRange(); + mDownlinkChannelNumber = config.getDownlinkChannelNumber(); + mUplinkChannelNumber = config.getUplinkChannelNumber(); + mCellBandwidthDownlinkKhz = config.getCellBandwidthDownlinkKhz(); + mCellBandwidthUplinkKhz = config.getCellBandwidthUplinkKhz(); + mCellConnectionStatus = config.getConnectionStatus(); + mContextIds = Arrays.copyOf(config.getContextIds(), config.getContextIds().length); + mPhysicalCellId = config.getPhysicalCellId(); + mBand = config.getBand(); + } + public PhysicalChannelConfig build() { return new PhysicalChannelConfig(this); } 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/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index cfb29f124b43..5a12865fb2a0 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -300,9 +300,12 @@ public class SmsMessage { * @param data Message data. * @param isCdma Indicates weather the type of the SMS is CDMA. * @return An SmsMessage representing the message. + * + * @hide */ + @SystemApi @Nullable - public static SmsMessage createSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) { + public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) { SmsMessageBase wrappedMessage; if (isCdma) { @@ -318,23 +321,6 @@ public class SmsMessage { } /** - * Create an SmsMessage from a native SMS-Submit PDU, specified by Bluetooth Message Access - * Profile Specification v1.4.2 5.8. - * This is used by Bluetooth MAP profile to decode message when sending non UTF-8 SMS messages. - * - * @param data Message data. - * @param isCdma Indicates weather the type of the SMS is CDMA. - * @return An SmsMessage representing the message. - * - * @hide - */ - @SystemApi - @Nullable - public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) { - return null; - } - - /** * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the * length in bytes (not hex chars) less the SMSC header * diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index a46621a83c1e..67fe783ee7a4 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -56,6 +56,7 @@ import android.os.RemoteException; import android.provider.Telephony.SimInfo; import android.telephony.euicc.EuiccManager; import android.telephony.ims.ImsMmTelManager; +import android.util.Base64; import android.util.Log; import android.util.Pair; @@ -67,6 +68,11 @@ import com.android.internal.util.FunctionalUtils; import com.android.internal.util.Preconditions; import com.android.telephony.Rlog; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -612,9 +618,9 @@ public class SubscriptionManager { public static final int D2D_SHARING_ALL_CONTACTS = 1; /** - * Device status is shared with all starred contacts. + * Device status is shared with all selected contacts. */ - public static final int D2D_SHARING_STARRED_CONTACTS = 2; + public static final int D2D_SHARING_SELECTED_CONTACTS = 2; /** * Device status is shared whenever possible. @@ -627,7 +633,7 @@ public class SubscriptionManager { value = { D2D_SHARING_DISABLED, D2D_SHARING_ALL_CONTACTS, - D2D_SHARING_STARRED_CONTACTS, + D2D_SHARING_SELECTED_CONTACTS, D2D_SHARING_ALL }) public @interface DeviceToDeviceStatusSharingPreference {} @@ -639,6 +645,13 @@ public class SubscriptionManager { public static final String D2D_STATUS_SHARING = SimInfo.COLUMN_D2D_STATUS_SHARING; /** + * TelephonyProvider column name for contacts information that allow device to device sharing. + * <P>Type: TEXT (String)</P> + */ + public static final String D2D_STATUS_SHARING_SELECTED_CONTACTS = + SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS; + + /** * TelephonyProvider column name for the color of a SIM. * <P>Type: INTEGER (int)</P> */ @@ -2439,6 +2452,57 @@ public class SubscriptionManager { } /** + * Serialize list of contacts uri to string + * @hide + */ + public static String serializeUriLists(List<Uri> uris) { + List<String> contacts = new ArrayList<>(); + for (Uri uri : uris) { + contacts.add(uri.toString()); + } + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(contacts); + oos.flush(); + return Base64.encodeToString(bos.toByteArray(), Base64.DEFAULT); + } catch (IOException e) { + logd("serializeUriLists IO exception"); + } + return ""; + } + + /** + * Return list of contacts uri corresponding to query result. + * @param subId Subscription Id of Subscription + * @param propKey Column name in SubscriptionInfo database + * @return list of contacts uri to be returned + * @hide + */ + private static List<Uri> getContactsFromSubscriptionProperty(int subId, String propKey, + Context context) { + String result = getSubscriptionProperty(subId, propKey, context); + if (result != null) { + try { + byte[] b = Base64.decode(result, Base64.DEFAULT); + ByteArrayInputStream bis = new ByteArrayInputStream(b); + ObjectInputStream ois = new ObjectInputStream(bis); + List<String> contacts = ArrayList.class.cast(ois.readObject()); + List<Uri> uris = new ArrayList<>(); + for (String contact : contacts) { + uris.add(Uri.parse(contact)); + } + return uris; + } catch (IOException e) { + logd("getContactsFromSubscriptionProperty IO exception"); + } catch (ClassNotFoundException e) { + logd("getContactsFromSubscriptionProperty ClassNotFound exception"); + } + } + return new ArrayList<>(); + } + + /** * Store properties associated with SubscriptionInfo in database * @param subId Subscription Id of Subscription * @param propKey Column name in SubscriptionInfo database @@ -3443,6 +3507,40 @@ public class SubscriptionManager { } /** + * Set the list of contacts that allow device to device status sharing for a subscription ID. + * The setting app uses this method to indicate with whom they wish to share device to device + * status information. + * @param contacts The list of contacts that allow device to device status sharing + * @param subscriptionId The unique Subscription ID in database + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setDeviceToDeviceStatusSharingContacts(@NonNull List<Uri> contacts, + int subscriptionId) { + String contactString = serializeUriLists(contacts); + if (VDBG) { + logd("[setDeviceToDeviceStatusSharingContacts] + contacts: " + contactString + + " subId: " + subscriptionId); + } + setSubscriptionPropertyHelper(subscriptionId, "setDeviceToDeviceSharingStatus", + (iSub)->iSub.setDeviceToDeviceStatusSharingContacts(serializeUriLists(contacts), + subscriptionId)); + } + + /** + * Returns the list of contacts that allow device to device status sharing. + * @param subscriptionId Subscription id of subscription + * @return The list of contacts that allow device to device status sharing + */ + public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts( + int subscriptionId) { + if (VDBG) { + logd("[getDeviceToDeviceStatusSharingContacts] + subId: " + subscriptionId); + } + return getContactsFromSubscriptionProperty(subscriptionId, + D2D_STATUS_SHARING_SELECTED_CONTACTS, mContext); + } + + /** * DO NOT USE. * This API is designed for features that are not finished at this point. Do not call this API. * @hide 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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d3246ca8ba6c..d2da51a1faca 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -14951,6 +14951,13 @@ public class TelephonyManager { public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING = "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING"; + /** + * Indicates whether {@link #getNetworkSlicingConfiguration} is supported. See comments on + * respective methods for more information. + */ + public static final String CAPABILITY_SLICING_CONFIG_SUPPORTED = + "CAPABILITY_SLICING_CONFIG_SUPPORTED"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @StringDef(prefix = "CAPABILITY_", value = { @@ -14958,6 +14965,7 @@ public class TelephonyManager { CAPABILITY_ALLOWED_NETWORK_TYPES_USED, CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE, CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING, + CAPABILITY_SLICING_CONFIG_SUPPORTED, }) public @interface RadioInterfaceCapability {} @@ -15077,7 +15085,12 @@ public class TelephonyManager { * DataThrottlingRequest#DATA_THROTTLING_ACTION_NO_DATA_THROTTLING} can still be requested in * order to undo the mitigations above it (i.e {@link * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_VOICE_ONLY} and/or {@link - * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}). + * ThermalMitigationRequest#THERMAL_MITIGATION_ACTION_RADIO_OFF}). </p> + * + * <p> In addition to the {@link Manifest.permission#MODIFY_PHONE_STATE} permission, callers of + * this API must also be listed in the device configuration as an authorized app in + * {@code packages/services/Telephony/res/values/config.xml} under the + * {@code thermal_mitigation_allowlisted_packages} key. </p> * * @param thermalMitigationRequest Thermal mitigation request. See {@link * ThermalMitigationRequest} for details. @@ -15096,7 +15109,8 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest); + return telephony.sendThermalMitigationRequest(getSubId(), thermalMitigationRequest, + getOpPackageName()); } throw new IllegalStateException("telephony service is null."); } catch (RemoteException ex) { @@ -15603,9 +15617,15 @@ public class TelephonyManager { * <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * </ul> * + * This will be invalid if the device does not support + * android.telephony.TelephonyManager#CAPABILITY_SLICING_CONFIG_SUPPORTED. + * * @param executor the executor on which callback will be invoked. * @param callback a callback to receive the current slicing configuration. */ + @RequiresFeature( + enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported", + value = TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED) @SuppressAutoDoc // No support for carrier privileges (b/72967236). @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getNetworkSlicingConfiguration( diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java index 2011eed26977..fe124ac15393 100644 --- a/telephony/java/android/telephony/data/NrQos.java +++ b/telephony/java/android/telephony/data/NrQos.java @@ -50,6 +50,18 @@ public final class NrQos extends Qos implements Parcelable { return new NrQos(in); } + public int get5Qi() { + return fiveQi; + } + + public int getQfi() { + return qosFlowId; + } + + public int getAveragingWindow() { + return averagingWindowMs; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(Qos.QOS_TYPE_NR, dest, flags); diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl new file mode 100644 index 000000000000..fd3bbb0865cb --- /dev/null +++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package android.telephony.data; + + parcelable NrQosSessionAttributes; diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.java b/telephony/java/android/telephony/data/NrQosSessionAttributes.java new file mode 100644 index 000000000000..857ccb960d52 --- /dev/null +++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.data; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.QosSessionAttributes; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Provides Qos attributes of an NR bearer. + * + * {@hide} + */ +@SystemApi +public final class NrQosSessionAttributes implements Parcelable, QosSessionAttributes { + private static final String TAG = NrQosSessionAttributes.class.getSimpleName(); + private final int m5Qi; + private final int mQfi; + private final long mMaxUplinkBitRate; + private final long mMaxDownlinkBitRate; + private final long mGuaranteedUplinkBitRate; + private final long mGuaranteedDownlinkBitRate; + private final long mAveragingWindow; + @NonNull private final List<InetSocketAddress> mRemoteAddresses; + + /** + * 5G QOS Identifier (5QI), see 3GPP TS 24.501 and 23.501. + * The allowed values are standard values(1-9, 65-68, 69-70, 75, 79-80, 82-85) + * defined in the spec and operator specific values in the range 128-254. + * + * @return the 5QI of the QOS flow + */ + public int get5Qi() { + return m5Qi; + } + + /** + * QOS flow identifier of the QOS flow description in the + * range of 1 to 63. see 3GPP TS 24.501 and 23.501. + * + * @return the QOS flow identifier of the session + */ + public int getQfi() { + return mQfi; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the uplink. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedUplinkBitRate() { + return mGuaranteedUplinkBitRate; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the downlink. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedDownlinkBitRate() { + return mGuaranteedDownlinkBitRate; + } + + /** + * The maximum uplink kbps that the network will accept. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max uplink bit rate in kbps + */ + public long getMaxUplinkBitRate() { + return mMaxUplinkBitRate; + } + + /** + * The maximum downlink kbps that the network can provide. + * + * see 3GPP TS 24.501 section 6.2.5 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max downlink bit rate in kbps + */ + public long getMaxDownlinkBitRate() { + return mMaxDownlinkBitRate; + } + + /** + * The duration in milliseconds over which the maximum bit rates and guaranteed bit rates + * are calculated + * + * see 3GPP TS 24.501 section 6.2.5 + * + * @return the averaging window duration in milliseconds + */ + public long getAveragingWindow() { + return mAveragingWindow; + } + + /** + * List of remote addresses associated with the Qos Session. The given uplink bit rates apply + * to this given list of remote addresses. + * + * Note: In the event that the list is empty, it is assumed that the uplink bit rates apply to + * all remote addresses that are not contained in a different set of attributes. + * + * @return list of remote socket addresses that the attributes apply to + */ + @NonNull + public List<InetSocketAddress> getRemoteAddresses() { + return mRemoteAddresses; + } + + /** + * ..ctor for attributes + * + * @param fiveQi 5G quality class indicator + * @param qfi QOS flow identifier + * @param maxDownlinkBitRate the max downlink bit rate in kbps + * @param maxUplinkBitRate the max uplink bit rate in kbps + * @param guaranteedDownlinkBitRate the guaranteed downlink bit rate in kbps + * @param guaranteedUplinkBitRate the guaranteed uplink bit rate in kbps + * @param averagingWindow the averaging window duration in milliseconds + * @param remoteAddresses the remote addresses that the uplink bit rates apply to + * + * @hide + */ + public NrQosSessionAttributes(final int fiveQi, final int qfi, + final long maxDownlinkBitRate, final long maxUplinkBitRate, + final long guaranteedDownlinkBitRate, final long guaranteedUplinkBitRate, + final long averagingWindow, @NonNull final List<InetSocketAddress> remoteAddresses) { + Objects.requireNonNull(remoteAddresses, "remoteAddress must be non-null"); + m5Qi = fiveQi; + mQfi = qfi; + mMaxDownlinkBitRate = maxDownlinkBitRate; + mMaxUplinkBitRate = maxUplinkBitRate; + mGuaranteedDownlinkBitRate = guaranteedDownlinkBitRate; + mGuaranteedUplinkBitRate = guaranteedUplinkBitRate; + mAveragingWindow = averagingWindow; + + final List<InetSocketAddress> remoteAddressesTemp = copySocketAddresses(remoteAddresses); + mRemoteAddresses = Collections.unmodifiableList(remoteAddressesTemp); + } + + private static List<InetSocketAddress> copySocketAddresses( + @NonNull final List<InetSocketAddress> remoteAddresses) { + final List<InetSocketAddress> remoteAddressesTemp = new ArrayList<>(); + for (final InetSocketAddress socketAddress : remoteAddresses) { + if (socketAddress != null && socketAddress.getAddress() != null) { + remoteAddressesTemp.add(socketAddress); + } + } + return remoteAddressesTemp; + } + + private NrQosSessionAttributes(@NonNull final Parcel in) { + m5Qi = in.readInt(); + mQfi = in.readInt(); + mMaxDownlinkBitRate = in.readLong(); + mMaxUplinkBitRate = in.readLong(); + mGuaranteedDownlinkBitRate = in.readLong(); + mGuaranteedUplinkBitRate = in.readLong(); + mAveragingWindow = in.readLong(); + + final int size = in.readInt(); + final List<InetSocketAddress> remoteAddresses = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + final byte[] addressBytes = in.createByteArray(); + final int port = in.readInt(); + try { + remoteAddresses.add( + new InetSocketAddress(InetAddress.getByAddress(addressBytes), port)); + } catch (final UnknownHostException e) { + // Impossible case since its filtered out the null values in the ..ctor + Log.e(TAG, "unable to unparcel remote address at index: " + i, e); + } + } + mRemoteAddresses = Collections.unmodifiableList(remoteAddresses); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(m5Qi); + dest.writeInt(mQfi); + dest.writeLong(mMaxDownlinkBitRate); + dest.writeLong(mMaxUplinkBitRate); + dest.writeLong(mGuaranteedDownlinkBitRate); + dest.writeLong(mGuaranteedUplinkBitRate); + dest.writeLong(mAveragingWindow); + + final int size = mRemoteAddresses.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + final InetSocketAddress address = mRemoteAddresses.get(i); + dest.writeByteArray(address.getAddress().getAddress()); + dest.writeInt(address.getPort()); + } + } + + @NonNull + public static final Creator<NrQosSessionAttributes> CREATOR = + new Creator<NrQosSessionAttributes>() { + @NonNull + @Override + public NrQosSessionAttributes createFromParcel(@NonNull final Parcel in) { + return new NrQosSessionAttributes(in); + } + + @NonNull + @Override + public NrQosSessionAttributes[] newArray(final int size) { + return new NrQosSessionAttributes[size]; + } + }; +} diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 9493c76d9a57..6493772039e6 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -302,4 +302,6 @@ interface ISub { int setUiccApplicationsEnabled(boolean enabled, int subscriptionId); int setDeviceToDeviceStatusSharing(int sharing, int subId); + + int setDeviceToDeviceStatusSharingContacts(String contacts, int subscriptionId); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 8ed9cffbe147..46752b7fe1ea 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2250,10 +2250,12 @@ interface ITelephony { * * @param subId the id of the subscription * @param thermalMitigationRequest holds the parameters necessary for the request. + * @param callingPackage the package name of the calling package. * @throws InvalidThermalMitigationRequestException if the parametes are invalid. */ int sendThermalMitigationRequest(int subId, - in ThermalMitigationRequest thermalMitigationRequest); + in ThermalMitigationRequest thermalMitigationRequest, + String callingPackage); /** * get the Generic Bootstrapping Architecture authentication keys @@ -2349,6 +2351,16 @@ interface ITelephony { boolean getCarrierSingleRegistrationEnabled(int subId); /** + * Overrides the ims feature validation result + */ + boolean setImsFeatureValidationOverride(int subId, String enabled); + + /** + * Gets the ims feature validation override value + */ + boolean getImsFeatureValidationOverride(int subId); + + /** * Return the mobile provisioning url that is used to launch a browser to allow users to manage * their mobile plan. */ 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/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt index 4f95ce585de2..b134fe737d05 100644 --- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt @@ -17,6 +17,7 @@ package com.android.test.input import android.os.HandlerThread +import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.Looper import android.view.InputChannel import android.view.InputEvent @@ -24,7 +25,8 @@ import android.view.InputEventReceiver import android.view.InputEventSender import android.view.KeyEvent import android.view.MotionEvent -import java.util.concurrent.CountDownLatch +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit import org.junit.Assert.assertEquals import org.junit.After import org.junit.Before @@ -44,41 +46,44 @@ private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) { assertEquals(expected.displayId, received.displayId) } -class TestInputEventReceiver(channel: InputChannel, looper: Looper) : - InputEventReceiver(channel, looper) { - companion object { - const val TAG = "TestInputEventReceiver" +private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T { + try { + return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS) + } catch (e: InterruptedException) { + throw RuntimeException("Unexpectedly interrupted while waiting for event") } +} - var lastEvent: InputEvent? = null +class TestInputEventReceiver(channel: InputChannel, looper: Looper) : + InputEventReceiver(channel, looper) { + private val mInputEvents = LinkedBlockingQueue<InputEvent>() override fun onInputEvent(event: InputEvent) { - lastEvent = when (event) { - is KeyEvent -> KeyEvent.obtain(event) - is MotionEvent -> MotionEvent.obtain(event) + when (event) { + is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event)) + is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event)) else -> throw Exception("Received $event is neither a key nor a motion") } finishInputEvent(event, true /*handled*/) } + + fun getInputEvent(): InputEvent { + return getEvent(mInputEvents) + } } class TestInputEventSender(channel: InputChannel, looper: Looper) : InputEventSender(channel, looper) { - companion object { - const val TAG = "TestInputEventSender" - } - data class FinishedResult(val seq: Int, val handled: Boolean) + data class FinishedSignal(val seq: Int, val handled: Boolean) + + private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>() - private var mFinishedSignal = CountDownLatch(1) override fun onInputEventFinished(seq: Int, handled: Boolean) { - finishedResult = FinishedResult(seq, handled) - mFinishedSignal.countDown() + mFinishedSignals.put(FinishedSignal(seq, handled)) } - lateinit var finishedResult: FinishedResult - fun waitForFinish() { - mFinishedSignal.await() - mFinishedSignal = CountDownLatch(1) // Ready for next event + fun getFinishedSignal(): FinishedSignal { + return getEvent(mFinishedSignals) } } @@ -111,13 +116,13 @@ class InputEventSenderAndReceiverTest { KeyEvent.KEYCODE_A, 0 /*repeat*/) val seq = 10 mSender.sendInputEvent(seq, key) - mSender.waitForFinish() + val receivedKey = mReceiver.getInputEvent() as KeyEvent + val finishedSignal = mSender.getFinishedSignal() // Check receiver - assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent) + assertKeyEvent(key, receivedKey) // Check sender - assertEquals(seq, mSender.finishedResult.seq) - assertEquals(true, mSender.finishedResult.handled) + assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) } } 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/UsesFeature2Test/AndroidManifest.xml b/tests/UsesFeature2Test/AndroidManifest.xml index 8caf4a158867..1f1a90958298 100644 --- a/tests/UsesFeature2Test/AndroidManifest.xml +++ b/tests/UsesFeature2Test/AndroidManifest.xml @@ -22,6 +22,9 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-feature android:name="android.hardware.sensor.accelerometer" /> <feature-group android:label="@string/minimal"> 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/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index f161e52c2fd5..e7718b546c1e 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -38,14 +38,12 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION; import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS; import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; -import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED; 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.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; -import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; import static android.os.Process.INVALID_UID; import static com.android.modules.utils.build.SdkLevel.isAtLeastR; @@ -103,20 +101,6 @@ public class NetworkCapabilitiesTest { @Test public void testMaybeMarkCapabilitiesRestricted() { - // verify EIMS is restricted - assertEquals((1 << NET_CAPABILITY_EIMS) & RESTRICTED_CAPABILITIES, - (1 << NET_CAPABILITY_EIMS)); - - // verify CBS is also restricted - assertEquals((1 << NET_CAPABILITY_CBS) & RESTRICTED_CAPABILITIES, - (1 << NET_CAPABILITY_CBS)); - - // verify default is not restricted - assertEquals((1 << NET_CAPABILITY_INTERNET) & RESTRICTED_CAPABILITIES, 0); - - // just to see - assertEquals(RESTRICTED_CAPABILITIES & UNRESTRICTED_CAPABILITIES, 0); - // check that internet does not get restricted NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); @@ -329,7 +313,8 @@ public class NetworkCapabilitiesTest { if (isAtLeastS()) { netCap.setSubIds(Set.of(TEST_SUBID1, TEST_SUBID2)); netCap.setUids(uids); - } else if (isAtLeastR()) { + } + if (isAtLeastR()) { netCap.setOwnerUid(123); netCap.setAdministratorUids(new int[] {5, 11}); } @@ -611,17 +596,18 @@ public class NetworkCapabilitiesTest { // From S, it is not allowed to have the same capability in both wanted and // unwanted list. assertThrows(IllegalArgumentException.class, () -> nc2.combineCapabilities(nc1)); + // Remove unwanted capability to continue other tests. + nc1.removeUnwantedCapability(NET_CAPABILITY_NOT_ROAMING); } else { nc2.combineCapabilities(nc1); // We will get this capability in both requested and unwanted lists thus this request // will never be satisfied. assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING)); + // For R or below, remove unwanted capability via removeCapability. + nc1.removeCapability(NET_CAPABILITY_NOT_ROAMING); } - // Remove unwanted capability to continue other tests. - nc1.removeUnwantedCapability(NET_CAPABILITY_NOT_ROAMING); - nc1.setSSID(TEST_SSID); nc2.combineCapabilities(nc1); if (isAtLeastR()) { @@ -983,26 +969,6 @@ public class NetworkCapabilitiesTest { assertNotEquals(-50, nc.getSignalStrength()); } - @Test @IgnoreUpTo(Build.VERSION_CODES.Q) - public void testDeduceRestrictedCapability() { - final NetworkCapabilities nc = new NetworkCapabilities(); - // Default capabilities don't have restricted capability. - assertFalse(nc.deduceRestrictedCapability()); - // If there is a force restricted capability, then the network capabilities is restricted. - nc.addCapability(NET_CAPABILITY_OEM_PAID); - nc.addCapability(NET_CAPABILITY_INTERNET); - assertTrue(nc.deduceRestrictedCapability()); - // Except for the force restricted capability, if there is any unrestricted capability in - // capabilities, then the network capabilities is not restricted. - nc.removeCapability(NET_CAPABILITY_OEM_PAID); - nc.addCapability(NET_CAPABILITY_CBS); - assertFalse(nc.deduceRestrictedCapability()); - // Except for the force restricted capability, the network capabilities will only be treated - // as restricted when there is no any unrestricted capability. - nc.removeCapability(NET_CAPABILITY_INTERNET); - assertTrue(nc.deduceRestrictedCapability()); - } - private void assertNoTransport(NetworkCapabilities nc) { for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { assertFalse(nc.hasTransport(i)); 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/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt index 01eb514a1c81..61ef5bdca487 100644 --- a/tests/net/integration/src/android/net/TestNetworkStackClient.kt +++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt @@ -19,6 +19,7 @@ package android.net import android.content.ComponentName import android.content.Context import android.content.Intent +import android.net.networkstack.NetworkStackClientBase import android.os.IBinder import com.android.server.net.integrationtests.TestNetworkStackService import org.mockito.Mockito.any @@ -29,28 +30,22 @@ import kotlin.test.fail const val TEST_ACTION_SUFFIX = ".Test" -class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) { +class TestNetworkStackClient(private val context: Context) : NetworkStackClientBase() { // TODO: consider switching to TrackRecord for more expressive checks private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>() + private val moduleConnector = ConnectivityModuleConnector { _, action, _, _ -> + val intent = Intent(action) + val serviceName = TestNetworkStackService::class.qualifiedName + ?: fail("TestNetworkStackService name not found") + intent.component = ComponentName(context.packageName, serviceName) + return@ConnectivityModuleConnector intent + }.also { it.init(context) } - private class TestDependencies(private val context: Context) : Dependencies { - override fun addToServiceManager(service: IBinder) = Unit - override fun checkCallerUid() = Unit - - override fun getConnectivityModuleConnector(): ConnectivityModuleConnector { - return ConnectivityModuleConnector { _, _, _, inSystemProcess -> - getNetworkStackIntent(inSystemProcess) - }.also { it.init(context) } - } - - private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? { - // Simulate out-of-system-process config: in-process service not found (null intent) - if (inSystemProcess) return null - val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX) - val serviceName = TestNetworkStackService::class.qualifiedName - ?: fail("TestNetworkStackService name not found") - intent.component = ComponentName(context.packageName, serviceName) - return intent + fun start() { + moduleConnector.startModuleService( + INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) { connector -> + onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(connector)) } } diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index db49e0b0047e..e039ef072542 100644 --- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -73,7 +73,7 @@ import kotlin.test.assertTrue import kotlin.test.fail const val SERVICE_BIND_TIMEOUT_MS = 5_000L -const val TEST_TIMEOUT_MS = 1_000L +const val TEST_TIMEOUT_MS = 10_000L /** * Test that exercises an instrumented version of ConnectivityService against an instrumented @@ -157,7 +157,6 @@ class ConnectivityServiceIntegrationTest { doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString()) networkStackClient = TestNetworkStackClient(realContext) - networkStackClient.init() networkStackClient.start() service = TestConnectivityService(makeDependencies()) diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 6fc605e269fe..36f205b72a62 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -64,6 +64,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.os.Process; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -219,8 +220,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - anyInt(), any(), nullable(String.class))).thenReturn(request); + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request); manager.requestNetwork(request, callback, handler); // callback triggers @@ -247,8 +248,8 @@ public class ConnectivityManagerTest { ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class); // register callback - when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - anyInt(), any(), nullable(String.class))).thenReturn(req1); + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1); manager.requestNetwork(req1, callback, handler); // callback triggers @@ -265,8 +266,8 @@ public class ConnectivityManagerTest { verify(callback, timeout(100).times(0)).onLosing(any(), anyInt()); // callback can be registered again - when(mService.requestNetwork(any(), anyInt(), captor.capture(), anyInt(), any(), anyInt(), - anyInt(), any(), nullable(String.class))).thenReturn(req2); + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2); manager.requestNetwork(req2, callback, handler); // callback triggers @@ -289,8 +290,8 @@ public class ConnectivityManagerTest { info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; when(mCtx.getApplicationInfo()).thenReturn(info); - when(mService.requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), anyInt(), - any(), nullable(String.class))).thenReturn(request); + when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), + anyInt(), any(), nullable(String.class))).thenReturn(request); Handler handler = new Handler(Looper.getMainLooper()); manager.requestNetwork(request, callback, handler); @@ -357,34 +358,40 @@ public class ConnectivityManagerTest { final NetworkCallback callback = new ConnectivityManager.NetworkCallback(); manager.requestNetwork(request, callback); - verify(mService).requestNetwork(eq(request.networkCapabilities), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); // Verify that register network callback does not calls requestNetwork at all. manager.registerNetworkCallback(request, callback); - verify(mService, never()).requestNetwork(any(), anyInt(), any(), anyInt(), any(), anyInt(), - anyInt(), any(), any()); + verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), + anyInt(), anyInt(), any(), any()); verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); + Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + manager.registerDefaultNetworkCallback(callback); - verify(mService).requestNetwork(eq(null), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); - Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + manager.registerDefaultNetworkCallbackAsUid(42, callback, handler); + verify(mService).requestNetwork(eq(42), eq(null), + eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + manager.requestBackgroundNetwork(request, handler, callback); - verify(mService).requestNetwork(eq(request.networkCapabilities), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); manager.registerSystemDefaultNetworkCallback(callback, handler); - verify(mService).requestNetwork(eq(null), + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), eq(testPkgName), eq(testAttributionTag)); reset(mService); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 0c2fb4ec6f27..44298d4b7652 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; @@ -216,7 +215,6 @@ import android.net.NetworkRequest; import android.net.NetworkScore; import android.net.NetworkSpecifier; import android.net.NetworkStack; -import android.net.NetworkStackClient; import android.net.NetworkStateSnapshot; import android.net.NetworkTestResultParcelable; import android.net.OemNetworkPreferences; @@ -236,6 +234,7 @@ import android.net.Uri; import android.net.VpnManager; import android.net.VpnTransportInfo; import android.net.metrics.IpConnectivityLog; +import android.net.networkstack.NetworkStackClientBase; import android.net.resolv.aidl.Nat64PrefixEventParcel; import android.net.resolv.aidl.PrivateDnsValidationEventParcel; import android.net.shared.NetworkMonitorUtils; @@ -266,6 +265,7 @@ import android.security.Credentials; import android.system.Os; import android.telephony.TelephonyManager; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; @@ -446,7 +446,7 @@ public class ConnectivityServiceTest { @Mock NetworkStatsManager mStatsManager; @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; - @Mock NetworkStackClient mNetworkStack; + @Mock NetworkStackClientBase mNetworkStack; @Mock PackageManager mPackageManager; @Mock UserManager mUserManager; @Mock NotificationManager mNotificationManager; @@ -1201,12 +1201,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()); @@ -1448,6 +1446,23 @@ public class ConnectivityServiceTest { }); } + private interface ExceptionalRunnable { + void run() throws Exception; + } + + private void withPermission(String permission, ExceptionalRunnable r) throws Exception { + if (mServiceContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + r.run(); + return; + } + try { + mServiceContext.setPermission(permission, PERMISSION_GRANTED); + r.run(); + } finally { + mServiceContext.setPermission(permission, PERMISSION_DENIED); + } + } + private static final int PRIMARY_USER = 0; private static final UidRange PRIMARY_UIDRANGE = UidRange.createForUser(UserHandle.of(PRIMARY_USER)); @@ -1556,7 +1571,7 @@ public class ConnectivityServiceTest { doReturn(mNetworkStack).when(deps).getNetworkStack(); doReturn(mSystemProperties).when(deps).getSystemProperties(); doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); - doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); + doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any()); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); @@ -3811,8 +3826,9 @@ public class ConnectivityServiceTest { NetworkCapabilities networkCapabilities = new NetworkCapabilities(); networkCapabilities.addTransportType(TRANSPORT_WIFI) .setNetworkSpecifier(new MatchAllNetworkSpecifier()); - mService.requestNetwork(networkCapabilities, NetworkRequest.Type.REQUEST.ordinal(), - null, 0, null, ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE, + mService.requestNetwork(Process.INVALID_UID, networkCapabilities, + NetworkRequest.Type.REQUEST.ordinal(), null, 0, null, + ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE, mContext.getPackageName(), getAttributionTag()); }); @@ -4041,7 +4057,7 @@ public class ConnectivityServiceTest { } @Test - public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception { + public void testRegisterPrivilegedDefaultCallbacksRequireNetworkSettings() throws Exception { mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false /* validated */); @@ -4050,12 +4066,19 @@ public class ConnectivityServiceTest { assertThrows(SecurityException.class, () -> mCm.registerSystemDefaultNetworkCallback(callback, handler)); callback.assertNoCallback(); + assertThrows(SecurityException.class, + () -> mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler)); + callback.assertNoCallback(); mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); mCm.registerSystemDefaultNetworkCallback(callback, handler); callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); mCm.unregisterNetworkCallback(callback); + + mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); } private void setCaptivePortalMode(int mode) { @@ -7488,6 +7511,10 @@ public class ConnectivityServiceTest { final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback(); registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID); + final TestNetworkCallback vpnDefaultCallbackAsUid = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallbackAsUid(VPN_UID, vpnDefaultCallbackAsUid, + new Handler(ConnectivityThread.getInstanceLooper())); + final int uid = Process.myUid(); final int userId = UserHandle.getUserId(uid); final ArrayList<String> allowList = new ArrayList<>(); @@ -7507,6 +7534,7 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7520,6 +7548,7 @@ public class ConnectivityServiceTest { defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -7535,6 +7564,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); // The following requires that the UID of this test package is greater than VPN_UID. This // is always true in practice because a plain AOSP build with no apps installed has almost @@ -7556,6 +7586,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7577,6 +7608,7 @@ public class ConnectivityServiceTest { assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7589,6 +7621,7 @@ public class ConnectivityServiceTest { assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7604,6 +7637,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7616,6 +7650,7 @@ public class ConnectivityServiceTest { defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7629,6 +7664,7 @@ public class ConnectivityServiceTest { assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertNull(mCm.getActiveNetwork()); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7640,6 +7676,7 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability. vpnUidDefaultCallback.assertNoCallback(); // VPN does not apply to VPN_UID + vpnDefaultCallbackAsUid.assertNoCallback(); assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7652,12 +7689,14 @@ public class ConnectivityServiceTest { defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); assertNull(mCm.getActiveNetwork()); mCm.unregisterNetworkCallback(callback); mCm.unregisterNetworkCallback(defaultCallback); mCm.unregisterNetworkCallback(vpnUidCallback); mCm.unregisterNetworkCallback(vpnUidDefaultCallback); + mCm.unregisterNetworkCallback(vpnDefaultCallbackAsUid); } private void setupLegacyLockdownVpn() { @@ -9758,14 +9797,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))); } @@ -9806,8 +9844,8 @@ public class ConnectivityServiceTest { for (int reqTypeInt : invalidReqTypeInts) { assertThrows("Expect throws for invalid request type " + reqTypeInt, IllegalArgumentException.class, - () -> mService.requestNetwork(nc, reqTypeInt, null, 0, null, - ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE, + () -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0, + null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE, mContext.getPackageName(), getAttributionTag()) ); } @@ -9926,7 +9964,7 @@ public class ConnectivityServiceTest { && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes)); mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() - .sendQosSessionLost(qosCallbackId, sessionId); + .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER); waitForIdle(); verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> session.getSessionId() == sessionId @@ -9934,6 +9972,36 @@ public class ConnectivityServiceTest { } @Test + public void testNrQosCallbackAvailableAndLost() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final int sessionId = 10; + final int qosCallbackId = 1; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + + final NrQosSessionAttributes attributes = new NrQosSessionAttributes( + 1, 2, 3, 4, 5, 6, 7, new ArrayList<>()); + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); + waitForIdle(); + + verify(mQosCallbackMockHelper.mCallback).onNrQosSessionAvailable(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_NR_BEARER), eq(attributes)); + + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_NR_BEARER); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_NR_BEARER)); + } + + @Test public void testQosCallbackTooManyRequests() throws Exception { mQosCallbackMockHelper = new QosCallbackMockHelper(); @@ -10378,6 +10446,7 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback, TEST_WORK_PROFILE_APP_UID); + // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well. mServiceContext.setPermission( Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); } @@ -10397,7 +10466,7 @@ public class ConnectivityServiceTest { private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup) throws Exception { - final int testPackageNameUid = 123; + final int testPackageNameUid = TEST_PACKAGE_UID; final String testPackageName = "per.app.defaults.package"; setupMultipleDefaultNetworksForOemNetworkPreferenceTest( networkPrefToSetup, testPackageNameUid, testPackageName); @@ -10533,6 +10602,11 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(defaultNetworkCallback); defaultNetworkCallback.assertNoCallback(); + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(Manifest.permission.NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -10543,19 +10617,22 @@ public class ConnectivityServiceTest { null, mEthernetNetworkAgent.getNetwork()); - // At this point with a restricted network used, the available callback should trigger + // At this point with a restricted network used, the available callback should trigger. defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mEthernetNetworkAgent.getNetwork()); + otherUidDefaultCallback.assertNoCallback(); // Now bring down the default network which should trigger a LOST callback. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); // At this point, with no network is available, the lost callback should trigger defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Confirm we can unregister without issues. mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); } @Test @@ -10573,6 +10650,11 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(defaultNetworkCallback); defaultNetworkCallback.assertNoCallback(); + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(Manifest.permission.NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. // The active nai for the default is null at this point as this is a restricted network. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); @@ -10584,15 +10666,19 @@ public class ConnectivityServiceTest { defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mEthernetNetworkAgent.getNetwork()); + otherUidDefaultCallback.assertNoCallback(); // Now bring down the default network which should trigger a LOST callback. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + otherUidDefaultCallback.assertNoCallback(); // At this point, with no network is available, the lost callback should trigger defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Confirm we can unregister without issues. mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); } @Test @@ -10606,6 +10692,11 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(defaultNetworkCallback); defaultNetworkCallback.assertNoCallback(); + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(Manifest.permission.NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + // Setup a process different than the test process to use the default network. This means // that the defaultNetworkCallback won't be tracked by the per-app policy. setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref); @@ -10621,6 +10712,9 @@ public class ConnectivityServiceTest { defaultNetworkCallback.assertNoCallback(); assertDefaultNetworkCapabilities(userId /* no networks */); + // The other UID does have access, and gets a callback. + otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + // Bring up unrestricted cellular. This should now satisfy the default network. setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, @@ -10628,25 +10722,31 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.getNetwork()); // At this point with an unrestricted network used, the available callback should trigger + // The other UID is unaffected and remains on the paid network. defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCellNetworkAgent.getNetwork()); assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Now bring down the per-app network. setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); - // Since the callback didn't use the per-app network, no callback should fire. + // Since the callback didn't use the per-app network, only the other UID gets a callback. + // Because the preference specifies no fallback, it does not switch to cellular. defaultNetworkCallback.assertNoCallback(); + otherUidDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); // Now bring down the default network. setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); // As this callback was tracking the default, this should now trigger. defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); // Confirm we can unregister without issues. mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); } /** diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 8c5d1d6d05e5..8b072c49de82 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -22,7 +22,9 @@ import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -56,6 +58,7 @@ import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -80,6 +83,12 @@ public class IpConnectivityMetricsTest { IpConnectivityMetrics mService; NetdEventListenerService mNetdListener; + private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build(); + private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build(); @Before public void setUp() { @@ -263,14 +272,6 @@ public class IpConnectivityMetricsTest { // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. IpConnectivityLog logger = new IpConnectivityLog(mService.impl); - NetworkCapabilities ncWifi = new NetworkCapabilities(); - NetworkCapabilities ncCell = new NetworkCapabilities(); - ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); - ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - - when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi); - when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell); - ApfStats apfStats = new ApfStats.Builder() .setDurationMs(45000) .setReceivedRas(10) @@ -584,11 +585,21 @@ public class IpConnectivityMetricsTest { return buffer.toString(); } - void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception { - mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1); + private void setCapabilities(int netId) { + final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + verify(mCm).registerNetworkCallback(any(), networkCallback.capture()); + networkCallback.getValue().onCapabilitiesChanged(new Network(netId), + netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL); + } + + void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception { + setCapabilities(netId); + mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1); } void dnsEvent(int netId, int type, int result, int latency) throws Exception { + setCapabilities(netId); mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0); } diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index 8ccea1aa3474..50aaaee24418 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -23,8 +23,9 @@ import static com.android.testutils.MiscAsserts.assertStringContains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; import android.content.Context; import android.net.ConnectivityManager; @@ -42,6 +43,7 @@ import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpCon import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.io.FileOutputStream; import java.io.PrintWriter; @@ -61,18 +63,16 @@ public class NetdEventListenerServiceTest { NetdEventListenerService mService; ConnectivityManager mCm; + private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build(); + private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build(); @Before public void setUp() { - NetworkCapabilities ncWifi = new NetworkCapabilities(); - NetworkCapabilities ncCell = new NetworkCapabilities(); - ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); - ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - mCm = mock(ConnectivityManager.class); - when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi); - when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell); - mService = new NetdEventListenerService(mCm); } @@ -470,7 +470,16 @@ public class NetdEventListenerServiceTest { assertEquals(want, got); } + private void setCapabilities(int netId) { + final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + verify(mCm).registerNetworkCallback(any(), networkCallback.capture()); + networkCallback.getValue().onCapabilitiesChanged(new Network(netId), + netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL); + } + Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) { + setCapabilities(netId); return new Thread(() -> { try { mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1); @@ -481,6 +490,7 @@ public class NetdEventListenerServiceTest { } void dnsEvent(int netId, int type, int result, int latency) throws Exception { + setCapabilities(netId); mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0); } diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index dde77b00a73f..2f3ee68249ab 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -50,9 +50,7 @@ import com.android.connectivity.resources.R; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -104,17 +102,6 @@ public class NetworkNotificationManagerTest { NetworkNotificationManager mManager; - - @BeforeClass - public static void setUpClass() { - Notification.DevFlags.sForceDefaults = true; - } - - @AfterClass - public static void tearDownClass() { - Notification.DevFlags.sForceDefaults = false; - } - @Before public void setUp() { MockitoAnnotations.initMocks(this); 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/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index 7515971b8307..516c206672d2 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -204,7 +204,7 @@ public class VcnManagerTest { new VcnStatusCallbackBinder(INLINE_EXECUTOR, mMockStatusCallback); cbBinder.onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); - verify(mMockStatusCallback).onVcnStatusChanged(VCN_STATUS_CODE_ACTIVE); + verify(mMockStatusCallback).onStatusChanged(VCN_STATUS_CODE_ACTIVE); cbBinder.onGatewayConnectionError( UNDERLYING_NETWORK_CAPABILITIES, diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index a9d5822be226..f15d4204d125 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; @@ -47,7 +49,9 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.AppOpsManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.NetworkCapabilities; @@ -238,9 +242,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 @@ -329,6 +338,13 @@ public class VcnManagementServiceTest { return captor.getValue(); } + private BroadcastReceiver getPackageChangeReceiver() { + final ArgumentCaptor<BroadcastReceiver> captor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mMockContext).registerReceiver(captor.capture(), any(), any(), any()); + return captor.getValue(); + } + private Vcn startAndGetVcnInstance(ParcelUuid uuid) { mVcnMgmtSvc.setVcnConfig(uuid, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); return mVcnMgmtSvc.getAllVcns().get(uuid); @@ -391,7 +407,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 @@ -405,6 +421,42 @@ public class VcnManagementServiceTest { } @Test + public void testPackageChangeListenerRegistered() throws Exception { + verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> { + return filter.hasAction(Intent.ACTION_PACKAGE_ADDED) + && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED) + && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); + }), any(), any()); + } + + @Test + public void testPackageChangeListener_packageAdded() throws Exception { + final BroadcastReceiver receiver = getPackageChangeReceiver(); + + verify(mMockContext).registerReceiver(any(), argThat(filter -> { + return filter.hasAction(Intent.ACTION_PACKAGE_ADDED) + && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED) + && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); + }), any(), any()); + + receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_ADDED)); + verify(mSubscriptionTracker).handleSubscriptionsChanged(); + } + + @Test + public void testPackageChangeListener_packageRemoved() throws Exception { + final BroadcastReceiver receiver = getPackageChangeReceiver(); + + verify(mMockContext).registerReceiver(any(), argThat(filter -> { + return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED) + && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); + }), any(), any()); + + receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED)); + verify(mSubscriptionTracker).handleSubscriptionsChanged(); + } + + @Test public void testSetVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); @@ -492,7 +544,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 +557,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 +568,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 +596,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 +625,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 +842,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/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index 4fa63d4ff640..c853fc50fdf7 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -29,6 +29,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -139,8 +140,7 @@ public class VcnTest { mTestLooper.dispatchAll(); } - @Test - public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() { + private void verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(boolean isActive) { final NetworkRequestListener requestListener = verifyAndGetRequestListener(); startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]); @@ -150,14 +150,27 @@ public class VcnTest { final TelephonySubscriptionSnapshot updatedSnapshot = mock(TelephonySubscriptionSnapshot.class); + mVcn.setIsActive(isActive); + mVcn.updateSubscriptionSnapshot(updatedSnapshot); mTestLooper.dispatchAll(); for (final VcnGatewayConnection gateway : gatewayConnections) { - verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot)); + verify(gateway, isActive ? times(1) : never()) + .updateSubscriptionSnapshot(eq(updatedSnapshot)); } } + @Test + public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() { + verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(true /* isActive */); + } + + @Test + public void testSubscriptionSnapshotUpdatesVcnGatewayConnectionsWhileInactive() { + verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(false /* isActive */); + } + private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { for (final int[] caps : TEST_CAPS) { startVcnGatewayWithCapabilities(requestListener, caps); @@ -187,7 +200,6 @@ public class VcnTest { NetworkRequestListener requestListener, Set<VcnGatewayConnection> expectedGatewaysTornDown) { assertFalse(mVcn.isActive()); - assertTrue(mVcn.getVcnGatewayConnections().isEmpty()); for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) { verify(gatewayConnection).teardownAsynchronously(); } @@ -238,6 +250,51 @@ public class VcnTest { } @Test + public void testGatewayQuitWhileInactive() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + final Set<VcnGatewayConnection> gatewayConnections = + new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener)); + + mVcn.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); + statusCallback.onQuit(); + mTestLooper.dispatchAll(); + + // Verify that the VCN requests the networkRequests be resent + assertEquals(1, mVcn.getVcnGatewayConnections().size()); + verify(mVcnNetworkProvider, never()).resendAllRequests(requestListener); + } + + @Test + public void testUpdateConfigReevaluatesGatewayConnections() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + startGatewaysAndGetGatewayConnections(requestListener); + assertEquals(2, mVcn.getVcnGatewayConnectionConfigMap().size()); + + // Create VcnConfig with only one VcnGatewayConnectionConfig so a gateway connection is torn + // down + final VcnGatewayConnectionConfig activeConfig = + VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[0]); + final VcnGatewayConnectionConfig removedConfig = + VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(TEST_CAPS[1]); + final VcnConfig updatedConfig = + new VcnConfig.Builder(mContext).addGatewayConnectionConfig(activeConfig).build(); + + mVcn.updateConfig(updatedConfig); + mTestLooper.dispatchAll(); + + final VcnGatewayConnection activeGatewayConnection = + mVcn.getVcnGatewayConnectionConfigMap().get(activeConfig); + final VcnGatewayConnection removedGatewayConnection = + mVcn.getVcnGatewayConnectionConfigMap().get(removedConfig); + verify(activeGatewayConnection, never()).teardownAsynchronously(); + verify(removedGatewayConnection).teardownAsynchronously(); + verify(mVcnNetworkProvider).resendAllRequests(requestListener); + } + + @Test public void testUpdateConfigExitsSafeMode() { final NetworkRequestListener requestListener = verifyAndGetRequestListener(); final Set<VcnGatewayConnection> gatewayConnections = @@ -261,8 +318,8 @@ public class VcnTest { verify(mVcnNetworkProvider, times(2)).registerListener(eq(requestListener)); assertTrue(mVcn.isActive()); for (final int[] caps : TEST_CAPS) { - // Expect each gateway connection created on initial startup, and again with new configs - verify(mDeps, times(2)) + // Expect each gateway connection created only on initial startup + verify(mDeps) .newVcnGatewayConnection( eq(mVcnContext), eq(TEST_SUB_GROUP), @@ -271,4 +328,14 @@ public class VcnTest { any()); } } + + @Test + public void testIgnoreNetworkRequestWhileInactive() { + mVcn.setIsActive(false /* isActive */); + + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + triggerVcnRequestListeners(requestListener); + + verify(mDeps, never()).newVcnGatewayConnection(any(), any(), any(), any(), any()); + } } 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 |