Merge "Store Brightness Per Display" into sc-dev
diff --git a/Android.bp b/Android.bp
index ee5e992..33b66b6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -401,6 +401,13 @@
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.hardware.security.keymint-V1-java-source",
+ ":android.hardware.security.secureclock-V1-java-source",
+ ":android.security.apc-java-source",
+ ":android.security.authorization-java-source",
+ ":android.security.maintenance-java-source",
+ ":android.security.vpnprofilestore-java-source",
+ ":android.system.keystore2-V1-java-source",
":credstore_aidl",
":dumpstate_aidl",
":framework_native_aidl",
@@ -582,13 +589,7 @@
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.vibrator-V2-java",
- "android.security.apc-java",
- "android.security.authorization-java",
- "android.security.maintenance-java",
- "android.security.vpnprofilestore-java",
- "android.system.keystore2-V1-java",
"android.system.suspend.control.internal-java",
- "cameraprotosnano",
"devicepolicyprotosnano",
"com.android.sysprop.apex",
@@ -668,7 +669,6 @@
// 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",
@@ -1461,7 +1461,7 @@
],
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 e08d22c..5acfe6a 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -8,6 +8,14 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 31ab259..35cea3e 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 @@
/**
* 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 @@
}
/**
- * 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 @@
/**
* Asserts that this {@link AppSearchBatchResult} has no failures.
- *
* @hide
*/
public void checkSuccess() {
@@ -162,8 +161,6 @@
* 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 @@
*
* @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 @@
*
* @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 @@
*
* @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 0c6b86b..9776827 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 @@
/** 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 @@
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 0000000..e585d91
--- /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 440f633..b66837d 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.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 @@
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 @@
/** 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 @@
*
* <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 @@
/**
* Creates a new successful {@link AppSearchResult}.
- *
- * @hide
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
@@ -219,8 +221,6 @@
/**
* Creates a new failed {@link AppSearchResult}.
- *
- * @hide
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@@ -228,6 +228,20 @@
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 9ea73a9..0f6468a 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 @@
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.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 @@
*/
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 @@
* 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 @@
// 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 @@
}
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 @@
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (Throwable t) {
+ callback.accept(AppSearchResult.throwableToFailedResult(t));
}
}
@@ -176,7 +230,7 @@
*/
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 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 @@
// 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 @@
// 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 @@
request.getNamespace(),
request.getUri(),
request.getUsageTimeMillis(),
+ /*systemUsage=*/ false,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@@ -541,6 +627,25 @@
}
/**
+ * 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 @@
}
}
}
+
+ /**
+ * 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 fb63e16..b30ed50 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.NonNull;
import android.annotation.UserIdInt;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -35,14 +36,15 @@
* <p>Apps can retrieve indexed documents through the {@link #search} API.
*/
public class GlobalSearchSession implements Closeable {
-
- private final IAppSearchManager mService;
-
- @UserIdInt
- private final int mUserId;
- private boolean mIsClosed = false;
+ private static final String TAG = "AppSearchGlobalSearchSe";
private final String mPackageName;
+ @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
@@ -117,9 +119,66 @@
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 ba27762..48c397f 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.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 @@
* 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 @@
in Map<String, List<Bundle>> schemasPackageAccessibleBundles,
boolean forceOverride,
in int userId,
+ in int schemaVersion,
in IAppSearchResultCallback callback);
/**
@@ -60,8 +63,7 @@
* @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 @@
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 @@
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 @@
* @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 @@
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 55f0c80..a8048dc 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 @@
*/
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 4ce95ea..8c9d950 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 @@
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 @@
*
* <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 @@
}
/**
- * 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 6881a27..1719e14 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 @@
* <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 0000000..1f56ef3
--- /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 5ae9a41..511b42a 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 @@
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 @@
* 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.
+ * Returns {@code true} if this migrator's source type needs to be migrated to update from
+ * currentVersion to finalVersion.
*
- * <p>Providing 0 will trigger migration for any version less than the final version in the new
- * schema.
- *
- * @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 @@
* <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 @@
* <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 455cf3a..8da68c0 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 @@
* <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 0000000..2e152f8
--- /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 2cd08c6..646e73c 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 @@
/** 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 cb20849..55a228d 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 7888c8d..19d9430 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 @@
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 @@
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 @@
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 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 @@
/** 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 @@
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 @@
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 @@
}
/**
+ * 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 c1eedcd..1324451 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 @@
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 @@
}
/**
- * 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 @@
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 @@
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 @@
}
/**
- * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
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 0000000..dc04cf3
--- /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 fae8ad4..c9473bd 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 @@
// 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 @@
+ 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 2806974..91ed6cd 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.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.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 @@
@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 @@
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 @@
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 @@
}
@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 @@
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 8c953d1..cacf880 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.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 @@
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 ad94a0a..1ed26d67 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.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 @@
/** 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 @@
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 @@
* @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 @@
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 @@
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 @@
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 5e8760e..de9d609 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.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.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.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.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 @@
* @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 @@
@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 @@
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 @@
* @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 @@
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 @@
AppSearchSchema schema =
SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
- result.add(schema);
+ responseBuilder.addSchema(schema);
}
- return result;
+ 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 results;
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -749,6 +797,18 @@
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 @@
@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 @@
}
}
+ /** 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 @@
throwIfClosedLocked();
PersistToDiskResultProto persistToDiskResultProto =
- mIcingSearchEngineLocked.persistToDisk();
+ mIcingSearchEngineLocked.persistToDisk(PersistType.Code.FULL);
checkSuccess(persistToDiskResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
@@ -1275,6 +1458,153 @@
.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 ce1c9f4..800b073 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 @@
* 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 @@
@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 1d8db72..bf7e533 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 @@
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 3b5e275..d9e8adb 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 @@
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 0952215..58f430b 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 9ef6e0b..f0de496 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.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.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 @@
@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 @@
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 69a4c18..5042ce0 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.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 @@
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 1428fb1..2069043 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 @@
/**
* 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 @@
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 4a3c7a5..fd4734c 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 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 440050f..f39916e 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 @@
import android.annotation.NonNull;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.io.Closeable;
/**
@@ -51,6 +53,26 @@
@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/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
index 999860f..e65abcf 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
@@ -48,7 +48,8 @@
@Override
public boolean onStopJob(final JobParameters params) {
Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId()
- + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason()));
+ + ", reason="
+ + JobParameters.getLegacyReasonCodeDescription(params.getLegacyStopReason()));
return false;
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 4c8ab93..5729385 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1021,6 +1021,41 @@
mJobId = jobId;
}
+ /**
+ * Creates a new Builder of JobInfo from an existing instance.
+ * @hide
+ */
+ public Builder(@NonNull JobInfo job) {
+ mJobId = job.getId();
+ mJobService = job.getService();
+ mExtras = job.getExtras();
+ mTransientExtras = job.getTransientExtras();
+ mClipData = job.getClipData();
+ mClipGrantFlags = job.getClipGrantFlags();
+ mPriority = job.getPriority();
+ mFlags = job.getFlags();
+ mConstraintFlags = job.getConstraintFlags();
+ mNetworkRequest = job.getRequiredNetwork();
+ mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+ mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
+ mTriggerContentUris = job.getTriggerContentUris() != null
+ ? new ArrayList<>(Arrays.asList(job.getTriggerContentUris())) : null;
+ mTriggerContentUpdateDelay = job.getTriggerContentUpdateDelay();
+ mTriggerContentMaxDelay = job.getTriggerContentMaxDelay();
+ mIsPersisted = job.isPersisted();
+ mMinLatencyMillis = job.getMinLatencyMillis();
+ mMaxExecutionDelayMillis = job.getMaxExecutionDelayMillis();
+ mIsPeriodic = job.isPeriodic();
+ mHasEarlyConstraint = job.hasEarlyConstraint();
+ mHasLateConstraint = job.hasLateConstraint();
+ mIntervalMillis = job.getIntervalMillis();
+ mFlexMillis = job.getFlexMillis();
+ mInitialBackoffMillis = job.getInitialBackoffMillis();
+ // mBackoffPolicySet isn't set but it's fine since this is copying from an already valid
+ // job.
+ mBackoffPolicy = job.getBackoffPolicy();
+ }
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Builder setPriority(int priority) {
@@ -1462,7 +1497,7 @@
* <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>
@@ -1483,7 +1518,7 @@
*
* <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/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 0d3e001..60f6475 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -16,10 +16,14 @@
package android.app.job;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
+import android.content.pm.PackageManager;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.Uri;
@@ -30,6 +34,9 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Contains the parameters used to configure/identify your job. You do not create this object
* yourself, instead it is handed in to your application by the System.
@@ -82,7 +89,7 @@
*/
// TODO(142420609): make it @SystemApi for mainline
@NonNull
- public static String getReasonCodeDescription(int reasonCode) {
+ public static String getLegacyReasonCodeDescription(int reasonCode) {
switch (reasonCode) {
case REASON_CANCELED: return "canceled";
case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
@@ -96,12 +103,119 @@
}
/** @hide */
- // @SystemApi TODO make it a system api for mainline
+ // TODO: move current users of legacy reasons to new public reasons
@NonNull
public static int[] getJobStopReasonCodes() {
return JOB_STOP_REASON_CODES;
}
+ /**
+ * There is no reason the job is stopped. This is the value returned from the JobParameters
+ * object passed to {@link JobService#onStartJob(JobParameters)}.
+ */
+ public static final int STOP_REASON_UNDEFINED = 0;
+ /**
+ * The job was cancelled directly by the app, either by calling
+ * {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or by scheduling a
+ * new job with the same job ID.
+ */
+ public static final int STOP_REASON_CANCELLED_BY_APP = 1;
+ /** The job was stopped to run a higher priority job of the app. */
+ public static final int STOP_REASON_PREEMPT = 2;
+ /**
+ * The job used up its maximum execution time and timed out. Each individual job has a maximum
+ * execution time limit, regardless of how much total quota the app has. See the note on
+ * {@link JobScheduler} for the execution time limits.
+ */
+ public static final int STOP_REASON_TIMEOUT = 3;
+ /**
+ * The device state (eg. Doze, battery saver, memory usage, etc) requires JobScheduler stop this
+ * job.
+ */
+ public static final int STOP_REASON_DEVICE_STATE = 4;
+ /**
+ * The requested battery-not-low constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5;
+ /**
+ * The requested charging constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresCharging(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_CHARGING = 6;
+ /**
+ * The requested connectivity constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7;
+ /**
+ * The requested idle constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+ /**
+ * The requested storage-not-low constraint is no longer satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+ */
+ public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9;
+ /**
+ * The app has consumed all of its current quota. Each app is assigned a quota of how much
+ * it can run jobs within a certain time frame. The quota is informed, in part, by app standby
+ * buckets. Once an app has used up all of its quota, it won't be able to start jobs until
+ * quota is replenished, is changed, or is temporarily not applied.
+ *
+ * @see UsageStatsManager#getAppStandbyBucket()
+ */
+ public static final int STOP_REASON_QUOTA = 10;
+ /**
+ * The app is restricted from running in the background.
+ *
+ * @see ActivityManager#isBackgroundRestricted()
+ * @see PackageManager#isInstantApp()
+ */
+ public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11;
+ /**
+ * The current standby bucket requires that the job stop now.
+ *
+ * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+ */
+ public static final int STOP_REASON_APP_STANDBY = 12;
+ /**
+ * The user stopped the job. This can happen either through force-stop, or via adb shell
+ * commands.
+ */
+ public static final int STOP_REASON_USER = 13;
+ /** The system is doing some processing that requires stopping this job. */
+ public static final int STOP_REASON_SYSTEM_PROCESSING = 14;
+
+ /** @hide */
+ @IntDef(prefix = {"STOP_REASON_"}, value = {
+ STOP_REASON_UNDEFINED,
+ STOP_REASON_CANCELLED_BY_APP,
+ STOP_REASON_PREEMPT,
+ STOP_REASON_TIMEOUT,
+ STOP_REASON_DEVICE_STATE,
+ STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+ STOP_REASON_CONSTRAINT_CHARGING,
+ STOP_REASON_CONSTRAINT_CONNECTIVITY,
+ STOP_REASON_CONSTRAINT_DEVICE_IDLE,
+ STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+ STOP_REASON_QUOTA,
+ STOP_REASON_BACKGROUND_RESTRICTION,
+ STOP_REASON_APP_STANDBY,
+ STOP_REASON_USER,
+ STOP_REASON_SYSTEM_PROCESSING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StopReason {
+ }
+
@UnsupportedAppUsage
private final int jobId;
private final PersistableBundle extras;
@@ -116,7 +230,8 @@
private final String[] mTriggeredContentAuthorities;
private final Network network;
- private int stopReason; // Default value of stopReason is REASON_CANCELED
+ private int mStopReason = STOP_REASON_UNDEFINED;
+ private int mLegacyStopReason; // Default value of stopReason is REASON_CANCELED
private String debugStopReason; // Human readable stop reason for debugging.
/** @hide */
@@ -145,15 +260,23 @@
}
/**
- * Reason onStopJob() was called on this job.
- * @hide
+ * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
+ * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
+ * yet been called.
*/
+ @StopReason
public int getStopReason() {
- return stopReason;
+ return mStopReason;
+ }
+
+ /** @hide */
+ public int getLegacyStopReason() {
+ return mLegacyStopReason;
}
/**
* Reason onStopJob() was called on this job.
+ *
* @hide
*/
public String getDebugStopReason() {
@@ -368,13 +491,16 @@
} else {
network = null;
}
- stopReason = in.readInt();
+ mStopReason = in.readInt();
+ mLegacyStopReason = in.readInt();
debugStopReason = in.readString();
}
/** @hide */
- public void setStopReason(int reason, String debugStopReason) {
- stopReason = reason;
+ public void setStopReason(@StopReason int reason, int legacyStopReason,
+ String debugStopReason) {
+ mStopReason = reason;
+ mLegacyStopReason = legacyStopReason;
this.debugStopReason = debugStopReason;
}
@@ -406,7 +532,8 @@
} else {
dest.writeInt(0);
}
- dest.writeInt(stopReason);
+ dest.writeInt(mStopReason);
+ dest.writeInt(mLegacyStopReason);
dest.writeString(debugStopReason);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 0f3d299..fa7a2d3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -139,19 +139,23 @@
* Once this method is called, you no longer need to call
* {@link #jobFinished(JobParameters, boolean)}.
*
- * <p>This will happen if the requirements specified at schedule time are no longer met. For
+ * <p>This may happen if the requirements specified at schedule time are no longer met. For
* example you may have requested WiFi with
* {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
* job was executing the user toggled WiFi. Another example is if you had specified
- * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
- * idle maintenance window. You are solely responsible for the behavior of your application
- * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+ * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left
+ * its idle maintenance window. There are many other reasons a job can be stopped early besides
+ * constraints no longer being satisfied. {@link JobParameters#getStopReason()} will return the
+ * reason this method was called. You are solely responsible for the behavior of your
+ * application upon receipt of this message; your app will likely start to misbehave if you
+ * ignore it.
* <p>
* Once this method returns (or times out), the system releases the wakelock that it is holding
* on behalf of the job.</p>
*
- * @param params The parameters identifying this job, as supplied to
- * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param params The parameters identifying this job, similar to what was supplied to the job in
+ * the {@link #onStartJob(JobParameters)} callback, but with the stop reason
+ * included.
* @return {@code true} to indicate to the JobManager whether you'd like to reschedule
* this job based on the retry criteria provided at job creation-time; or {@code false}
* to end the job entirely. Regardless of the value returned, your job must stop executing.
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 88f21a5..63b3959 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -40,9 +40,9 @@
/**
* Interface to access and modify the permanent and temporary power save allow list. The two lists
* are kept separately. Apps placed on the permanent allow list are only removed via an explicit
- * {@link #removeFromAllowList(String)} call. Apps allow-listed by default by the system cannot be
- * removed. Apps placed on the temporary allow list are removed from that allow list after a
- * predetermined amount of time.
+ * {@link #removeFromPermanentAllowList(String)} call. Apps allow-listed by default by the system
+ * cannot be removed. Apps placed on the temporary allow list are removed from that allow list after
+ * a predetermined amount of time.
*
* @hide
*/
@@ -402,9 +402,9 @@
*
* @param includingIdle Set to true if the app should be allow-listed from device idle as well
* as other power save restrictions
- * @hide
*/
@NonNull
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public int[] getAllowListedAppIds(boolean includingIdle) {
try {
if (includingIdle) {
@@ -445,7 +445,7 @@
* @param packageName The app to remove from the allow list
*/
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
- public void removeFromAllowList(@NonNull String packageName) {
+ public void removeFromPermanentAllowList(@NonNull String packageName) {
try {
mService.removePowerSaveWhitelistApp(packageName);
} catch (RemoteException e) {
@@ -463,8 +463,8 @@
*/
@UserHandleAware
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
- public void addToTemporaryAllowList(@NonNull String packageName, long durationMs,
- @ReasonCode int reasonCode, @Nullable String reason) {
+ public void addToTemporaryAllowList(@NonNull String packageName, @ReasonCode int reasonCode,
+ @Nullable String reason, long durationMs) {
try {
mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(),
reasonCode, reason);
@@ -488,7 +488,7 @@
@UserHandleAware
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public long addToTemporaryAllowListForEvent(@NonNull String packageName,
- @AllowListEvent int event, @ReasonCode int reasonCode, @Nullable String reason) {
+ @ReasonCode int reasonCode, @Nullable String reason, @AllowListEvent int event) {
try {
switch (event) {
case EVENT_MMS:
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index eba39c7..07231b0 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -439,12 +439,12 @@
* whitelisted by default by the system cannot be removed.
*
* @param packageName The app to remove from the whitelist
- * @deprecated Use {@link PowerExemptionManager#removeFromAllowList(String)} instead
+ * @deprecated Use {@link PowerExemptionManager#removeFromPermanentAllowList(String)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void removeFromWhitelist(@NonNull String packageName) {
- mPowerExemptionManager.removeFromAllowList(packageName);
+ mPowerExemptionManager.removeFromPermanentAllowList(packageName);
}
/**
@@ -455,13 +455,13 @@
* @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure.
* @param reason a optional human readable reason string, could be null or empty string.
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
- * String, long, int, String)} instead
+ * String, int, String, long)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public void whitelistAppTemporarily(@NonNull String packageName, long durationMs,
@ReasonCode int reasonCode, @Nullable String reason) {
- mPowerExemptionManager.addToTemporaryAllowList(packageName, durationMs, reasonCode, reason);
+ mPowerExemptionManager.addToTemporaryAllowList(packageName, reasonCode, reason, durationMs);
}
/**
@@ -470,13 +470,13 @@
* @param packageName The package to add to the temp whitelist
* @param durationMs How long to keep the app on the temp whitelist for (in milliseconds)
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
- * String, long, int, String)} instead
+ * String, int, String, long)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) {
mPowerExemptionManager.addToTemporaryAllowList(
- packageName, durationMs, REASON_UNKNOWN, packageName);
+ packageName, REASON_UNKNOWN, packageName, durationMs);
}
/**
@@ -490,14 +490,14 @@
* used for logging purposes. Could be null or empty string.
* @return The duration (in milliseconds) that the app is whitelisted for
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
- * String, int, int, String)} instead
+ * String, int, String, int)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
@WhitelistEvent int event, @Nullable String reason) {
return mPowerExemptionManager.addToTemporaryAllowListForEvent(
- packageName, event, REASON_UNKNOWN, reason);
+ packageName, REASON_UNKNOWN, reason, event);
}
/**
@@ -512,14 +512,14 @@
* used for logging purposes. Could be null or empty string.
* @return The duration (in milliseconds) that the app is whitelisted for
* @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
- * String, int, int, String)} instead
+ * String, int, String, int)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
@WhitelistEvent int event, @ReasonCode int reasonCode, @Nullable String reason) {
return mPowerExemptionManager.addToTemporaryAllowListForEvent(
- packageName, event, reasonCode, reason);
+ packageName, reasonCode, reason, event);
}
/**
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 7833a03..6ae91a0 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.util.proto.ProtoOutputStream;
import java.util.List;
@@ -36,7 +37,7 @@
/**
* Cancel the jobs for a given uid (e.g. when app data is cleared)
*/
- void cancelJobsForUid(int uid, String reason);
+ void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, String debugReason);
/**
* These are for activity manager to communicate to use what is currently performing backups.
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 b958c3f..d94d638 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -266,6 +266,8 @@
String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];
+ int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
+
String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];
private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
@@ -505,6 +507,7 @@
int[] preferredUidForContext = mRecycledPreferredUidForContext;
int[] workTypeForContext = mRecycledWorkTypeForContext;
String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
+ int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext;
String[] shouldStopJobReason = mRecycledShouldStopJobReason;
updateCounterConfigLocked();
@@ -528,6 +531,7 @@
slotChanged[i] = false;
preferredUidForContext[i] = js.getPreferredUid();
preemptReasonForContext[i] = null;
+ preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED;
shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
}
if (DEBUG) {
@@ -551,6 +555,7 @@
int allWorkTypes = getJobWorkTypes(nextPending);
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
boolean startingJob = false;
+ int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
String preemptReason = null;
// TODO(141645789): rewrite this to look at empty contexts first so we don't
// unnecessarily preempt
@@ -582,6 +587,7 @@
// assign the new job to this context since we'll reassign when the
// preempted job finally stops.
preemptReason = reason;
+ preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
}
continue;
}
@@ -597,6 +603,7 @@
minPriorityForPreemption = jobPriority;
selectedContextId = j;
preemptReason = "higher priority job found";
+ preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
// In this case, we're just going to preempt a low priority job, we're not
// actually starting a job, so don't set startingJob.
}
@@ -604,6 +611,7 @@
if (selectedContextId != -1) {
contextIdToJobMap[selectedContextId] = nextPending;
slotChanged[selectedContextId] = true;
+ preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
preemptReasonForContext[selectedContextId] = preemptReason;
}
if (startingJob) {
@@ -631,8 +639,13 @@
}
// preferredUid will be set to uid of currently running job.
activeServices.get(i).cancelExecutingJobLocked(
+ preemptReasonCodeForContext[i],
JobParameters.REASON_PREEMPT, preemptReasonForContext[i]);
- preservePreferredUid = true;
+ // Only preserve the UID if we're preempting for the same UID. If we're stopping
+ // the job because something is pending (eg. EJs), then we shouldn't preserve
+ // the UID.
+ preservePreferredUid =
+ preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT;
} else {
final JobStatus pendingJob = contextIdToJobMap[i];
if (DEBUG) {
@@ -657,7 +670,8 @@
final JobStatus jobStatus = jsc.getRunningJobLocked();
if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_TIMEOUT, debugReason);
+ jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_TIMEOUT, debugReason);
}
}
}
@@ -877,7 +891,7 @@
}
// 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/JobPackageTracker.java b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
index 6ffac91..02f9129 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
@@ -374,7 +374,7 @@
pw.print(pe.stopReasons.valueAt(k));
pw.print("x ");
pw.print(JobParameters
- .getReasonCodeDescription(pe.stopReasons.keyAt(k)));
+ .getLegacyReasonCodeDescription(pe.stopReasons.keyAt(k)));
}
pw.println();
}
@@ -621,7 +621,7 @@
if (reason != null) {
pw.print(mEventReasons[index]);
} else {
- pw.print(JobParameters.getReasonCodeDescription(
+ pw.print(JobParameters.getLegacyReasonCodeDescription(
(mEventCmds[index] & EVENT_STOP_REASON_MASK)
>> EVENT_STOP_REASON_SHIFT));
}
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 2b08ba5..8ac237e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -746,8 +746,11 @@
Slog.d(TAG, "Removing jobs for package " + pkgName
+ " in user " + userId);
}
+ // By the time we get here, the process should have already
+ // been stopped, so the app wouldn't get the stop reason,
+ // so just put USER instead of UNINSTALL or DISABLED.
cancelJobsForPackageAndUid(pkgName, pkgUid,
- "app disabled");
+ JobParameters.STOP_REASON_USER, "app disabled");
}
} catch (RemoteException|IllegalArgumentException e) {
/*
@@ -785,7 +788,11 @@
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
- cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
+ // By the time we get here, the process should have already
+ // been stopped, so the app wouldn't get the stop reason,
+ // so just put USER instead of UNINSTALL or DISABLED.
+ cancelJobsForPackageAndUid(pkgName, uidRemoved,
+ JobParameters.STOP_REASON_USER, "app uninstalled");
synchronized (mLock) {
for (int c = 0; c < mControllers.size(); ++c) {
mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
@@ -837,7 +844,8 @@
if (DEBUG) {
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
- cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
+ cancelJobsForPackageAndUid(pkgName, pkgUid,
+ JobParameters.STOP_REASON_USER, "app force stopped");
}
}
}
@@ -924,8 +932,7 @@
final String servicePkg = job.getService().getPackageName();
if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
// Only limit schedule calls for persisted jobs scheduled by the app itself.
- final String pkg =
- packageName == null ? job.getService().getPackageName() : packageName;
+ final String pkg = packageName == null ? servicePkg : packageName;
if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) {
// Don't log too frequently
@@ -972,14 +979,10 @@
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(uId,
- job.getService().getPackageName())) {
- Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
- + " -- package not allowed to start");
- return JobScheduler.RESULT_FAILURE;
- }
- } catch (RemoteException e) {
+ if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
+ Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ + " -- package not allowed to start");
+ return JobScheduler.RESULT_FAILURE;
}
synchronized (mLock) {
@@ -1029,7 +1032,8 @@
if (toCancel != null) {
// Implicitly replaces the existing job record with the new instance
- cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
+ cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
+ "job rescheduled by app");
} else {
startTrackingJobLocked(jobStatus, null);
}
@@ -1105,7 +1109,10 @@
final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
for (int i=0; i<jobsForUser.size(); i++) {
JobStatus toRemove = jobsForUser.get(i);
- cancelJobImplLocked(toRemove, null, "user removed");
+ // By the time we get here, the process should have already been stopped, so the
+ // app wouldn't get the stop reason, so just put USER instead of UNINSTALL.
+ cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
+ "user removed");
}
}
}
@@ -1117,7 +1124,8 @@
}
}
- void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
+ void cancelJobsForPackageAndUid(String pkgName, int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
if ("android".equals(pkgName)) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
return;
@@ -1127,7 +1135,7 @@
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
if (job.getSourcePackageName().equals(pkgName)) {
- cancelJobImplLocked(job, null, reason);
+ cancelJobImplLocked(job, null, reason, debugReason);
}
}
}
@@ -1137,10 +1145,11 @@
* Entry point from client to cancel all jobs originating from their uid.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
- * @param uid Uid to check against for removal of a job.
*
+ * @param uid Uid to check against for removal of a job.
*/
- public boolean cancelJobsForUid(int uid, String reason) {
+ public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return false;
@@ -1151,7 +1160,7 @@
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
- cancelJobImplLocked(toRemove, null, reason);
+ cancelJobImplLocked(toRemove, null, reason, debugReason);
jobsCanceled = true;
}
}
@@ -1162,15 +1171,17 @@
* Entry point from client to cancel the job corresponding to the jobId provided.
* This will remove the job from the master list, and cancel the job if it was staged for
* execution or being executed.
- * @param uid Uid of the calling client.
+ *
+ * @param uid Uid of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
- public boolean cancelJob(int uid, int jobId, int callingUid) {
+ private boolean cancelJob(int uid, int jobId, int callingUid,
+ @JobParameters.StopReason int reason) {
JobStatus toCancel;
synchronized (mLock) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
- cancelJobImplLocked(toCancel, null,
+ cancelJobImplLocked(toCancel, null, reason,
"cancel() called by app, callingUid=" + callingUid
+ " uid=" + uid + " jobId=" + jobId);
}
@@ -1184,7 +1195,8 @@
* {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
* currently scheduled jobs.
*/
- private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
+ private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
+ @JobParameters.StopReason int reason, String debugReason) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked();
stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
@@ -1193,7 +1205,8 @@
mJobPackageTracker.noteNonpending(cancelled);
}
// Cancel if running.
- stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
+ stopJobOnServiceContextLocked(cancelled, reason, JobParameters.REASON_CANCELED,
+ debugReason);
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
@@ -1232,7 +1245,8 @@
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null && !executing.canRunInDoze()) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
+ jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_DEVICE_IDLE,
"cancelled due to doze");
}
}
@@ -1430,7 +1444,8 @@
if (DEBUG) {
Slog.v(TAG, " replacing " + oldJob + " with " + newJob);
}
- cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation");
+ cancelJobImplLocked(oldJob, newJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+ "deferred rtc calculation");
}
}
};
@@ -1550,12 +1565,13 @@
return removed;
}
- private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
+ private boolean stopJobOnServiceContextLocked(JobStatus job,
+ @JobParameters.StopReason int reason, int legacyReason, String debugReason) {
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
- jsc.cancelExecutingJobLocked(reason, debugReason);
+ jsc.cancelExecutingJobLocked(reason, legacyReason, debugReason);
return true;
}
}
@@ -1880,7 +1896,7 @@
queueReadyJobsForExecutionLocked();
break;
case MSG_STOP_JOB:
- cancelJobImplLocked((JobStatus) message.obj, null,
+ cancelJobImplLocked((JobStatus) message.obj, null, message.arg1,
"app no longer allowed to run");
break;
@@ -1895,7 +1911,9 @@
final boolean disabled = message.arg2 != 0;
updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (disabled) {
- cancelJobsForUid(uid, "uid gone");
+ cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ "uid gone");
}
synchronized (mLock) {
mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1913,7 +1931,9 @@
final int uid = message.arg1;
final boolean disabled = message.arg2 != 0;
if (disabled) {
- cancelJobsForUid(uid, "app uid idle");
+ cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ "app uid idle");
}
synchronized (mLock) {
mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1965,10 +1985,12 @@
if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX
&& !running.areDynamicConstraintsSatisfied()) {
serviceContext.cancelExecutingJobLocked(
+ running.getStopReason(),
JobParameters.REASON_RESTRICTED_BUCKET,
"cancelled due to restricted bucket");
} else {
serviceContext.cancelExecutingJobLocked(
+ running.getStopReason(),
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
"cancelled due to unsatisfied constraints");
}
@@ -1977,7 +1999,9 @@
if (restriction != null) {
final int reason = restriction.getReason();
serviceContext.cancelExecutingJobLocked(reason,
- "restricted due to " + JobParameters.getReasonCodeDescription(reason));
+ restriction.getLegacyReason(),
+ "restricted due to " + JobParameters.getLegacyReasonCodeDescription(
+ reason));
}
}
}
@@ -2058,15 +2082,14 @@
@Override
public void accept(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
- try {
- if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
- job.getJob().getService().getPackageName())) {
- Slog.w(TAG, "Aborting job " + job.getUid() + ":"
- + job.getJob().toString() + " -- package not allowed to start");
- mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
- return;
- }
- } catch (RemoteException e) {
+ if (mActivityManagerInternal.isAppStartModeDisabled(job.getUid(),
+ job.getJob().getService().getPackageName())) {
+ Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ + job.getJob().toString() + " -- package not allowed to start");
+ mHandler.obtainMessage(MSG_STOP_JOB,
+ JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job)
+ .sendToTarget();
+ return;
}
final boolean shouldForceBatchJob;
@@ -2164,6 +2187,10 @@
*/
@VisibleForTesting
boolean isReadyToBeExecutedLocked(JobStatus job) {
+ return isReadyToBeExecutedLocked(job, true);
+ }
+
+ boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) {
final boolean jobReady = job.isReady();
if (DEBUG) {
@@ -2202,7 +2229,7 @@
}
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()
@@ -2276,7 +2303,7 @@
if (restriction != null) {
if (DEBUG) {
Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
- + " restricted due to " + restriction.getReason());
+ + " restricted due to " + restriction.getLegacyReason());
}
return false;
}
@@ -2367,8 +2394,9 @@
}
@Override
- public void cancelJobsForUid(int uid, String reason) {
- JobSchedulerService.this.cancelJobsForUid(uid, reason);
+ public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
+ String debugReason) {
+ JobSchedulerService.this.cancelJobsForUid(uid, reason, debugReason);
}
@Override
@@ -2706,6 +2734,7 @@
final long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP,
"cancelAll() called by app, callingUid=" + uid);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2718,7 +2747,8 @@
final long ident = Binder.clearCallingIdentity();
try {
- JobSchedulerService.this.cancelJob(uid, jobId, uid);
+ JobSchedulerService.this.cancelJob(uid, jobId, uid,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2924,12 +2954,13 @@
if (!hasJobId) {
pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
- if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+ if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER,
+ "cancel shell command for package")) {
pw.println("No matching jobs found.");
}
} else {
pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
- if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
+ if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
pw.println("No matching job found.");
}
}
@@ -3164,8 +3195,9 @@
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
final JobRestriction restriction = mJobRestrictions.get(i);
if (restriction.isJobRestricted(job)) {
- final int reason = restriction.getReason();
- pw.print(" " + JobParameters.getReasonCodeDescription(reason));
+ final int reason = restriction.getLegacyReason();
+ pw.print(" ");
+ pw.print(JobParameters.getLegacyReasonCodeDescription(reason));
}
}
} else {
@@ -3430,7 +3462,7 @@
final long restrictionsToken = proto.start(
JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS);
proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON,
- restriction.getReason());
+ restriction.getLegacyReason());
proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING,
restriction.isJobRestricted(job));
proto.end(restrictionsToken);
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 9ef46df..790fae0 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 @@
/** 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 @@
mAvailable = false;
mStoppedReason = null;
mStoppedTime = 0;
+ job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
return true;
}
}
@@ -354,8 +363,9 @@
/** Called externally when a job that was scheduled for execution should be cancelled. */
@GuardedBy("mLock")
- void cancelExecutingJobLocked(int reason, @NonNull String debugReason) {
- doCancelLocked(reason, debugReason);
+ void cancelExecutingJobLocked(@JobParameters.StopReason int reason,
+ int legacyStopReason, @NonNull String debugReason) {
+ doCancelLocked(reason, legacyStopReason, debugReason);
}
int getPreferredUid() {
@@ -387,7 +397,8 @@
&& (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
&& (!matchJobId || jobId == executing.getJobId())) {
if (mVerb == VERB_EXECUTING) {
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+ mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.REASON_TIMEOUT, reason);
sendStopMessageLocked("force timeout from shell");
return true;
}
@@ -614,7 +625,8 @@
}
@GuardedBy("mLock")
- private void doCancelLocked(int stopReasonCode, @Nullable String debugReason) {
+ private void doCancelLocked(@JobParameters.StopReason int stopReasonCode, int legacyStopReason,
+ @Nullable String debugReason) {
if (mVerb == VERB_FINISHED) {
if (DEBUG) {
Slog.d(TAG,
@@ -622,8 +634,21 @@
}
return;
}
- mParams.setStopReason(stopReasonCode, debugReason);
- if (stopReasonCode == JobParameters.REASON_PREEMPT) {
+ 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() :
NO_PREFERRED_UID;
}
@@ -774,6 +799,23 @@
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();
@@ -781,7 +823,8 @@
// Not an error - client ran out of time.
Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
+ " Sending onStop: " + getRunningJobNameLocked());
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
+ mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.REASON_TIMEOUT, "client timed out");
sendStopMessageLocked("timeout while executing");
} else {
// We've given the app the minimum execution time. See if we should stop it or
@@ -790,7 +833,11 @@
if (reason != null) {
Slog.i(TAG, "Stopping client after min execution time: "
+ getRunningJobNameLocked() + " because " + reason);
- mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
+ // Tell the developer we're stopping the job due to device state instead
+ // of timeout since all of the reasons could equate to "the system needs
+ // the resources the app is currently using."
+ mParams.setStopReason(JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.REASON_TIMEOUT, reason);
sendStopMessageLocked(reason);
} else {
Slog.i(TAG, "Letting " + getRunningJobNameLocked()
@@ -844,12 +891,12 @@
}
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
- final int stopReason = mParams.getStopReason();
- mJobPackageTracker.noteInactive(completedJob, stopReason, reason);
+ final int legacyStopReason = mParams.getLegacyStopReason();
+ mJobPackageTracker.noteInactive(completedJob, legacyStopReason, reason);
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.getSourceUid(), null, completedJob.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
- stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+ legacyStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
completedJob.hasChargingConstraint(),
completedJob.hasBatteryNotLowConstraint(),
completedJob.hasStorageNotLowConstraint(),
@@ -860,7 +907,7 @@
completedJob.hasContentTriggerConstraint());
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
- stopReason);
+ legacyStopReason);
} catch (RemoteException e) {
// Whatever.
}
@@ -878,8 +925,11 @@
mCancelled = false;
service = null;
mAvailable = true;
+ mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
+ mPendingLegacyStopReason = 0;
+ mPendingDebugStopReason = null;
removeOpTimeOutLocked();
- mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule);
+ mCompletedListener.onJobCompletedLocked(completedJob, legacyStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
@@ -964,7 +1014,16 @@
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 aa8d98c..9cd3a8f 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 @@
/**
* 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 @@
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/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index a230b23..548a1ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -210,7 +210,8 @@
jobStatus.maybeLogBucketMismatch();
}
boolean didChange =
- jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun);
+ jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun,
+ !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
didChange |= jobStatus.setUidActive(isActive);
return didChange;
}
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 6e542f3..df21d75 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 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 @@
}
}
+ 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 @@
}
// 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 @@
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 @@
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 bad8dc1..8d999e1 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
@@ -24,11 +24,13 @@
import android.app.AppGlobals;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
import android.net.Network;
+import android.net.NetworkRequest;
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -37,6 +39,7 @@
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
+import android.util.Range;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -54,6 +57,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.function.Predicate;
/**
@@ -324,6 +328,12 @@
/** 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;
@@ -353,6 +363,9 @@
*/
private long mLastFailedRunTime;
+ /** Whether or not the app is background restricted by the user (FAS). */
+ private boolean mIsUserBgRestricted;
+
/**
* Transient: when a job is inflated from disk before we have a reliable RTC clock time,
* we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
@@ -409,6 +422,9 @@
/** The job's dynamic requirements have been satisfied. */
private boolean mReadyDynamicSatisfied;
+ /** The reason a job most recently went from ready to not ready. */
+ private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+
/** Provide a handle to the service that this job will be run on. */
public int getServiceToken() {
return callingUid;
@@ -520,8 +536,15 @@
// Later, when we check if a given network satisfies the required
// network, we need to know the UID that is requesting it, so push
// our source UID into place.
- job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
+ final JobInfo.Builder builder = new JobInfo.Builder(job);
+ final NetworkRequest.Builder requestBuilder =
+ new NetworkRequest.Builder(job.getRequiredNetwork());
+ requestBuilder.setUids(
+ Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
+ builder.setRequiredNetwork(requestBuilder.build());
+ job = builder.build();
}
+
final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
mHasMediaBackupExemption = !job.hasLateConstraint() && exemptedMediaUrisOnly
&& requiresNetwork && this.sourcePackageName.equals(jsi.getMediaBackupPackage());
@@ -1042,6 +1065,11 @@
mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
}
+ @JobParameters.StopReason
+ public int getStopReason() {
+ return mReasonReadyToUnready;
+ }
+
/**
* Return the fractional position of "now" within the "run time" window of
* this job.
@@ -1100,18 +1128,19 @@
*/
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. */
@@ -1172,7 +1201,9 @@
}
/** @return true if the constraint was changed, false otherwise. */
- boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state) {
+ boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state,
+ boolean isUserBgRestricted) {
+ mIsUserBgRestricted = isUserBgRestricted;
if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, nowElapsed, state)) {
// The constraint was changed. Update the ready flag.
mReadyNotRestrictedInBg = state;
@@ -1226,6 +1257,7 @@
"Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for "
+ toShortString());
}
+ final boolean wasReady = !state && isReady();
satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
mReadyDynamicSatisfied = mDynamicConstraints != 0
@@ -1244,9 +1276,81 @@
mConstraintChangeHistoryIndex =
(mConstraintChangeHistoryIndex + 1) % NUM_CONSTRAINT_CHANGE_HISTORY;
+ // Can't use isReady() directly since "cache booleans" haven't updated yet.
+ final boolean isReady = readinessStatusWithConstraint(constraint, state);
+ if (wasReady && !isReady) {
+ mReasonReadyToUnready = constraintToStopReason(constraint);
+ } else if (!wasReady && isReady) {
+ mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
+ }
+
return true;
}
+ @JobParameters.StopReason
+ private int constraintToStopReason(int constraint) {
+ switch (constraint) {
+ case CONSTRAINT_BATTERY_NOT_LOW:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_CHARGING:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_CHARGING;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_CONNECTIVITY:
+ return JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY;
+ case CONSTRAINT_IDLE:
+ if ((requiredConstraints & constraint) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobParameters.STOP_REASON_CONSTRAINT_DEVICE_IDLE;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobParameters.STOP_REASON_APP_STANDBY;
+ case CONSTRAINT_STORAGE_NOT_LOW:
+ return JobParameters.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+
+ case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
+ // The BACKGROUND_NOT_RESTRICTED constraint could be dissatisfied either because
+ // the app is background restricted, or because we're restricting background work
+ // in battery saver. Assume that background restriction is the reason apps that
+ // are background restricted have their jobs stopped, and battery saver otherwise.
+ // This has the benefit of being consistent for background restricted apps
+ // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+ // battery saver state.
+ if (mIsUserBgRestricted) {
+ return JobParameters.STOP_REASON_BACKGROUND_RESTRICTION;
+ }
+ return JobParameters.STOP_REASON_DEVICE_STATE;
+ case CONSTRAINT_DEVICE_NOT_DOZING:
+ return JobParameters.STOP_REASON_DEVICE_STATE;
+
+ case CONSTRAINT_WITHIN_QUOTA:
+ case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
+ return JobParameters.STOP_REASON_QUOTA;
+
+ // These should never be stop reasons since they can never go from true to false.
+ case CONSTRAINT_CONTENT_TRIGGER:
+ case CONSTRAINT_DEADLINE:
+ case CONSTRAINT_TIMING_DELAY:
+ default:
+ Slog.wtf(TAG, "Unsupported constraint (" + constraint + ") --stop reason mapping");
+ return JobParameters.STOP_REASON_UNDEFINED;
+ }
+ }
+
boolean isConstraintSatisfied(int constraint) {
return (satisfiedConstraints&constraint) != 0;
}
@@ -1330,33 +1434,42 @@
* granted, based on its requirements.
*/
boolean wouldBeReadyWithConstraint(int constraint) {
+ return readinessStatusWithConstraint(constraint, true);
+ }
+
+ private boolean readinessStatusWithConstraint(int constraint, boolean value) {
boolean oldValue = false;
int satisfied = mSatisfiedConstraintsOfInterest;
switch (constraint) {
case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
oldValue = mReadyNotRestrictedInBg;
- mReadyNotRestrictedInBg = true;
+ mReadyNotRestrictedInBg = value;
break;
case CONSTRAINT_DEADLINE:
oldValue = mReadyDeadlineSatisfied;
- mReadyDeadlineSatisfied = true;
+ mReadyDeadlineSatisfied = value;
break;
case CONSTRAINT_DEVICE_NOT_DOZING:
oldValue = mReadyNotDozing;
- mReadyNotDozing = true;
+ mReadyNotDozing = value;
break;
case CONSTRAINT_WITHIN_QUOTA:
oldValue = mReadyWithinQuota;
- mReadyWithinQuota = true;
+ mReadyWithinQuota = value;
break;
case CONSTRAINT_WITHIN_EXPEDITED_QUOTA:
oldValue = mReadyWithinExpeditedQuota;
- mReadyWithinExpeditedQuota = true;
+ mReadyWithinExpeditedQuota = value;
break;
default:
- satisfied |= constraint;
+ if (value) {
+ satisfied |= constraint;
+ } else {
+ satisfied &= ~constraint;
+ }
mReadyDynamicSatisfied = mDynamicConstraints != 0
&& mDynamicConstraints == (satisfied & mDynamicConstraints);
+
break;
}
@@ -1926,7 +2039,10 @@
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();
@@ -2065,9 +2181,6 @@
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);
@@ -2156,10 +2269,6 @@
}
}
- 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 2b79969..91189e4 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 @@
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 @@
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 @@
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 @@
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/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index ac59f95..2962b10 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -17,6 +17,7 @@
package com.android.server.job.restrictions;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
@@ -26,9 +27,8 @@
/**
* Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
* should be scheduled or not based on the state of the system/device.
- * Every restriction is associated with exactly one reason (from {@link
- * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link
- * #getReason()}.
+ * Every restriction is associated with exactly one stop reason, which could be retrieved using
+ * {@link #getReason()} (and the legacy reason via {@link #getLegacyReason()}).
* Note, that this is not taken into account for the jobs that have priority
* {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
*/
@@ -36,10 +36,13 @@
final JobSchedulerService mService;
private final int mReason;
+ private final int mLegacyReason;
- JobRestriction(JobSchedulerService service, int reason) {
+ JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
+ int legacyReason) {
mService = service;
mReason = reason;
+ mLegacyReason = legacyReason;
}
/**
@@ -66,7 +69,12 @@
public abstract void dumpConstants(ProtoOutputStream proto);
/** @return reason code for the Restriction. */
+ @JobParameters.StopReason
public final int getReason() {
return mReason;
}
+
+ public final int getLegacyReason() {
+ return mLegacyReason;
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index 954a5b8..8b699e9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -34,7 +34,7 @@
private PowerManager mPowerManager;
public ThermalStatusRestriction(JobSchedulerService service) {
- super(service, JobParameters.REASON_DEVICE_THERMAL);
+ super(service, JobParameters.STOP_REASON_DEVICE_STATE, JobParameters.REASON_DEVICE_THERMAL);
}
@Override
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 24f7b37..a62e258 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 @@
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 80698f7..bebf019 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -69,10 +69,12 @@
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 8bdca76..cff422d 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.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.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 @@
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 @@
MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));
nativeSubmitMetrics(
+ // TODO: mLogSessionId,
mParserName,
mCreatedByName,
String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
@@ -1341,6 +1345,15 @@
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 @@
// 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 7acad18..2e2147f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -55,7 +55,9 @@
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 @@
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 @@
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 @@
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
@@ -1729,42 +1733,66 @@
field @Deprecated public static final int secondary_text_dark_nodisable = 17170438; // 0x1060006
field @Deprecated public static final int secondary_text_light = 17170439; // 0x1060007
field @Deprecated public static final int secondary_text_light_nodisable = 17170440; // 0x1060008
- field public static final int system_neutral_0 = 17170485; // 0x1060035
- field public static final int system_neutral_100 = 17170487; // 0x1060037
- field public static final int system_neutral_1000 = 17170496; // 0x1060040
- field public static final int system_neutral_200 = 17170488; // 0x1060038
- field public static final int system_neutral_300 = 17170489; // 0x1060039
- field public static final int system_neutral_400 = 17170490; // 0x106003a
- field public static final int system_neutral_50 = 17170486; // 0x1060036
- field public static final int system_neutral_500 = 17170491; // 0x106003b
- field public static final int system_neutral_600 = 17170492; // 0x106003c
- field public static final int system_neutral_700 = 17170493; // 0x106003d
- field public static final int system_neutral_800 = 17170494; // 0x106003e
- field public static final int system_neutral_900 = 17170495; // 0x106003f
- field public static final int system_primary_0 = 17170461; // 0x106001d
- field public static final int system_primary_100 = 17170463; // 0x106001f
- field public static final int system_primary_1000 = 17170472; // 0x1060028
- field public static final int system_primary_200 = 17170464; // 0x1060020
- field public static final int system_primary_300 = 17170465; // 0x1060021
- field public static final int system_primary_400 = 17170466; // 0x1060022
- field public static final int system_primary_50 = 17170462; // 0x106001e
- field public static final int system_primary_500 = 17170467; // 0x1060023
- field public static final int system_primary_600 = 17170468; // 0x1060024
- field public static final int system_primary_700 = 17170469; // 0x1060025
- field public static final int system_primary_800 = 17170470; // 0x1060026
- field public static final int system_primary_900 = 17170471; // 0x1060027
- field public static final int system_secondary_0 = 17170473; // 0x1060029
- field public static final int system_secondary_100 = 17170475; // 0x106002b
- field public static final int system_secondary_1000 = 17170484; // 0x1060034
- field public static final int system_secondary_200 = 17170476; // 0x106002c
- field public static final int system_secondary_300 = 17170477; // 0x106002d
- field public static final int system_secondary_400 = 17170478; // 0x106002e
- field public static final int system_secondary_50 = 17170474; // 0x106002a
- field public static final int system_secondary_500 = 17170479; // 0x106002f
- field public static final int system_secondary_600 = 17170480; // 0x1060030
- field public static final int system_secondary_700 = 17170481; // 0x1060031
- field public static final int system_secondary_800 = 17170482; // 0x1060032
- field public static final int system_secondary_900 = 17170483; // 0x1060033
+ field public static final int system_accent1_0 = 17170485; // 0x1060035
+ field public static final int system_accent1_100 = 17170487; // 0x1060037
+ field public static final int system_accent1_1000 = 17170496; // 0x1060040
+ field public static final int system_accent1_200 = 17170488; // 0x1060038
+ field public static final int system_accent1_300 = 17170489; // 0x1060039
+ field public static final int system_accent1_400 = 17170490; // 0x106003a
+ field public static final int system_accent1_50 = 17170486; // 0x1060036
+ field public static final int system_accent1_500 = 17170491; // 0x106003b
+ field public static final int system_accent1_600 = 17170492; // 0x106003c
+ field public static final int system_accent1_700 = 17170493; // 0x106003d
+ field public static final int system_accent1_800 = 17170494; // 0x106003e
+ field public static final int system_accent1_900 = 17170495; // 0x106003f
+ field public static final int system_accent2_0 = 17170497; // 0x1060041
+ field public static final int system_accent2_100 = 17170499; // 0x1060043
+ field public static final int system_accent2_1000 = 17170508; // 0x106004c
+ field public static final int system_accent2_200 = 17170500; // 0x1060044
+ field public static final int system_accent2_300 = 17170501; // 0x1060045
+ field public static final int system_accent2_400 = 17170502; // 0x1060046
+ field public static final int system_accent2_50 = 17170498; // 0x1060042
+ field public static final int system_accent2_500 = 17170503; // 0x1060047
+ field public static final int system_accent2_600 = 17170504; // 0x1060048
+ field public static final int system_accent2_700 = 17170505; // 0x1060049
+ field public static final int system_accent2_800 = 17170506; // 0x106004a
+ field public static final int system_accent2_900 = 17170507; // 0x106004b
+ field public static final int system_accent3_0 = 17170509; // 0x106004d
+ field public static final int system_accent3_100 = 17170511; // 0x106004f
+ field public static final int system_accent3_1000 = 17170520; // 0x1060058
+ field public static final int system_accent3_200 = 17170512; // 0x1060050
+ field public static final int system_accent3_300 = 17170513; // 0x1060051
+ field public static final int system_accent3_400 = 17170514; // 0x1060052
+ field public static final int system_accent3_50 = 17170510; // 0x106004e
+ field public static final int system_accent3_500 = 17170515; // 0x1060053
+ field public static final int system_accent3_600 = 17170516; // 0x1060054
+ field public static final int system_accent3_700 = 17170517; // 0x1060055
+ field public static final int system_accent3_800 = 17170518; // 0x1060056
+ field public static final int system_accent3_900 = 17170519; // 0x1060057
+ field public static final int system_neutral1_0 = 17170461; // 0x106001d
+ field public static final int system_neutral1_100 = 17170463; // 0x106001f
+ field public static final int system_neutral1_1000 = 17170472; // 0x1060028
+ field public static final int system_neutral1_200 = 17170464; // 0x1060020
+ field public static final int system_neutral1_300 = 17170465; // 0x1060021
+ field public static final int system_neutral1_400 = 17170466; // 0x1060022
+ field public static final int system_neutral1_50 = 17170462; // 0x106001e
+ field public static final int system_neutral1_500 = 17170467; // 0x1060023
+ field public static final int system_neutral1_600 = 17170468; // 0x1060024
+ field public static final int system_neutral1_700 = 17170469; // 0x1060025
+ field public static final int system_neutral1_800 = 17170470; // 0x1060026
+ field public static final int system_neutral1_900 = 17170471; // 0x1060027
+ field public static final int system_neutral2_0 = 17170473; // 0x1060029
+ field public static final int system_neutral2_100 = 17170475; // 0x106002b
+ field public static final int system_neutral2_1000 = 17170484; // 0x1060034
+ field public static final int system_neutral2_200 = 17170476; // 0x106002c
+ field public static final int system_neutral2_300 = 17170477; // 0x106002d
+ field public static final int system_neutral2_400 = 17170478; // 0x106002e
+ field public static final int system_neutral2_50 = 17170474; // 0x106002a
+ field public static final int system_neutral2_500 = 17170479; // 0x106002f
+ field public static final int system_neutral2_600 = 17170480; // 0x1060030
+ field public static final int system_neutral2_700 = 17170481; // 0x1060031
+ field public static final int system_neutral2_800 = 17170482; // 0x1060032
+ field public static final int system_neutral2_900 = 17170483; // 0x1060033
field public static final int tab_indicator_text = 17170441; // 0x1060009
field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -5656,6 +5684,7 @@
field public static final String EXTRA_PEOPLE_LIST = "android.people.list";
field public static final String EXTRA_PICTURE = "android.picture";
field public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = "android.pictureContentDescription";
+ field public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
field public static final String EXTRA_PROGRESS = "android.progress";
field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
@@ -5804,8 +5833,9 @@
method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.Bitmap);
method @NonNull public android.app.Notification.BigPictureStyle bigLargeIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.Bitmap);
- method @NonNull public android.app.Notification.BigPictureStyle bigPictureContentDescription(@Nullable CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle bigPicture(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.app.Notification.BigPictureStyle setBigContentTitle(@Nullable CharSequence);
+ method @NonNull public android.app.Notification.BigPictureStyle setContentDescription(@Nullable CharSequence);
method @NonNull public android.app.Notification.BigPictureStyle setSummaryText(@Nullable CharSequence);
method @NonNull public android.app.Notification.BigPictureStyle showBigPictureWhenCollapsed(boolean);
}
@@ -6290,13 +6320,13 @@
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();
@@ -6983,6 +7013,7 @@
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);
@@ -7037,6 +7068,7 @@
}
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);
@@ -7155,8 +7187,10 @@
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);
@@ -7164,7 +7198,6 @@
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;
@@ -7219,6 +7252,7 @@
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);
@@ -7238,7 +7272,6 @@
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);
@@ -7944,6 +7977,7 @@
method @NonNull public android.os.PersistableBundle getExtras();
method public int getJobId();
method @Nullable public android.net.Network getNetwork();
+ method public int getStopReason();
method @NonNull public android.os.Bundle getTransientExtras();
method @Nullable public String[] getTriggeredContentAuthorities();
method @Nullable public android.net.Uri[] getTriggeredContentUris();
@@ -7952,6 +7986,21 @@
method public boolean isOverrideDeadlineExpired();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobParameters> CREATOR;
+ field public static final int STOP_REASON_APP_STANDBY = 12; // 0xc
+ field public static final int STOP_REASON_BACKGROUND_RESTRICTION = 11; // 0xb
+ field public static final int STOP_REASON_CANCELLED_BY_APP = 1; // 0x1
+ field public static final int STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5; // 0x5
+ field public static final int STOP_REASON_CONSTRAINT_CHARGING = 6; // 0x6
+ field public static final int STOP_REASON_CONSTRAINT_CONNECTIVITY = 7; // 0x7
+ field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+ field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
+ field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
+ field public static final int STOP_REASON_PREEMPT = 2; // 0x2
+ field public static final int STOP_REASON_QUOTA = 10; // 0xa
+ field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
+ field public static final int STOP_REASON_TIMEOUT = 3; // 0x3
+ field public static final int STOP_REASON_UNDEFINED = 0; // 0x0
+ field public static final int STOP_REASON_USER = 13; // 0xd
}
public abstract class JobScheduler {
@@ -8954,6 +9003,7 @@
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);
@@ -8967,6 +9017,8 @@
field public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
field public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
field public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
+ field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
+ field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
field public static final int BOND_BONDED = 12; // 0xc
field public static final int BOND_BONDING = 11; // 0xb
field public static final int BOND_NONE = 10; // 0xa
@@ -11815,7 +11867,7 @@
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();
@@ -11870,6 +11922,9 @@
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;
@@ -13324,7 +13379,8 @@
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);
@@ -17810,6 +17866,7 @@
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;
@@ -17819,6 +17876,7 @@
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;
@@ -17838,9 +17896,11 @@
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;
@@ -17850,13 +17910,17 @@
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;
@@ -18153,8 +18217,10 @@
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
@@ -18172,6 +18238,8 @@
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
@@ -18301,6 +18369,7 @@
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;
@@ -18404,6 +18473,8 @@
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;
@@ -18578,6 +18649,7 @@
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();
@@ -18586,6 +18658,7 @@
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);
@@ -19756,7 +19829,8 @@
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();
@@ -19772,6 +19846,7 @@
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);
@@ -20229,6 +20304,7 @@
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);
@@ -20251,6 +20327,7 @@
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();
@@ -20270,6 +20347,7 @@
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);
@@ -20279,6 +20357,7 @@
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);
@@ -20317,6 +20396,10 @@
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";
@@ -20530,6 +20613,7 @@
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();
@@ -20552,6 +20636,7 @@
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);
@@ -20667,6 +20752,7 @@
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);
@@ -20704,6 +20790,7 @@
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);
@@ -21258,7 +21345,7 @@
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;
@@ -21284,8 +21371,9 @@
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;
@@ -21297,14 +21385,16 @@
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
@@ -21324,6 +21414,7 @@
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
@@ -21414,6 +21505,10 @@
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);
}
@@ -21427,6 +21522,11 @@
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);
@@ -21752,6 +21852,7 @@
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
}
@@ -21909,7 +22010,7 @@
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();
@@ -22114,6 +22215,11 @@
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();
@@ -22146,6 +22252,7 @@
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);
@@ -22167,6 +22274,7 @@
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
@@ -22770,6 +22878,7 @@
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();
@@ -22792,6 +22901,7 @@
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;
@@ -24418,11 +24528,18 @@
public abstract class Event {
ctor protected Event(long);
+ method @NonNull public android.os.Bundle getMetricsBundle();
method @IntRange(from=0xffffffff) public long getTimeSinceCreatedMillis();
}
+ 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 {
method @NonNull public android.media.metrics.PlaybackSession createPlaybackSession();
+ method @NonNull public android.media.metrics.RecordingSession createRecordingSession();
field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
}
@@ -24437,32 +24554,57 @@
field public static final int NETWORK_TYPE_5G_NSA = 7; // 0x7
field public static final int NETWORK_TYPE_5G_SA = 8; // 0x8
field public static final int NETWORK_TYPE_ETHERNET = 3; // 0x3
- field public static final int NETWORK_TYPE_NONE = 0; // 0x0
+ field public static final int NETWORK_TYPE_OFFLINE = 9; // 0x9
field public static final int NETWORK_TYPE_OTHER = 1; // 0x1
+ field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0
field public static final int NETWORK_TYPE_WIFI = 2; // 0x2
}
public static final class NetworkEvent.Builder {
ctor public NetworkEvent.Builder();
method @NonNull public android.media.metrics.NetworkEvent build();
+ method @NonNull public android.media.metrics.NetworkEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
method @NonNull public android.media.metrics.NetworkEvent.Builder setNetworkType(int);
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();
method @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) public int getSubErrorCode();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackErrorEvent> CREATOR;
- field public static final int ERROR_CODE_OTHER = 1; // 0x1
- field public static final int ERROR_CODE_RUNTIME = 2; // 0x2
- field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_AUDIOTRACK_INIT = 17; // 0x11
+ field public static final int ERROR_AUDIOTRACK_OTHER = 19; // 0x13
+ field public static final int ERROR_AUDIOTRACK_WRITE = 18; // 0x12
+ field public static final int ERROR_DECODER_DECODE = 14; // 0xe
+ field public static final int ERROR_DECODER_INIT = 13; // 0xd
+ field public static final int ERROR_DECODER_OOM = 15; // 0xf
+ field public static final int ERROR_DECODER_OTHER = 16; // 0x10
+ field public static final int ERROR_DRM_CONTENT_ERROR = 28; // 0x1c
+ field public static final int ERROR_DRM_DISALLOWED = 26; // 0x1a
+ field public static final int ERROR_DRM_LICENSE_ERROR = 25; // 0x19
+ field public static final int ERROR_DRM_OTHER = 30; // 0x1e
+ field public static final int ERROR_DRM_PROVISIONING_FAILED = 24; // 0x18
+ field public static final int ERROR_DRM_REVOKED = 29; // 0x1d
+ field public static final int ERROR_DRM_SYSTEM_ERROR = 27; // 0x1b
+ field public static final int ERROR_DRM_UNAVAILABLE = 23; // 0x17
+ field public static final int ERROR_MEDIA_MANIFEST = 10; // 0xa
+ field public static final int ERROR_MEDIA_OTHER = 12; // 0xc
+ field public static final int ERROR_MEDIA_PARSER = 11; // 0xb
+ field public static final int ERROR_NETWORK_BAD_STATUS = 5; // 0x5
+ field public static final int ERROR_NETWORK_CLOSED = 8; // 0x8
+ field public static final int ERROR_NETWORK_CONNECT = 4; // 0x4
+ field public static final int ERROR_NETWORK_DNS = 6; // 0x6
+ field public static final int ERROR_NETWORK_OFFLINE = 3; // 0x3
+ field public static final int ERROR_NETWORK_OTHER = 9; // 0x9
+ field public static final int ERROR_NETWORK_TIMEOUT = 7; // 0x7
+ field public static final int ERROR_OTHER = 1; // 0x1
+ field public static final int ERROR_PLAYER_BEHIND_LIVE_WINDOW = 21; // 0x15
+ field public static final int ERROR_PLAYER_OTHER = 22; // 0x16
+ field public static final int ERROR_PLAYER_REMOTE = 20; // 0x14
+ field public static final int ERROR_RUNTIME = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
}
public static final class PlaybackErrorEvent.Builder {
@@ -24470,6 +24612,7 @@
method @NonNull public android.media.metrics.PlaybackErrorEvent build();
method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setErrorCode(int);
method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setException(@NonNull Exception);
+ method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setSubErrorCode(@IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.media.metrics.PlaybackErrorEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
}
@@ -24478,10 +24621,12 @@
method public int describeContents();
method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getAudioUnderrunCount();
method public int getContentType();
+ method @NonNull public byte[] getDrmSessionId();
method public int getDrmType();
method @NonNull public long[] getExperimentIds();
method @IntRange(from=0xffffffff) public long getLocalBytesRead();
method @IntRange(from=0xffffffff) public long getMediaDurationMillis();
+ method @NonNull public android.os.Bundle getMetricsBundle();
method @IntRange(from=0xffffffff) public long getNetworkBytesRead();
method @IntRange(from=0xffffffff) public long getNetworkTransferDurationMillis();
method public int getPlaybackType();
@@ -24492,9 +24637,10 @@
method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesDropped();
method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getVideoFramesPlayed();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CONTENT_TYPE_AD = 1; // 0x1
- field public static final int CONTENT_TYPE_MAIN = 0; // 0x0
- field public static final int CONTENT_TYPE_OTHER = 2; // 0x2
+ field public static final int CONTENT_TYPE_AD = 2; // 0x2
+ field public static final int CONTENT_TYPE_MAIN = 1; // 0x1
+ field public static final int CONTENT_TYPE_OTHER = 3; // 0x3
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.PlaybackMetrics> CREATOR;
field public static final int DRM_TYPE_CLEARKEY = 6; // 0x6
field public static final int DRM_TYPE_NONE = 0; // 0x0
@@ -24503,9 +24649,10 @@
field public static final int DRM_TYPE_WIDEVINE_L1 = 3; // 0x3
field public static final int DRM_TYPE_WIDEVINE_L3 = 4; // 0x4
field public static final int DRM_TYPE_WV_L3_FALLBACK = 5; // 0x5
- field public static final int PLAYBACK_TYPE_LIVE = 1; // 0x1
- field public static final int PLAYBACK_TYPE_OTHER = 2; // 0x2
- field public static final int PLAYBACK_TYPE_VOD = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_LIVE = 2; // 0x2
+ field public static final int PLAYBACK_TYPE_OTHER = 3; // 0x3
+ field public static final int PLAYBACK_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_VOD = 1; // 0x1
field public static final int STREAM_SOURCE_DEVICE = 2; // 0x2
field public static final int STREAM_SOURCE_MIXED = 3; // 0x3
field public static final int STREAM_SOURCE_NETWORK = 1; // 0x1
@@ -24524,9 +24671,11 @@
method @NonNull public android.media.metrics.PlaybackMetrics build();
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setAudioUnderrunCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setContentType(int);
+ method @NonNull public android.media.metrics.PlaybackMetrics.Builder setDrmSessionId(@NonNull byte[]);
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setDrmType(int);
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setLocalBytesRead(@IntRange(from=0xffffffff) long);
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setMediaDurationMillis(@IntRange(from=0xffffffff) long);
+ method @NonNull public android.media.metrics.PlaybackMetrics.Builder setMetricsBundle(@NonNull android.os.Bundle);
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkBytesRead(@IntRange(from=0xffffffff) long);
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setNetworkTransferDurationMillis(@IntRange(from=0xffffffff) long);
method @NonNull public android.media.metrics.PlaybackMetrics.Builder setPlaybackType(int);
@@ -24540,7 +24689,7 @@
public final class PlaybackSession implements java.lang.AutoCloseable {
method public void close();
- method @NonNull public String getId();
+ method @NonNull public android.media.metrics.LogSessionId getSessionId();
method public void reportNetworkEvent(@NonNull android.media.metrics.NetworkEvent);
method public void reportPlaybackErrorEvent(@NonNull android.media.metrics.PlaybackErrorEvent);
method public void reportPlaybackMetrics(@NonNull android.media.metrics.PlaybackMetrics);
@@ -24573,13 +24722,19 @@
public static final class PlaybackStateEvent.Builder {
ctor public PlaybackStateEvent.Builder();
method @NonNull public android.media.metrics.PlaybackStateEvent build();
+ method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setState(int);
method @NonNull public android.media.metrics.PlaybackStateEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
}
+ public final class RecordingSession implements java.lang.AutoCloseable {
+ method public void close();
+ method @NonNull public android.media.metrics.LogSessionId getSessionId();
+ }
+
public final class TrackChangeEvent extends android.media.metrics.Event implements android.os.Parcelable {
- ctor public TrackChangeEvent(int, int, @Nullable String, @Nullable String, @Nullable String, int, long, int, @Nullable String, @Nullable String, int, int, int, int);
method public int describeContents();
+ method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getAudioSampleRate();
method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getBitrate();
method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getChannelCount();
method @Nullable public String getCodecName();
@@ -24588,10 +24743,10 @@
method @Nullable public String getLanguage();
method @Nullable public String getLanguageRegion();
method @Nullable public String getSampleMimeType();
- method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getSampleRate();
method public int getTrackChangeReason();
method public int getTrackState();
method public int getTrackType();
+ method @FloatRange(from=0xffffffff, to=java.lang.Float.MAX_VALUE) public float getVideoFrameRate();
method @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) public int getWidth();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.TrackChangeEvent> CREATOR;
@@ -24610,6 +24765,7 @@
public static final class TrackChangeEvent.Builder {
ctor public TrackChangeEvent.Builder(int);
method @NonNull public android.media.metrics.TrackChangeEvent build();
+ method @NonNull public android.media.metrics.TrackChangeEvent.Builder setAudioSampleRate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setBitrate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setChannelCount(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setCodecName(@NonNull String);
@@ -24617,11 +24773,12 @@
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setHeight(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguage(@NonNull String);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setLanguageRegion(@NonNull String);
+ method @NonNull public android.media.metrics.TrackChangeEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleMimeType(@NonNull String);
- method @NonNull public android.media.metrics.TrackChangeEvent.Builder setSampleRate(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackChangeReason(int);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setTrackState(int);
+ method @NonNull public android.media.metrics.TrackChangeEvent.Builder setVideoFrameRate(@FloatRange(from=0xffffffff, to=java.lang.Float.MAX_VALUE) float);
method @NonNull public android.media.metrics.TrackChangeEvent.Builder setWidth(@IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) int);
}
@@ -26862,7 +27019,7 @@
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);
}
}
@@ -34786,6 +34943,7 @@
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";
@@ -34814,7 +34972,7 @@
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";
@@ -35445,6 +35603,9 @@
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";
@@ -37352,6 +37513,7 @@
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";
}
@@ -37627,6 +37789,22 @@
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();
@@ -38271,6 +38449,8 @@
field public static final int REASON_CANCEL = 2; // 0x2
field public static final int REASON_CANCEL_ALL = 3; // 0x3
field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
+ field public static final int REASON_CHANNEL_REMOVED = 20; // 0x14
+ field public static final int REASON_CLEAR_DATA = 21; // 0x15
field public static final int REASON_CLICK = 1; // 0x1
field public static final int REASON_ERROR = 4; // 0x4
field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd
@@ -39107,7 +39287,7 @@
method public android.telecom.Call getParent();
method public String getRemainingPostDialSequence();
method @Nullable public android.telecom.Call.RttCall getRttCall();
- method public int getState();
+ method @Deprecated public int getState();
method public android.telecom.InCallService.VideoCall getVideoCall();
method public void handoverTo(android.telecom.PhoneAccountHandle, int, android.os.Bundle);
method public void hold();
@@ -39202,6 +39382,7 @@
method public android.net.Uri getHandle();
method public int getHandlePresentation();
method public android.os.Bundle getIntentExtras();
+ method public final int getState();
method public android.telecom.StatusHints getStatusHints();
method public int getVideoState();
method public static boolean hasProperty(int, int);
@@ -40441,6 +40622,7 @@
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";
@@ -40465,6 +40647,7 @@
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";
@@ -40630,6 +40813,7 @@
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";
}
@@ -41946,7 +42130,6 @@
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();
@@ -42047,7 +42230,8 @@
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
- method public int getDeviceToDeviceStatusSharing(int);
+ 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);
method @Nullable public int[] getSubscriptionIds(int);
@@ -42060,7 +42244,8 @@
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 setDeviceToDeviceStatusSharing(int, int);
+ 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);
method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long);
@@ -42075,8 +42260,9 @@
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
@@ -42184,7 +42370,7 @@
}
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 {
@@ -42232,7 +42418,7 @@
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
}
@@ -42283,6 +42469,7 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getNetworkSlicingConfiguration(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.data.SlicingConfig,android.telephony.TelephonyManager.SlicingException>);
method public String getNetworkSpecifier();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
+ method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
@@ -42319,6 +42506,7 @@
method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
method public boolean isConcurrentVoiceAndDataSupported();
+ method public boolean isDataCapable();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
@@ -42392,6 +42580,7 @@
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
@@ -47981,6 +48170,7 @@
}
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);
@@ -52424,9 +52614,44 @@
package android.view.translation {
+ public final class TranslationCapability implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+ method public int getState();
+ method public int getSupportedTranslationFlags();
+ method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+ method public boolean isUiTranslationEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationCapability> CREATOR;
+ field public static final int STATE_AVAILABLE_TO_DOWNLOAD = 1; // 0x1
+ field public static final int STATE_DOWNLOADING = 2; // 0x2
+ field public static final int STATE_ON_DEVICE = 3; // 0x3
+ }
+
+ public final class TranslationContext implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+ method @NonNull public android.view.translation.TranslationSpec getTargetSpec();
+ method public int getTranslationFlags();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationContext> CREATOR;
+ field public static final int FLAG_DICTIONARY_DESCRIPTION = 4; // 0x4
+ field public static final int FLAG_LOW_LATENCY = 1; // 0x1
+ field public static final int FLAG_TRANSLITERATION = 2; // 0x2
+ }
+
+ public static final class TranslationContext.Builder {
+ ctor public TranslationContext.Builder(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
+ method @NonNull public android.view.translation.TranslationContext build();
+ method @NonNull public android.view.translation.TranslationContext.Builder setTranslationFlags(int);
+ }
+
public final class TranslationManager {
- method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
- method @NonNull @WorkerThread public java.util.List<java.lang.String> getSupportedLocales();
+ 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);
}
public final class TranslationRequest implements android.os.Parcelable {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e3b7c88..5925b72 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -201,14 +201,6 @@
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 @@
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 13b83796..2f48610 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3,6 +3,7 @@
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";
@@ -65,6 +66,7 @@
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
field public static final String BRICK = "android.permission.BRICK";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
+ field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
@@ -91,6 +93,7 @@
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";
@@ -266,6 +269,7 @@
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";
@@ -309,6 +313,7 @@
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
@@ -698,6 +703,9 @@
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 {
@@ -944,6 +952,9 @@
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
@@ -1191,12 +1202,14 @@
public class RestoreSet implements android.os.Parcelable {
ctor public RestoreSet();
- ctor public RestoreSet(String, String, long);
+ ctor public RestoreSet(@Nullable String, @Nullable String, long);
+ ctor public RestoreSet(@Nullable String, @Nullable String, long, int);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.backup.RestoreSet> CREATOR;
- field public String device;
- field public String name;
+ field public final int backupTransportFlags;
+ field @Nullable public String device;
+ field @Nullable public String name;
field public long token;
}
@@ -1831,7 +1844,8 @@
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);
method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean);
@@ -1943,7 +1957,6 @@
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 {
@@ -2138,7 +2151,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.ResultStorageDescriptor> CREATOR;
}
+ public final class ScanFilter implements android.os.Parcelable {
+ method public int getAddressType();
+ method @Nullable public byte[] getIrk();
+ }
+
+ public static final class ScanFilter.Builder {
+ method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int);
+ method @NonNull public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(@NonNull String, int, @NonNull byte[]);
+ field public static final int LEN_IRK_OCTETS = 16; // 0x10
+ }
+
public final class ScanSettings implements android.os.Parcelable {
+ field public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3; // 0x3
field public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1; // 0x1
field public static final int SCAN_RESULT_TYPE_FULL = 0; // 0x0
}
@@ -2158,6 +2183,8 @@
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);
}
@@ -2424,6 +2451,7 @@
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();
@@ -2783,7 +2811,9 @@
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);
}
}
@@ -2849,18 +2879,15 @@
}
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
}
@@ -4431,6 +4458,7 @@
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();
@@ -4446,6 +4474,7 @@
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);
@@ -4804,7 +4833,7 @@
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";
}
@@ -5235,6 +5264,10 @@
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 {
@@ -5413,6 +5446,8 @@
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);
@@ -7551,9 +7586,11 @@
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
}
@@ -7761,6 +7798,7 @@
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);
@@ -7773,6 +7811,7 @@
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
@@ -7783,6 +7822,10 @@
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;
@@ -8254,9 +8297,10 @@
public class PowerExemptionManager {
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull String);
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToPermanentAllowList(@NonNull java.util.List<java.lang.String>);
- method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String, long, int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String, int, int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromAllowList(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void addToTemporaryAllowList(@NonNull String, int, @Nullable String, long);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long addToTemporaryAllowListForEvent(@NonNull String, int, @Nullable String, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public int[] getAllowListedAppIds(boolean);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromPermanentAllowList(@NonNull String);
field public static final int EVENT_MMS = 2; // 0x2
field public static final int EVENT_SMS = 1; // 0x1
field public static final int EVENT_UNSPECIFIED = 0; // 0x0
@@ -8480,6 +8524,7 @@
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();
@@ -8493,6 +8538,7 @@
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";
@@ -8506,6 +8552,7 @@
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";
}
@@ -8653,9 +8700,15 @@
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
@@ -8902,6 +8955,7 @@
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";
@@ -8918,6 +8972,7 @@
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";
@@ -8979,6 +9034,8 @@
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";
}
@@ -10243,9 +10300,10 @@
ctor public TranslationService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onConnected();
- method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, int);
+ method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationContext, int);
method public void onDisconnected();
method public abstract void onFinishTranslationSession(int);
+ method public abstract void onTranslationCapabilitiesRequest(int, int, @NonNull java.util.function.Consumer<java.util.Set<android.view.translation.TranslationCapability>>);
method public abstract void onTranslationRequest(@NonNull android.view.translation.TranslationRequest, int, @NonNull android.os.CancellationSignal, @NonNull android.service.translation.TranslationService.OnTranslationResultCallback);
field public static final String SERVICE_INTERFACE = "android.service.translation.TranslationService";
field public static final String SERVICE_META_DATA = "android.translation_service";
@@ -10304,9 +10362,9 @@
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
@@ -10329,11 +10387,13 @@
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();
}
@@ -10372,13 +10432,13 @@
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 {
@@ -10397,7 +10457,7 @@
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();
}
@@ -11055,7 +11115,8 @@
public final class DataSpecificRegistrationInfo implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
+ method @Deprecated @NonNull public android.telephony.LteVopsSupportInfo getLteVopsSupportInfo();
+ method @Nullable public android.telephony.VopsSupportInfo getVopsSupportInfo();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DataSpecificRegistrationInfo> CREATOR;
}
@@ -11100,14 +11161,16 @@
field public static final int LCE_TYPE_SECONDARY = 1; // 0x1
}
- public final class LteVopsSupportInfo implements android.os.Parcelable {
+ public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public LteVopsSupportInfo(int, int);
- method public int describeContents();
method public int getEmcBearerSupport();
method public int getVopsSupport();
- method public void writeToParcel(android.os.Parcel, int);
+ method public boolean isEmergencyServiceFallbackSupported();
+ method public boolean isEmergencyServiceSupported();
+ method public boolean isVopsSupported();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LteVopsSupportInfo> CREATOR;
- field public static final int LTE_STATUS_NOT_AVAILABLE = 1; // 0x1
+ field @Deprecated public static final int LTE_STATUS_NOT_AVAILABLE = 1; // 0x1
field public static final int LTE_STATUS_NOT_SUPPORTED = 3; // 0x3
field public static final int LTE_STATUS_SUPPORTED = 2; // 0x2
}
@@ -11197,6 +11260,29 @@
field public static final int RESULT_SUCCESS = 0; // 0x0
}
+ public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
+ ctor public NrVopsSupportInfo(int, int, int);
+ method public int getEmcSupport();
+ method public int getEmfSupport();
+ method public int getVopsSupport();
+ method public boolean isEmergencyServiceFallbackSupported();
+ method public boolean isEmergencyServiceSupported();
+ method public boolean isVopsSupported();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NrVopsSupportInfo> CREATOR;
+ field public static final int NR_STATUS_EMC_5GCN_ONLY = 1; // 0x1
+ field public static final int NR_STATUS_EMC_EUTRA_5GCN_ONLY = 2; // 0x2
+ field public static final int NR_STATUS_EMC_NOT_SUPPORTED = 0; // 0x0
+ field public static final int NR_STATUS_EMC_NR_EUTRA_5GCN = 3; // 0x3
+ field public static final int NR_STATUS_EMF_5GCN_ONLY = 1; // 0x1
+ field public static final int NR_STATUS_EMF_EUTRA_5GCN_ONLY = 2; // 0x2
+ field public static final int NR_STATUS_EMF_NOT_SUPPORTED = 0; // 0x0
+ field public static final int NR_STATUS_EMF_NR_EUTRA_5GCN = 3; // 0x3
+ field public static final int NR_STATUS_VOPS_3GPP_SUPPORTED = 1; // 0x1
+ field public static final int NR_STATUS_VOPS_NON_3GPP_SUPPORTED = 2; // 0x2
+ field public static final int NR_STATUS_VOPS_NOT_SUPPORTED = 0; // 0x0
+ }
+
public interface NumberVerificationCallback {
method public default void onCallReceived(@NonNull String);
method public default void onVerificationFailed(int);
@@ -11211,12 +11297,11 @@
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
}
@@ -11862,6 +11947,7 @@
field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
+ field public static final String CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING = "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING";
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -12023,6 +12109,16 @@
method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
}
+ public abstract class VopsSupportInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public abstract boolean equals(Object);
+ method public abstract int hashCode();
+ method public abstract boolean isEmergencyServiceFallbackSupported();
+ method public abstract boolean isEmergencyServiceSupported();
+ method public abstract boolean isVopsSupported();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.VopsSupportInfo> CREATOR;
+ }
+
}
package android.telephony.cdma {
@@ -12222,6 +12318,20 @@
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);
@@ -13240,6 +13350,7 @@
method @Nullable public android.telephony.ims.RcsContactPresenceTuple getCapabilityTuple(@NonNull String);
method @NonNull public java.util.List<android.telephony.ims.RcsContactPresenceTuple> getCapabilityTuples();
method @NonNull public android.net.Uri getContactUri();
+ method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
method public int getRequestResult();
method public int getSourceType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -13254,6 +13365,14 @@
field public static final int SOURCE_TYPE_NETWORK = 0; // 0x0
}
+ public static final class RcsContactUceCapability.OptionsBuilder {
+ ctor public RcsContactUceCapability.OptionsBuilder(@NonNull android.net.Uri);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTag(@NonNull String);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder addFeatureTags(@NonNull java.util.Set<java.lang.String>);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.OptionsBuilder setRequestResult(int);
+ }
+
public static final class RcsContactUceCapability.PresenceBuilder {
ctor public RcsContactUceCapability.PresenceBuilder(@NonNull android.net.Uri, int, int);
method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuple(@NonNull android.telephony.ims.RcsContactPresenceTuple);
@@ -13547,7 +13666,7 @@
package android.telephony.ims.stub {
public interface CapabilityExchangeEventListener {
- method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
+ method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
method public void onUnpublish() throws android.telephony.ims.ImsException;
}
@@ -13744,7 +13863,7 @@
public class RcsCapabilityExchangeImplBase {
ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
- method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.List<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
+ method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
method public void subscribeForCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3
field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1
@@ -13960,7 +14079,7 @@
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();
@@ -14204,14 +14323,14 @@
package android.view.translation {
+ public final class TranslationCapability implements android.os.Parcelable {
+ ctor public TranslationCapability(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, boolean, int);
+ }
+
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 0c02c43..b50b8dd 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -48,6 +48,14 @@
}
+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.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 4be6206..97ad48c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -29,12 +29,12 @@
field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
field public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS";
field public static final String QUERY_AUDIO_STATE = "android.permission.QUERY_AUDIO_STATE";
- field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
+ field public static final String 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";
@@ -107,7 +107,6 @@
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
- field public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L; // 0xa6929b8L
field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL
field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf
field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
@@ -234,6 +233,8 @@
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
@@ -819,6 +820,7 @@
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();
@@ -1475,6 +1477,14 @@
}
+package android.media.metrics {
+
+ public final class LogSessionId {
+ ctor public LogSessionId(@NonNull String);
+ }
+
+}
+
package android.media.tv {
public final class TvInputManager {
@@ -1696,6 +1706,7 @@
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
@@ -1705,35 +1716,30 @@
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();
- 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;
- }
-
- 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 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();
+ 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 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;
+ 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 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 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 {
@@ -1834,6 +1840,7 @@
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);
}
@@ -1850,8 +1857,93 @@
}
+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);
@@ -1866,6 +1958,10 @@
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 {
@@ -2254,6 +2350,7 @@
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 a536efb..1833ed5 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, 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.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.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.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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3fedda3..36c66e5 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -901,7 +901,6 @@
*
* @hide
*/
- @TestApi
@ChangeId
public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a1dce77..006b2b5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1606,7 +1606,8 @@
/**
* Sets a launch cookie that can be used to track the activity and task that are launch as a
- * result of this option.
+ * result of this option. If the launched activity is a trampoline that starts another activity
+ * immediately, the cookie will be transferred to the next activity.
*
* @hide
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c1d8541..7298d87 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2328,7 +2328,7 @@
}
@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 27b19bc..7e4af1a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1073,6 +1073,8 @@
/** @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 @@
/** @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 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 @@
*
* @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 @@
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 @@
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 @@
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 @@
OPSTR_FINE_LOCATION_SOURCE,
OPSTR_COARSE_LOCATION_SOURCE,
OPSTR_MANAGE_MEDIA,
+ OPSTR_BLUETOOTH_CONNECT,
};
/**
@@ -2070,6 +2081,7 @@
"FINE_LOCATION_SOURCE",
"COARSE_LOCATION_SOURCE",
"MANAGE_MEDIA",
+ "BLUETOOTH_CONNECT",
};
/**
@@ -2155,7 +2167,7 @@
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 @@
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 @@
null, // ACCESS_FINE_LOCATION_SOURCE
null, // ACCESS_COARSE_LOCATION_SOURCE
null, // MANAGE_MEDIA
+ null, // BLUETOOTH_CONNECT
};
/**
@@ -2426,6 +2440,7 @@
null, // ACCESS_FINE_LOCATION_SOURCE
null, // ACCESS_COARSE_LOCATION_SOURCE
null, // MANAGE_MEDIA
+ null, // BLUETOOTH_CONNECT
};
/**
@@ -2543,6 +2558,7 @@
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 @@
false, // ACCESS_FINE_LOCATION_SOURCE
false, // ACCESS_COARSE_LOCATION_SOURCE
false, // MANAGE_MEDIA
+ false, // BLUETOOTH_CONNECT
};
/**
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 146d648..618eda8 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -22,6 +22,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
+import android.content.ComponentCallbacksController;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -53,14 +54,14 @@
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private static final String TAG = "Application";
@UnsupportedAppUsage
- private ArrayList<ComponentCallbacks> mComponentCallbacks =
- new ArrayList<ComponentCallbacks>();
- @UnsupportedAppUsage
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
@UnsupportedAppUsage
private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
+
/** @hide */
@UnsupportedAppUsage
public LoadedApk mLoadedApk;
@@ -260,47 +261,25 @@
@CallSuper
public void onConfigurationChanged(@NonNull Configuration newConfig) {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- ((ComponentCallbacks)callbacks[i]).onConfigurationChanged(newConfig);
- }
- }
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
}
@CallSuper
public void onLowMemory() {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- ((ComponentCallbacks)callbacks[i]).onLowMemory();
- }
- }
+ mCallbacksController.dispatchLowMemory();
}
@CallSuper
public void onTrimMemory(int level) {
- Object[] callbacks = collectComponentCallbacks();
- if (callbacks != null) {
- for (int i=0; i<callbacks.length; i++) {
- Object c = callbacks[i];
- if (c instanceof ComponentCallbacks2) {
- ((ComponentCallbacks2)c).onTrimMemory(level);
- }
- }
- }
+ mCallbacksController.dispatchTrimMemory(level);
}
public void registerComponentCallbacks(ComponentCallbacks callback) {
- synchronized (mComponentCallbacks) {
- mComponentCallbacks.add(callback);
- }
+ mCallbacksController.registerCallbacks(callback);
}
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
- synchronized (mComponentCallbacks) {
- mComponentCallbacks.remove(callback);
- }
+ mCallbacksController.unregisterCallbacks(callback);
}
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
@@ -575,16 +554,6 @@
}
}
- private Object[] collectComponentCallbacks() {
- Object[] callbacks = null;
- synchronized (mComponentCallbacks) {
- if (mComponentCallbacks.size() > 0) {
- callbacks = mComponentCallbacks.toArray();
- }
- }
- return callbacks;
- }
-
@UnsupportedAppUsage
private Object[] collectActivityLifecycleCallbacks() {
Object[] callbacks = null;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0358fe56..03e95fc 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,10 +19,12 @@
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.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 @@
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 @@
@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 @@
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();
- final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
- ? mPackageInfo.getCompatibilityInfo()
- : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- final List<ResourcesLoader> loaders = mResources.getLoaders();
+ /**
+ * 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();
- return mResourcesManager.createBaseTokenResources(mToken, resDir, splitResDirs,
- legacyOverlayDirs, overlayPaths, libDirs, displayId, null /* overrideConfig */,
- compatInfo, mClassLoader, loaders);
+ 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)
+ ? packageInfo.getCompatibilityInfo()
+ : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ final List<ResourcesLoader> loaders = windowContextBase.mResources.getLoaders();
+
+ return windowContextBase.mResourcesManager.createBaseTokenResources(token, resDir,
+ splitResDirs, legacyOverlayDirs, overlayPaths, libDirs, displayId,
+ null /* overrideConfig */, compatInfo, classLoader, loaders);
}
@NonNull
@@ -3114,6 +3183,14 @@
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 47de040..5964f71 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -135,4 +135,20 @@
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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 3bfddf7..e5a969a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -244,6 +244,8 @@
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken, int flags);
boolean isAssistDataAllowedOnCurrentActivity();
+ boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
+ in String callingPackageName);
/**
* Notify the system that the keyguard is going away.
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index c8e1478..4bf8a3f 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -22,4 +22,5 @@
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 b6d25cf..4326c2d 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -133,6 +133,42 @@
*/
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 @@
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 @@
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 @@
userId);
success = true;
break;
- case LockTypes.PIN:
+ case PIN:
CharSequence pinStr = new String(password);
lockPatternUtils.setLockCredential(
LockscreenCredential.createPin(pinStr),
@@ -775,7 +811,7 @@
userId);
success = true;
break;
- case LockTypes.PATTERN:
+ case PATTERN:
List<LockPatternView.Cell> pattern =
LockPatternUtils.byteArrayToPattern(password);
lockPatternUtils.setLockCredential(
@@ -796,18 +832,4 @@
}
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 f4e214c..420ec08 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -20,8 +20,6 @@
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
import static java.util.Objects.requireNonNull;
import android.annotation.AttrRes;
@@ -41,7 +39,6 @@
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;
@@ -1207,9 +1204,16 @@
public static final String EXTRA_PICTURE = "android.picture";
/**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown in {@link BigPictureStyle} expanded notifications, supplied to
+ * {@link BigPictureStyle#bigPicture(Icon)}.
+ */
+ public static final String EXTRA_PICTURE_ICON = "android.pictureIcon";
+
+ /**
* {@link #extras} key: this is a content description of the big picture supplied from
* {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to
- * {@link BigPictureStyle#bigPictureContentDescription(CharSequence)}.
+ * {@link BigPictureStyle#setContentDescription(CharSequence)}.
*/
public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION =
"android.pictureContentDescription";
@@ -2668,6 +2672,14 @@
}
}
+ private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) {
+ if (icon == null) return;
+ final int iconType = icon.getType();
+ if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) {
+ visitor.accept(icon.getUri());
+ }
+ }
+
/**
* Note all {@link Uri} that are referenced internally, with the expectation
* that Uri permission grants will need to be issued to ensure the recipient
@@ -2683,7 +2695,19 @@
if (bigContentView != null) bigContentView.visitUris(visitor);
if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
+ visitIconUri(visitor, mSmallIcon);
+ visitIconUri(visitor, mLargeIcon);
+
+ if (actions != null) {
+ for (Action action : actions) {
+ visitIconUri(visitor, action.getIcon());
+ }
+ }
+
if (extras != null) {
+ visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON));
+
// NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a
// String representation of a Uri, but the previous implementation (and unit test) of
// this method has always treated it as a Uri object. Given the inconsistency,
@@ -2702,14 +2726,12 @@
ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
if (people != null && !people.isEmpty()) {
for (Person p : people) {
- if (p.getIconUri() != null) {
- visitor.accept(p.getIconUri());
- }
+ visitor.accept(p.getIconUri());
}
}
final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON);
- if (person != null && person.getIconUri() != null) {
+ if (person != null) {
visitor.accept(person.getIconUri());
}
}
@@ -2722,7 +2744,7 @@
visitor.accept(message.getDataUri());
Person senderPerson = message.getSenderPerson();
- if (senderPerson != null && senderPerson.getIconUri() != null) {
+ if (senderPerson != null) {
visitor.accept(senderPerson.getIconUri());
}
}
@@ -2735,19 +2757,15 @@
visitor.accept(message.getDataUri());
Person senderPerson = message.getSenderPerson();
- if (senderPerson != null && senderPerson.getIconUri() != null) {
+ if (senderPerson != null) {
visitor.accept(senderPerson.getIconUri());
}
}
}
}
- if (mBubbleMetadata != null && mBubbleMetadata.getIcon() != null) {
- final Icon icon = mBubbleMetadata.getIcon();
- final int iconType = icon.getType();
- if (iconType == TYPE_URI_ADAPTIVE_BITMAP || iconType == TYPE_URI) {
- visitor.accept(icon.getUri());
- }
+ if (mBubbleMetadata != null) {
+ visitIconUri(visitor, mBubbleMetadata.getIcon());
}
}
@@ -3680,8 +3698,6 @@
private int mTextColorsAreForBackground = COLOR_INVALID;
private int mPrimaryTextColor = COLOR_INVALID;
private int mSecondaryTextColor = COLOR_INVALID;
- private int mBackgroundColor = COLOR_INVALID;
- private int mForegroundColor = COLOR_INVALID;
private boolean mRebuildStyledRemoteViews;
private boolean mTintActionButtons;
@@ -5020,7 +5036,8 @@
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
p.headerless(resId == getBaseLayoutResource()
- || resId == getHeadsUpBaseLayoutResource());
+ || resId == getHeadsUpBaseLayoutResource()
+ || resId == R.layout.notification_template_material_media);
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
@@ -5071,7 +5088,7 @@
}
private CharSequence processTextSpans(CharSequence text) {
- if (hasForegroundColor() || mInNightMode) {
+ if (mInNightMode) {
return ContrastColorUtil.clearColorSpans(text);
}
return text;
@@ -5082,10 +5099,6 @@
contentView.setTextColor(id, getPrimaryTextColor(p));
}
- private boolean hasForegroundColor() {
- return mForegroundColor != COLOR_INVALID;
- }
-
/**
* @param p the template params to inflate this with
* @return the primary text color
@@ -5119,75 +5132,15 @@
|| mSecondaryTextColor == COLOR_INVALID
|| mTextColorsAreForBackground != backgroundColor) {
mTextColorsAreForBackground = backgroundColor;
- if (!hasForegroundColor() || !isColorized(p)) {
- mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
- backgroundColor, mInNightMode);
- mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
- backgroundColor, mInNightMode);
- if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
- mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- mPrimaryTextColor, backgroundColor, 4.5);
- mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
- mSecondaryTextColor, backgroundColor, 4.5);
- }
- } else {
- double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
- double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
- double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
- backgroundColor);
- // We only respect the given colors if worst case Black or White still has
- // contrast
- boolean backgroundLight = backLum > textLum
- && satisfiesTextContrast(backgroundColor, Color.BLACK)
- || backLum <= textLum
- && !satisfiesTextContrast(backgroundColor, Color.WHITE);
- if (contrast < 4.5f) {
- if (backgroundLight) {
- mSecondaryTextColor = ContrastColorUtil.findContrastColor(
- mForegroundColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
- } else {
- mSecondaryTextColor =
- ContrastColorUtil.findContrastColorAgainstDark(
- mForegroundColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
- }
- } else {
- mPrimaryTextColor = mForegroundColor;
- mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
- mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
- : LIGHTNESS_TEXT_DIFFERENCE_DARK);
- if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
- backgroundColor) < 4.5f) {
- // oh well the secondary is not good enough
- if (backgroundLight) {
- mSecondaryTextColor = ContrastColorUtil.findContrastColor(
- mSecondaryTextColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- } else {
- mSecondaryTextColor
- = ContrastColorUtil.findContrastColorAgainstDark(
- mSecondaryTextColor,
- backgroundColor,
- true /* findFG */,
- 4.5f);
- }
- mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
- mSecondaryTextColor, backgroundLight
- ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
- : -LIGHTNESS_TEXT_DIFFERENCE_DARK);
- }
- }
+ mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+ backgroundColor, mInNightMode);
+ mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
+ backgroundColor, mInNightMode);
+ if (backgroundColor != COLOR_DEFAULT && isColorized(p)) {
+ mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ mPrimaryTextColor, backgroundColor, 4.5);
+ mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
+ mSecondaryTextColor, backgroundColor, 4.5);
}
}
}
@@ -5233,11 +5186,7 @@
result = new TemplateBindResult();
}
bindLargeIcon(contentView, p, result);
- if (p.mHeaderless) {
- // views in the headerless (collapsed) state
- result.mHeadingExtraMarginSet.applyToView(contentView,
- R.id.notification_headerless_view_column);
- } else {
+ if (!p.mHeaderless) {
// views in states with a header (big states)
result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header);
result.mTitleMarginSet.applyToView(contentView, R.id.title);
@@ -5257,6 +5206,8 @@
@NonNull TemplateBindResult result) {
final Resources resources = mContext.getResources();
final float density = resources.getDisplayMetrics().density;
+ final float iconMarginDp = resources.getDimension(
+ R.dimen.notification_right_icon_content_margin) / density;
final float contentMarginDp = resources.getDimension(
R.dimen.notification_content_margin_end) / density;
final float expanderSizeDp = resources.getDimension(
@@ -5265,8 +5216,7 @@
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();
@@ -5278,7 +5228,7 @@
}
}
}
- final float extraMarginEndDpIfVisible = viewWidthDp + contentMarginDp;
+ final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp;
result.setRightIconState(largeIconShown, viewWidthDp,
extraMarginEndDpIfVisible, expanderSizeDp);
}
@@ -5333,8 +5283,7 @@
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);
@@ -5342,7 +5291,7 @@
private void bindHeaderChronometerAndTime(RemoteViews contentView,
StandardTemplateParams p, boolean hasTextToLeft) {
- if (showsTimeOrChronometer()) {
+ if (!p.mHideTime && showsTimeOrChronometer()) {
if (hasTextToLeft) {
contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
setTextViewColorSecondary(contentView, R.id.time_divider, p);
@@ -5373,6 +5322,9 @@
*/
private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
+ if (p.mHideSubText) {
+ return false;
+ }
CharSequence summaryText = p.summaryText;
if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet
&& mStyle.hasSummaryInHeader()) {
@@ -5403,6 +5355,9 @@
*/
private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p,
boolean hasTextToLeft) {
+ if (p.mHideSubText) {
+ return false;
+ }
if (!TextUtils.isEmpty(p.headerTextSecondary)) {
contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
processLegacyText(p.headerTextSecondary)));
@@ -5704,55 +5659,45 @@
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;
}
@@ -5811,12 +5756,6 @@
.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);
@@ -6279,6 +6218,25 @@
}
/**
+ * 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) {
@@ -6633,7 +6591,7 @@
*/
private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) {
if (isColorized(p)) {
- return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : getRawColor(p);
+ return getRawColor(p);
} else {
return COLOR_DEFAULT;
}
@@ -6661,21 +6619,6 @@
}
/**
- * Set a color palette to be used as the background and textColors
- *
- * @param backgroundColor the color to be used as the background
- * @param foregroundColor the color to be used as the foreground
- *
- * @hide
- */
- public void setColorPalette(@ColorInt int backgroundColor, @ColorInt int foregroundColor) {
- mBackgroundColor = backgroundColor;
- mForegroundColor = foregroundColor;
- mTextColorsAreForBackground = COLOR_INVALID;
- ensureColors(mParams.reset().fillTextsFrom(this));
- }
-
- /**
* Forces all styled remoteViews to be built from scratch and not use any cached
* RemoteViews.
* This is needed for legacy apps that are baking in their remoteviews into the
@@ -6731,24 +6674,14 @@
if (mLargeIcon != null || largeIcon != null) {
Resources resources = context.getResources();
Class<? extends Style> style = getNotificationStyle();
- int maxWidth = resources.getDimensionPixelSize(isLowRam
+ int maxSize = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_right_icon_size_low_ram
: R.dimen.notification_right_icon_size);
- int maxHeight = maxWidth;
- if (MediaStyle.class.equals(style)
- || DecoratedMediaCustomViewStyle.class.equals(style)) {
- maxHeight = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_media_image_max_height_low_ram
- : R.dimen.notification_media_image_max_height);
- maxWidth = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_media_image_max_width_low_ram
- : R.dimen.notification_media_image_max_width);
- }
if (mLargeIcon != null) {
- mLargeIcon.scaleDownIfNecessary(maxWidth, maxHeight);
+ mLargeIcon.scaleDownIfNecessary(maxSize, maxSize);
}
if (largeIcon != null) {
- largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxWidth, maxHeight);
+ largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize);
}
}
reduceImageSizesForRemoteView(contentView, context, isLowRam);
@@ -6835,9 +6768,6 @@
* @hide
*/
public boolean isColorized() {
- if (isColorizedMedia()) {
- return true;
- }
return extras.getBoolean(EXTRA_COLORIZED)
&& (hasColorizedPermission() || isForegroundService());
}
@@ -6851,27 +6781,6 @@
}
/**
- * @return true if this notification is colorized and it is a media notification
- *
- * @hide
- */
- public boolean isColorizedMedia() {
- Class<? extends Style> style = getNotificationStyle();
- if (MediaStyle.class.equals(style)) {
- Boolean colorized = (Boolean) extras.get(EXTRA_COLORIZED);
- if ((colorized == null || colorized) && hasMediaSession()) {
- return true;
- }
- } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
- if (extras.getBoolean(EXTRA_COLORIZED) && hasMediaSession()) {
- return true;
- }
- }
- return false;
- }
-
-
- /**
* @return true if this is a media notification
*
* @hide
@@ -6955,24 +6864,18 @@
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();
@@ -7159,15 +7062,6 @@
/**
* @hide
- * @return true if the style positions the progress bar on the second line; false if the
- * style hides the progress bar
- */
- protected boolean hasProgress() {
- return true;
- }
-
- /**
- * @hide
* @return Whether we should put the summary be put into the notification header
*/
public boolean hasSummaryInHeader() {
@@ -7231,7 +7125,7 @@
* @see Notification#bigContentView
*/
public static class BigPictureStyle extends Style {
- private Bitmap mPicture;
+ private Icon mPictureIcon;
private Icon mBigLargeIcon;
private boolean mBigLargeIconSet = false;
private CharSequence mPictureContentDescription;
@@ -7271,7 +7165,7 @@
* Set the content description of the big picture.
*/
@NonNull
- public BigPictureStyle bigPictureContentDescription(
+ public BigPictureStyle setContentDescription(
@Nullable CharSequence contentDescription) {
mPictureContentDescription = contentDescription;
return this;
@@ -7280,8 +7174,12 @@
/**
* @hide
*/
- public Bitmap getBigPicture() {
- return mPicture;
+ @Nullable
+ public Icon getBigPicture() {
+ if (mPictureIcon != null) {
+ return mPictureIcon;
+ }
+ return null;
}
/**
@@ -7289,7 +7187,16 @@
*/
@NonNull
public BigPictureStyle bigPicture(@Nullable Bitmap b) {
- mPicture = b;
+ mPictureIcon = b == null ? null : Icon.createWithBitmap(b);
+ return this;
+ }
+
+ /**
+ * Provide the content Uri to be used as the payload for the BigPicture notification.
+ */
+ @NonNull
+ public BigPictureStyle bigPicture(@Nullable Icon icon) {
+ mPictureIcon = icon;
return this;
}
@@ -7331,10 +7238,8 @@
@Override
public void purgeResources() {
super.purgeResources();
- if (mPicture != null &&
- mPicture.isMutable() &&
- mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
- mPicture = mPicture.asShared();
+ if (mPictureIcon != null) {
+ mPictureIcon.convertToAshmem();
}
if (mBigLargeIcon != null) {
mBigLargeIcon.convertToAshmem();
@@ -7349,14 +7254,14 @@
super.reduceImageSizes(context);
Resources resources = context.getResources();
boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
- if (mPicture != null) {
+ if (mPictureIcon != null) {
int maxPictureWidth = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_height_low_ram
: R.dimen.notification_big_picture_max_height);
int maxPictureHeight = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_big_picture_max_width_low_ram
: R.dimen.notification_big_picture_max_width);
- mPicture = Icon.scaleDownIfNecessary(mPicture, maxPictureWidth, maxPictureHeight);
+ mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight);
}
if (mBigLargeIcon != null) {
int rightIconSize = resources.getDimensionPixelSize(isLowRam
@@ -7371,12 +7276,12 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (mPicture == null || !mShowBigPictureWhenCollapsed) {
+ if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
return super.makeContentView(increasedHeight);
}
Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
- mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+ mBuilder.mN.mLargeIcon = mPictureIcon;
// The legacy largeIcon might not allow us to clear the image, as it's taken in
// replacement if the other one is null. Because we're restoring these legacy icons
// for old listeners, this is in general non-null.
@@ -7401,12 +7306,12 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (mPicture == null || !mShowBigPictureWhenCollapsed) {
+ if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) {
return super.makeHeadsUpContentView(increasedHeight);
}
Icon oldLargeIcon = mBuilder.mN.mLargeIcon;
- mBuilder.mN.mLargeIcon = Icon.createWithBitmap(mPicture);
+ mBuilder.mN.mLargeIcon = mPictureIcon;
// The legacy largeIcon might not allow us to clear the image, as it's taken in
// replacement if the other one is null. Because we're restoring these legacy icons
// for old listeners, this is in general non-null.
@@ -7463,7 +7368,7 @@
mBuilder.mN.largeIcon = largeIconLegacy;
}
- contentView.setImageViewBitmap(R.id.big_picture, mPicture);
+ contentView.setImageViewIcon(R.id.big_picture, mPictureIcon);
if (mPictureContentDescription != null) {
contentView.setContentDescription(R.id.big_picture, mPictureContentDescription);
@@ -7486,7 +7391,17 @@
mPictureContentDescription);
}
extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
- extras.putParcelable(EXTRA_PICTURE, mPicture);
+
+ // If the icon contains a bitmap, use the old extra so that listeners which look for
+ // that extra can still find the picture. Don't include the new extra in that case,
+ // to avoid duplicating data.
+ if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) {
+ extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
+ extras.putParcelable(EXTRA_PICTURE_ICON, null);
+ } else {
+ extras.putParcelable(EXTRA_PICTURE, null);
+ extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
+ }
}
/**
@@ -7507,7 +7422,16 @@
}
mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
- mPicture = extras.getParcelable(EXTRA_PICTURE);
+
+ // When this style adds a picture, we only add one of the keys. If both were added,
+ // it would most likely be a legacy app trying to override the picture in some way.
+ // Because of that case it's better to give precedence to the legacy field.
+ Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE);
+ if (bitmapPicture != null) {
+ mPictureIcon = Icon.createWithBitmap(bitmapPicture);
+ } else {
+ mPictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ }
}
/**
@@ -7529,20 +7453,32 @@
return true;
}
BigPictureStyle otherS = (BigPictureStyle) other;
- return areBitmapsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
+ return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
}
- private static boolean areBitmapsObviouslyDifferent(Bitmap a, Bitmap b) {
+ private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
if (a == b) {
return false;
}
if (a == null || b == null) {
return true;
}
- return a.getWidth() != b.getWidth()
- || a.getHeight() != b.getHeight()
- || a.getConfig() != b.getConfig()
- || a.getGenerationId() != b.getGenerationId();
+ if (a.sameAs(b)) {
+ return false;
+ }
+ final int aType = a.getType();
+ if (aType != b.getType()) {
+ return true;
+ }
+ if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
+ final Bitmap aBitmap = a.getBitmap();
+ final Bitmap bBitmap = b.getBitmap();
+ return aBitmap.getWidth() != bBitmap.getWidth()
+ || aBitmap.getHeight() != bBitmap.getHeight()
+ || aBitmap.getConfig() != bBitmap.getConfig()
+ || aBitmap.getGenerationId() != bBitmap.getGenerationId();
+ }
+ return true;
}
}
@@ -8978,7 +8914,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- return makeMediaContentView();
+ return makeMediaContentView(null /* customContent */);
}
/**
@@ -8986,7 +8922,7 @@
*/
@Override
public RemoteViews makeBigContentView() {
- return makeMediaBigContentView();
+ return makeMediaBigContentView(null /* customContent */);
}
/**
@@ -8994,7 +8930,7 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- return makeMediaContentView();
+ return makeMediaContentView(null /* customContent */);
}
/** @hide */
@@ -9064,88 +9000,70 @@
container.setContentDescription(buttonId, action.title);
}
- private RemoteViews makeMediaContentView() {
- StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
- .hideProgress(true)
- .fillTextsFrom(mBuilder);
- RemoteViews view = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_media, p,
- null /* result */);
-
+ /** @hide */
+ protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) {
final int numActions = mBuilder.mActions.size();
- final int numActionsToShow = mActionsToShowInCompact == null
- ? 0
- : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
+ final int numActionsToShow = Math.min(mActionsToShowInCompact == null
+ ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
if (numActionsToShow > numActions) {
throw new IllegalArgumentException(String.format(
"setShowActionsInCompactView: action %d out of bounds (max %d)",
numActions, numActions - 1));
}
+
+ StandardTemplateParams p = mBuilder.mParams.reset()
+ .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
+ .hideTime(numActionsToShow > 1) // hide if actions wider than a large icon
+ .hideSubText(numActionsToShow > 1) // hide if actions wider than a large icon
+ .hideLargeIcon(numActionsToShow > 0) // large icon or actions; not both
+ .hideProgress(true)
+ .fillTextsFrom(mBuilder);
+ TemplateBindResult result = new TemplateBindResult();
+ RemoteViews template = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_media, p,
+ null /* result */);
+
for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
if (i < numActionsToShow) {
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
- bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p);
+ bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p);
} else {
- view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+ template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
- handleImage(view);
- // handle the content margin
- int endMargin = R.dimen.notification_content_margin_end;
- if (mBuilder.mN.hasLargeIcon()) {
- endMargin = R.dimen.notification_media_image_margin_end;
- }
- view.setViewLayoutMarginDimen(R.id.notification_main_column,
- RemoteViews.MARGIN_END, endMargin);
- return view;
+ // Prevent a swooping expand animation when there are no actions
+ boolean hasActions = numActionsToShow != 0;
+ 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);
+ return template;
}
- private RemoteViews makeMediaBigContentView() {
+ /** @hide */
+ protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
- // Dont add an expanded view if there is no more content to be revealed
- int actionsInCompact = mActionsToShowInCompact == null
- ? 0
- : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
- if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) {
- return null;
- }
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_BIG)
.hideProgress(true)
.fillTextsFrom(mBuilder);
- RemoteViews big = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_big_media, p , null /* result */);
+ TemplateBindResult result = new TemplateBindResult();
+ RemoteViews template = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_big_media, p , result);
for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) {
if (i < actionCount) {
- bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
+ bindMediaActionButton(template,
+ MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p);
} else {
- big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
+ template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
}
}
- handleImage(big);
- return big;
- }
-
- private void handleImage(RemoteViews contentView) {
- if (mBuilder.mN.hasLargeIcon()) {
- contentView.setViewLayoutMarginDimen(R.id.title, RemoteViews.MARGIN_END, 0);
- contentView.setViewLayoutMarginDimen(R.id.text, RemoteViews.MARGIN_END, 0);
- }
- }
-
- /**
- * @hide
- */
- @Override
- protected boolean hasProgress() {
- return false;
+ buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result);
+ return template;
}
}
-
-
/**
* Helper class for generating large-format notifications that include a large image attachment.
*
@@ -9718,16 +9636,15 @@
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;
}
@@ -9735,16 +9652,15 @@
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;
}
@@ -9755,24 +9671,18 @@
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
*/
@@ -9833,9 +9743,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */);
- return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
- mBuilder.mN.contentView);
+ return makeMediaContentView(mBuilder.mN.contentView);
}
/**
@@ -9843,24 +9751,10 @@
*/
@Override
public RemoteViews makeBigContentView() {
- RemoteViews customRemoteView = mBuilder.mN.bigContentView != null
+ RemoteViews customContent = mBuilder.mN.bigContentView != null
? mBuilder.mN.bigContentView
: mBuilder.mN.contentView;
- return makeBigContentViewWithCustomContent(customRemoteView);
- }
-
- private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) {
- RemoteViews remoteViews = super.makeBigContentView();
- if (remoteViews != null) {
- return buildIntoRemoteView(remoteViews, R.id.notification_main_column,
- customRemoteView);
- } else if (customRemoteView != mBuilder.mN.contentView){
- remoteViews = super.makeContentView(false /* increasedHeight */);
- return buildIntoRemoteView(remoteViews, R.id.notification_content_container,
- customRemoteView);
- } else {
- return null;
- }
+ return makeMediaBigContentView(customContent);
}
/**
@@ -9868,10 +9762,10 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null
+ RemoteViews customContent = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
- return makeBigContentViewWithCustomContent(customRemoteView);
+ return makeMediaBigContentView(customContent);
}
/**
@@ -9886,18 +9780,21 @@
return false;
}
- private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
- RemoteViews customContent) {
+ private RemoteViews buildIntoRemoteView(RemoteViews template, RemoteViews customContent,
+ boolean headerless) {
if (customContent != null) {
// Need to clone customContent before adding, because otherwise it can no longer be
// parceled independently of remoteViews.
customContent = customContent.clone();
customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams));
- remoteViews.removeAllViews(id);
- remoteViews.addView(id, customContent);
- remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
+ if (headerless) {
+ template.removeFromParent(R.id.notification_top_line);
+ }
+ template.removeAllViews(R.id.notification_main_column);
+ template.addView(R.id.notification_main_column, customContent);
+ template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED);
}
- return remoteViews;
+ return template;
}
}
@@ -12178,6 +12075,22 @@
}
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;
@@ -12190,6 +12103,8 @@
boolean mHeaderless;
boolean mHideAppName;
boolean mHideTitle;
+ boolean mHideSubText;
+ boolean mHideTime;
boolean mHideActions;
boolean mHideProgress;
boolean mHideSnoozeButton;
@@ -12212,6 +12127,8 @@
mHeaderless = false;
mHideAppName = false;
mHideTitle = false;
+ mHideSubText = false;
+ mHideTime = false;
mHideActions = false;
mHideProgress = false;
mHideSnoozeButton = false;
@@ -12225,6 +12142,7 @@
summaryText = null;
headerTextSecondary = null;
maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
+ hideLargeIcon = false;
allowColorization = true;
mReduceHighlights = false;
return this;
@@ -12249,6 +12167,16 @@
return this;
}
+ public StandardTemplateParams hideSubText(boolean hideSubText) {
+ mHideSubText = hideSubText;
+ return this;
+ }
+
+ public StandardTemplateParams hideTime(boolean hideTime) {
+ mHideTime = hideTime;
+ return this;
+ }
+
final StandardTemplateParams hideActions(boolean hideActions) {
this.mHideActions = hideActions;
return this;
@@ -12348,112 +12276,20 @@
}
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 11adc5a..f4b9542 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1010,7 +1010,7 @@
* @deprecated Renamed to {@link #getCreatorPackage()}.
*/
@Deprecated
- @NonNull
+ @Nullable
public String getTargetPackage() {
return getCreatorPackage();
}
@@ -1032,7 +1032,7 @@
*
* @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 ac8d3a2..74134e1 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -39,13 +39,13 @@
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.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 @@
/**
* 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/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 6b2e649..d640a6f 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -189,7 +189,7 @@
} else {
palette = Palette
.from(bitmap, new CelebiQuantizer())
- .maximumColorCount(256)
+ .maximumColorCount(5)
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3ef6757a..6a71c92 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1962,14 +1962,20 @@
}
/**
- * 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/WindowContext.java b/core/java/android/app/WindowContext.java
deleted file mode 100644
index cbe2995..0000000
--- a/core/java/android/app/WindowContext.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.app;
-
-import static android.view.WindowManagerImpl.createWindowContextWindowManager;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UiContext;
-import android.content.Context;
-import android.content.ContextWrapper;
-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;
-
-import java.lang.ref.Reference;
-
-/**
- * {@link WindowContext} is a context for non-activity windows such as
- * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} windows or system
- * windows. Its resources and configuration are adjusted to the area of the display that will be
- * used when a new window is added via {@link android.view.WindowManager#addView}.
- *
- * @see Context#createWindowContext(int, Bundle)
- * @hide
- */
-@UiContext
-public class WindowContext extends ContextWrapper {
- private final WindowManager mWindowManager;
- private final IWindowManager mWms;
- private final WindowTokenClient mToken;
- private boolean mListenerRegistered;
-
- /**
- * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
- * the token.
- *
- * @param base Base {@link Context} for this new instance.
- * @param type Window type to be used with this context.
- * @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);
-
- mWindowManager = createWindowContextWindowManager(this);
-
- 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);
- }
-
- @Override
- public Object getSystemService(String name) {
- if (WINDOW_SERVICE.equals(name)) {
- return mWindowManager;
- }
- return super.getSystemService(name);
- }
-
- @Override
- protected void finalize() throws Throwable {
- release();
- super.finalize();
- }
-
- /** 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();
- }
- }
- destroy();
- }
-
- void destroy() {
- final ContextImpl impl = (ContextImpl) getBaseContext();
- impl.scheduleFinalCleanup(getClass().getName(), "WindowContext");
- Reference.reachabilityFence(this);
- }
-}
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 747a2de..da64dcd 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -524,6 +524,16 @@
"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 @@
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 @@
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 426159f..26e67411 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -872,8 +872,7 @@
*
* 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 @@
"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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
+ * 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 7901791..5e49a98 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -268,8 +268,8 @@
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 @@
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/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index 6759346..51430c0 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,12 +33,14 @@
* Name of this restore set. May be user generated, may simply be the name
* of the handset model, e.g. "T-Mobile G1".
*/
+ @Nullable
public String name;
/**
* Identifier of the device whose data this is. This will be as unique as
* is practically possible; for example, it might be an IMEI.
*/
+ @Nullable
public String device;
/**
@@ -47,15 +50,48 @@
*/
public long token;
+ /**
+ * Properties of the {@link BackupTransport} transport that was used to obtain the data in
+ * this restore set.
+ */
+ public final int backupTransportFlags;
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ */
public RestoreSet() {
// Leave everything zero / null
+ backupTransportFlags = 0;
}
- public RestoreSet(String _name, String _dev, long _token) {
- name = _name;
- device = _dev;
- token = _token;
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ *
+ * @param name The name of the restore set.
+ * @param device The name of the device where the restore data is coming from.
+ * @param token The unique identifier for the current restore set.
+ */
+ public RestoreSet(@Nullable String name, @Nullable String device, long token) {
+ this(name, device, token, /* backupTransportFlags */ 0);
+ }
+
+ /**
+ * Constructs a RestoreSet object that identifies a set of data that can be restored.
+ *
+ * @param name The name of the restore set.
+ * @param device The name of the device where the restore data is coming from.
+ * @param token The unique identifier for the current restore set.
+ * @param backupTransportFlags Flags returned by {@link BackupTransport#getTransportFlags()}
+ * implementation of the backup transport used by the source device
+ * to create this restore set. See {@link BackupAgent} for possible
+ * flag values.
+ */
+ public RestoreSet(@Nullable String name, @Nullable String device, long token,
+ int backupTransportFlags) {
+ this.name = name;
+ this.device = device;
+ this.token = token;
+ this.backupTransportFlags = backupTransportFlags;
}
// Parcelable implementation
@@ -67,6 +103,7 @@
out.writeString(name);
out.writeString(device);
out.writeLong(token);
+ out.writeInt(backupTransportFlags);
}
public static final @android.annotation.NonNull Parcelable.Creator<RestoreSet> CREATOR
@@ -84,5 +121,6 @@
name = in.readString();
device = in.readString();
token = in.readLong();
+ backupTransportFlags = in.readInt();
}
}
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index dd2ba7d..e645831 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -56,6 +56,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 c8fa5c8..c71badb0 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -264,7 +264,7 @@
* 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/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 5d50c5d7..ef92172 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -110,7 +110,9 @@
public long mTotalTimeForegroundServiceUsed;
/**
- * Last time this package's component is used, measured in milliseconds since the epoch.
+ * Last time this package's component is used by a client package, measured in milliseconds
+ * since the epoch. Note that component usage is only reported in certain cases (e.g. broadcast
+ * receiver, service, content provider).
* See {@link UsageEvents.Event#APP_COMPONENT_USED}
* @hide
*/
@@ -274,8 +276,10 @@
}
/**
- * Get the last time this package's component was used, measured in milliseconds since the
- * epoch.
+ * Get the last time this package's component was used by a client package, measured in
+ * milliseconds since the epoch. Note that component usage is only reported in certain cases
+ * (e.g. broadcast receiver, service, content provider).
+ * See {@link UsageEvents.Event#APP_COMPONENT_USED}
* @hide
*/
@SystemApi
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index 132cc40..a36da88 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -24,6 +24,8 @@
import android.os.RemoteException;
import android.os.ServiceManager;
+import java.util.List;
+
/**
* This class provides an API surface for system apps to manipulate the app hibernation
* state of a package for the user provided in the context.
@@ -31,7 +33,7 @@
*/
@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;
@@ -111,4 +113,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Get the hibernating packages for the user. This is equivalent to the list of packages for
+ * the user that return true for {@link #isHibernatingForUser}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
+ public @NonNull List<String> getHibernatingPackagesForUser() {
+ try {
+ return mIAppHibernationService.getHibernatingPackagesForUser(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl
index 6a068ee..afdb3fe 100644
--- a/core/java/android/apphibernation/IAppHibernationService.aidl
+++ b/core/java/android/apphibernation/IAppHibernationService.aidl
@@ -25,4 +25,5 @@
void setHibernatingForUser(String packageName, int userId, boolean isHibernating);
boolean isHibernatingGlobally(String packageName);
void setHibernatingGlobally(String packageName, boolean isHibernating);
+ List<String> getHibernatingPackagesForUser(int userId);
}
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index a6b4b47..82da4fb 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -841,8 +841,8 @@
* Calling this method will trigger a full re-inflation of the App Widget.
*
* The color resources that can be overloaded are the ones whose name is prefixed with
- * {@code system_primary_}, {@code system_secondary_} or {@code system_neutral_}, for example
- * {@link android.R.color#system_primary_500}.
+ * {@code system_neutral} or {@code system_accent}, for example
+ * {@link android.R.color#system_neutral1_500}.
*/
public void setColorResources(@NonNull SparseIntArray colorMapping) {
mColorResources = RemoteViews.ColorResources.create(mContext, colorMapping);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8d41572..a3d19ca 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -52,6 +52,8 @@
import android.util.Log;
import android.util.Pair;
+import com.android.internal.util.Preconditions;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -3152,6 +3154,25 @@
return true;
}
+ /**
+ * Determines whether a String Bluetooth address, such as "00:43:A8:23:10:F0"
+ * is a RANDOM STATIC address.
+ *
+ * RANDOM STATIC: (addr & 0b11) == 0b11
+ * RANDOM RESOLVABLE: (addr & 0b11) == 0b10
+ * RANDOM non-RESOLVABLE: (addr & 0b11) == 0b00
+ *
+ * @param address Bluetooth address as string
+ * @return true if the 2 Least Significant Bits of the address equals 0b11.
+ *
+ * @hide
+ */
+ public static boolean isAddressRandomStatic(@NonNull String address) {
+ Preconditions.checkNotNull(address);
+ return checkBluetoothAddress(address)
+ && (Integer.parseInt(address.split(":")[5], 16) & 0b11) == 0b11;
+ }
+
@UnsupportedAppUsage
/*package*/ IBluetoothManager getBluetoothManager() {
return mManagerService;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 07dbdce..a96c14f 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -26,6 +26,7 @@
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;
@@ -1022,6 +1023,24 @@
public static final String EXTRA_MAS_INSTANCE =
"android.bluetooth.device.extra.MAS_INSTANCE";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "ADDRESS_TYPE_" },
+ value = {
+ /** Hardware MAC Address */
+ ADDRESS_TYPE_PUBLIC,
+ /** Address is either resolvable, non-resolvable or static.*/
+ ADDRESS_TYPE_RANDOM,
+ }
+ )
+ public @interface AddressType {}
+
+ /** Hardware MAC Address of the device */
+ public static final int ADDRESS_TYPE_PUBLIC = 0;
+ /** Address is either resolvable, non-resolvable or static. */
+ public static final int ADDRESS_TYPE_RANDOM = 1;
+
/**
* Lazy initialization. Guaranteed final after first object constructed, or
* getService() called.
@@ -1030,6 +1049,7 @@
private static volatile IBluetooth sService;
private final String mAddress;
+ @AddressType private final int mAddressType;
/*package*/
@UnsupportedAppUsage
@@ -1084,6 +1104,7 @@
}
mAddress = address;
+ mAddressType = ADDRESS_TYPE_PUBLIC;
}
@Override
@@ -1211,8 +1232,7 @@
}
/**
- * 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
@@ -1238,25 +1258,35 @@
}
/**
- * 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 4fb5577..632572d 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -567,6 +567,7 @@
* @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 51f63f7..27c579b 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -16,15 +16,19 @@
package android.bluetooth.le;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevice.AddressType;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.List;
@@ -53,6 +57,11 @@
@Nullable
private final String mDeviceAddress;
+ private final @AddressType int mAddressType;
+
+ @Nullable
+ private final byte[] mIrk;
+
@Nullable
private final ParcelUuid mServiceUuid;
@Nullable
@@ -79,12 +88,12 @@
/** @hide */
public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
-
private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
ParcelUuid uuidMask, ParcelUuid solicitationUuid,
ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
byte[] serviceData, byte[] serviceDataMask,
- int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
+ int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
+ @AddressType int addressType, @Nullable byte[] irk) {
mDeviceName = name;
mServiceUuid = uuid;
mServiceUuidMask = uuidMask;
@@ -97,6 +106,8 @@
mManufacturerId = manufacturerId;
mManufacturerData = manufacturerData;
mManufacturerDataMask = manufacturerDataMask;
+ mAddressType = addressType;
+ mIrk = irk;
}
@Override
@@ -280,6 +291,23 @@
return mDeviceAddress;
}
+ /**
+ * @hide
+ */
+ @SystemApi
+ public @AddressType int getAddressType() {
+ return mAddressType;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public byte[] getIrk() {
+ return mIrk;
+ }
+
@Nullable
public byte[] getServiceData() {
return mServiceData;
@@ -516,8 +544,16 @@
*/
public static final class Builder {
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final int LEN_IRK_OCTETS = 16;
+
private String mDeviceName;
private String mDeviceAddress;
+ private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
+ private byte[] mIrk;
private ParcelUuid mServiceUuid;
private ParcelUuid mUuidMask;
@@ -546,14 +582,130 @@
*
* @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
* format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
- * BluetoothAdapter#checkBluetoothAddress}.
+ * BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to {@link
+ * BluetoothDevice#ADDRESS_TYPE_PUBLIC}
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
*/
public Builder setDeviceAddress(String deviceAddress) {
- if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
+ return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
+ }
+
+ /**
+ * Set filter on Address with AddressType
+ *
+ * <p>This key is used to resolve a private address from a public address.
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}. May be any type of address.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length
+ * @throws NullPointerException if {@code deviceAddress} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public Builder setDeviceAddress(@NonNull String deviceAddress,
+ @AddressType int addressType) {
+ return setDeviceAddressInternal(deviceAddress, addressType, null);
+ }
+
+ /**
+ * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+ *
+ * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from
+ * a PRIVATE_ADDRESS type.
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}. This Address type must only be PUBLIC OR RANDOM
+ * STATIC.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+ * @param irk non-null byte array representing the Identity Resolving Key
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException if the {@code irk} is invalid length.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not
+ * PUBLIC or RANDOM STATIC when an IRK is present.
+ * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public Builder setDeviceAddress(@NonNull String deviceAddress,
+ @AddressType int addressType,
+ @NonNull byte[] irk) {
+ Preconditions.checkNotNull(irk);
+ if (irk.length != LEN_IRK_OCTETS) {
+ throw new IllegalArgumentException("'irk' is invalid length!");
+ }
+ return setDeviceAddressInternal(deviceAddress, addressType, irk);
+ }
+
+ /**
+ * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+ *
+ * <p>Internal setter for the device address
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * @param irk non-null byte array representing the Identity Resolving Address; nullable
+ * internally.
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length.
+ * @throws NullPointerException if {@code deviceAddress} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ private Builder setDeviceAddressInternal(@NonNull String deviceAddress,
+ @AddressType int addressType,
+ @Nullable byte[] irk) {
+
+ // Make sure our deviceAddress is valid!
+ Preconditions.checkNotNull(deviceAddress);
+ if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
throw new IllegalArgumentException("invalid device address " + deviceAddress);
}
+
+ // Verify type range
+ if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC
+ || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+ throw new IllegalArgumentException("'addressType' is invalid!");
+ }
+
+ // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address.
+ if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+ // Don't want a bad combination of address and irk!
+ if (irk != null) {
+ // Since there are 3 possible RANDOM subtypes we must check to make sure
+ // the correct type of address is used.
+ if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) {
+ throw new IllegalArgumentException(
+ "Invalid combination: IRK requires either a PUBLIC or "
+ + "RANDOM (STATIC) Address");
+ }
+ }
+ }
+
+ // PUBLIC doesn't require extra work
+ // Without an IRK any address may be accepted
+
mDeviceAddress = deviceAddress;
+ mAddressType = addressType;
+ mIrk = irk;
return this;
}
@@ -727,7 +879,8 @@
mServiceUuid, mUuidMask, mServiceSolicitationUuid,
mServiceSolicitationUuidMask,
mServiceDataUuid, mServiceData, mServiceDataMask,
- mManufacturerId, mManufacturerData, mManufacturerDataMask);
+ mManufacturerId, mManufacturerData, mManufacturerDataMask,
+ mAddressType, mIrk);
}
}
}
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 504118e..368d1ee 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -52,6 +52,16 @@
public static final int SCAN_MODE_LOW_LATENCY = 2;
/**
+ * Perform Bluetooth LE scan in ambient discovery mode. This mode has lower duty cycle and more
+ * aggressive scan interval than balanced mode that provides a good trade-off between scan
+ * latency and power consumption.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3;
+
+ /**
* Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
* If no filter is active, all advertisement packets are reported.
*/
@@ -276,10 +286,17 @@
* @throws IllegalArgumentException If the {@code scanMode} is invalid.
*/
public Builder setScanMode(int scanMode) {
- if (scanMode < SCAN_MODE_OPPORTUNISTIC || scanMode > SCAN_MODE_LOW_LATENCY) {
- throw new IllegalArgumentException("invalid scan mode " + scanMode);
+ switch (scanMode) {
+ case SCAN_MODE_OPPORTUNISTIC:
+ case SCAN_MODE_LOW_POWER:
+ case SCAN_MODE_BALANCED:
+ case SCAN_MODE_LOW_LATENCY:
+ case SCAN_MODE_AMBIENT_DISCOVERY:
+ mScanMode = scanMode;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid scan mode " + scanMode);
}
- mScanMode = scanMode;
return this;
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b441b36..2ce7156 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -25,6 +25,7 @@
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 @@
}
/**
+ * 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 @@
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 95d3515..83db358 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -51,4 +51,6 @@
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/ComponentCallbacksController.java b/core/java/android/content/ComponentCallbacksController.java
new file mode 100644
index 0000000..a81aaf7
--- /dev/null
+++ b/core/java/android/content/ComponentCallbacksController.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A helper class to manage {@link ComponentCallbacks} and {@link ComponentCallbacks2}, such as
+ * registering ,unregistering {@link ComponentCallbacks} and sending callbacks to all registered
+ * {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ * @see ComponentCallbacks
+ * @see ComponentCallbacks2
+ *
+ * @hide
+ */
+public class ComponentCallbacksController {
+ @GuardedBy("mLock")
+ private List<ComponentCallbacks> mComponentCallbacks;
+
+ private final Object mLock = new Object();
+
+ /**
+ * Register the {@link ComponentCallbacks}.
+ *
+ * @see Context#registerComponentCallbacks(ComponentCallbacks)
+ */
+ public void registerCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null) {
+ mComponentCallbacks = new ArrayList<>();
+ }
+ mComponentCallbacks.add(callbacks);
+ }
+ }
+
+ /**
+ * Unregister the {@link ComponentCallbacks}.
+ *
+ * @see Context#unregisterComponentCallbacks(ComponentCallbacks)
+ */
+ public void unregisterCallbacks(@NonNull ComponentCallbacks callbacks) {
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ mComponentCallbacks.remove(callbacks);
+ }
+ }
+
+ /**
+ * Clear all registered {@link ComponentCallbacks}.
+ * It is useful when the associated {@link Context} is going to be released.
+ */
+ public void clearCallbacks() {
+ synchronized (mLock) {
+ if (mComponentCallbacks != null) {
+ mComponentCallbacks.clear();
+ }
+ }
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onConfigurationChanged(Configuration)} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ forAllComponentCallbacks(callbacks -> callbacks.onConfigurationChanged(newConfig));
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks#onLowMemory()} to all registered
+ * {@link ComponentCallbacks}.
+ */
+ public void dispatchLowMemory() {
+ forAllComponentCallbacks(ComponentCallbacks::onLowMemory);
+ }
+
+ /**
+ * Sending {@link ComponentCallbacks2#onTrimMemory(int)} to all registered
+ * {@link ComponentCallbacks2}.
+ */
+ public void dispatchTrimMemory(int level) {
+ forAllComponentCallbacks(callbacks -> {
+ if (callbacks instanceof ComponentCallbacks2) {
+ ((ComponentCallbacks2) callbacks).onTrimMemory(level);
+ }
+ });
+ }
+
+ private void forAllComponentCallbacks(Consumer<ComponentCallbacks> callbacksConsumer) {
+ final ComponentCallbacks[] callbacksArray;
+ synchronized (mLock) {
+ if (mComponentCallbacks == null || mComponentCallbacks.isEmpty()) {
+ return;
+ }
+ callbacksArray = new ComponentCallbacks[mComponentCallbacks.size()];
+ mComponentCallbacks.toArray(callbacksArray);
+ }
+ for (ComponentCallbacks callbacks : callbacksArray) {
+ callbacksConsumer.accept(callbacks);
+ }
+ }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 64ca92f..45a8ffd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -81,6 +81,7 @@
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;
@@ -655,12 +656,21 @@
/**
* Add a new {@link ComponentCallbacks} to the base application of the
* Context, which will be called at the same times as the ComponentCallbacks
- * methods of activities and other components are called. Note that you
+ * methods of activities and other components are called. Note that you
* <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
* appropriate in the future; this will not be removed for you.
+ * <p>
+ * After {@link Build.VERSION_CODES#S}, Registering the ComponentCallbacks to Context created
+ * via {@link #createWindowContext(int, Bundle)} or
+ * {@link #createWindowContext(Display, int, Bundle)} will receive
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} from Window Context rather
+ * than its base application. It is helpful if you want to handle UI components that
+ * associated with the Window Context when the Window Context has configuration changes.</p>
*
* @param callback The interface to call. This can be either a
* {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+ *
+ * @see Context#createWindowContext(int, Bundle)
*/
public void registerComponentCallbacks(ComponentCallbacks callback) {
getApplicationContext().registerComponentCallbacks(callback);
@@ -6358,6 +6368,16 @@
* windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
* </pre>
* <p>
+ * After {@link Build.VERSION_CODES#S}, window context provides the capability to listen to its
+ * {@link Configuration} changes by calling
+ * {@link #registerComponentCallbacks(ComponentCallbacks)}, while other kinds of {@link Context}
+ * will register the {@link ComponentCallbacks} to {@link #getApplicationContext() its
+ * Application context}. Note that window context only propagate
+ * {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback.
+ * {@link ComponentCallbacks#onLowMemory()} or other callbacks in {@link ComponentCallbacks2}
+ * won't be invoked.
+ * </p>
+ * <p>
* Note that using {@link android.app.Application} or {@link android.app.Service} context for
* UI-related queries may result in layout or continuity issues on devices with variable screen
* sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect
@@ -6774,4 +6794,15 @@
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 d0d406a..01b554a 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -8,3 +8,5 @@
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 = [email protected]
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index a2880df..614143e 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -44,9 +44,12 @@
},
{
"include-filter": "android.content.ContextTest"
+ },
+ {
+ "include-filter": "android.content.ComponentCallbacksControllerTest"
}
],
- "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"]
+ "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
}
]
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index feb58a30..0952b3e 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -550,9 +550,18 @@
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 66295eb..9283e5f 100644
--- a/core/java/android/content/pm/AppSearchPerson.java
+++ b/core/java/android/content/pm/AppSearchPerson.java
@@ -107,7 +107,7 @@
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 5af3b5a..b2478ca 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -40,6 +40,7 @@
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -216,9 +217,8 @@
@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())
@@ -327,12 +327,25 @@
return si;
}
+ /**
+ * @hide
+ */
+ @NonNull
+ public static List<GenericDocument> toGenericDocuments(
+ @NonNull final Collection<ShortcutInfo> shortcuts) {
+ final List<GenericDocument> docs = new ArrayList<>(shortcuts.size());
+ for (ShortcutInfo si : shortcuts) {
+ docs.add(AppSearchShortcutInfo.instance(si));
+ }
+ return docs;
+ }
+
/** @hide */
@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);
}
/**
@@ -560,16 +573,6 @@
/**
* @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 1a5dad5..5f3ec36 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1363,7 +1363,7 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
zygotePreloadName = source.readString8();
gwpAsanMode = source.readInt();
memtagMode = source.readInt();
- nativeHeapZeroInit = sForBoolean.unparcel(source);
+ nativeHeapZeroInitialized = source.readInt();
+ requestOptimizedExternalStorageAccess = sForBoolean.unparcel(source);
}
/**
@@ -2079,6 +2119,24 @@
}
/**
+ * @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 @@
/** {@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 @@
/** {@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 3dd5ee1..632c0f5 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -62,8 +62,7 @@
/**
* 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 @@
this.deniedPermissions = orig.deniedPermissions;
this.gwpAsanMode = orig.gwpAsanMode;
this.memtagMode = orig.memtagMode;
- this.nativeHeapZeroInit = orig.nativeHeapZeroInit;
+ this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
}
@@ -101,7 +100,7 @@
* @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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
};
@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]\[email protected](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]\[email protected](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 60f3218..23945ee 100644
--- a/core/java/android/content/pm/SuspendDialogInfo.java
+++ b/core/java/android/content/pm/SuspendDialogInfo.java
@@ -65,16 +65,20 @@
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 @@
}
/**
+ * @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 @@
}
/**
- * @return the text to be shown
+ * @return the text to be shown on the neutral button
* @hide
*/
@StringRes
@@ -157,6 +171,16 @@
}
/**
+ * @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 @@
}
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 @@
}
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 @@
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 @@
}
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 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 @@
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 @@
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 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 @@
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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 cfb6e1b..5a89708 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -321,6 +321,10 @@
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 ba6416d..1c65e00 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.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 @@
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 b3c26ab..60aac76 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -382,12 +382,18 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 9f52183..cfd828e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -887,20 +887,25 @@
* @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 2be0157..9f69d0c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2008,9 +2008,17 @@
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 d99c410..ff6aaad 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 @@
| 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 89fef9d..54a60d3 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 @@
@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 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 @@
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 @@
}
@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 @@
// 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 @@
// 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 @@
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 @@
};
@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]\[email protected](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]\[email protected](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 2579774..e417e74 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 @@
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 55e19f2..33920c6 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.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 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 @@
* @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 @@
* @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 @@
*/
@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 @@
*/
@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 @@
* @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 @@
* @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());
@@ -341,11 +316,11 @@
}
/**
- * Retrieve the user selection data for the given {@param packageName} and the current user.
+ * Retrieve the user state for the given package and the {@link Context}'s user.
*
* @param packageName The app to query state for.
- * @return the user selection verification data for the given package for the current user, or
- * null if the package does not declare any HTTP/HTTPS domains.
+ * @return The user selection verification data for the given package for the user, or null if
+ * the package does not declare any HTTP/HTTPS domains.
*/
@Nullable
public DomainVerificationUserState getDomainVerificationUserState(
@@ -405,4 +380,12 @@
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 aed0823..ac4b7b7 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -360,7 +360,7 @@
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 @@
* 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 @@
}
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 @@
* 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 @@
* @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 @@
*/
@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 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 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 @@
@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 @@
* <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 @@
* @hide
*/
public int[] getAllAttributes() {
- return mThemeImpl.getAllAttributes();
+ synchronized (mLock) {
+ return mThemeImpl.getAllAttributes();
+ }
}
/**
@@ -1738,7 +1768,9 @@
* @see ActivityInfo
*/
public @Config int getChangingConfigurations() {
- return mThemeImpl.getChangingConfigurations();
+ synchronized (mLock) {
+ return mThemeImpl.getChangingConfigurations();
+ }
}
/**
@@ -1749,23 +1781,31 @@
* @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 @@
*/
@ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true)
public String[] getTheme() {
- return mThemeImpl.getTheme();
+ synchronized (mLock) {
+ return mThemeImpl.getTheme();
+ }
}
/** @hide */
@@ -1800,7 +1842,9 @@
* {@link #applyStyle(int, boolean)}.
*/
public void rebase() {
- mThemeImpl.rebase();
+ synchronized (mLock) {
+ mThemeImpl.rebase();
+ }
}
/**
@@ -1862,12 +1906,14 @@
@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 cbcdb37..553e11b 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1314,22 +1314,16 @@
}
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 @@
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
- synchronized (mKey) {
- final int len = attrs.length;
- final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
+ 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;
- }
+ // 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 @@
}
@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 @@
* {@link #applyStyle(int, boolean)}.
*/
void rebase() {
- synchronized (mKey) {
- AssetManager.nativeThemeClear(mTheme);
+ 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);
- }
+ // 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 @@
@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 5eaa766..2a349e9 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -46,7 +46,7 @@
* 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 @@
}
/**
+ * 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 07ebbaf..6654c2c 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1210,6 +1210,25 @@
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 @@
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 @@
* <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 @@
* @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 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 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 b7b1a14..726bca4 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -507,6 +507,18 @@
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 @@
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 924dcee..d4da3b9 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -586,7 +586,7 @@
* 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 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 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 9ac2ff5..906256d 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1391,6 +1391,13 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
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 e7457e7..6ff68c1 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -812,6 +812,13 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
* 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 @@
* @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 @@
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 0a42981..2920e67 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.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 @@
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 4defd23..b578bf8 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@
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.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 @@
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 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 @@
mDeviceCallback = callback;
mDeviceExecutor = executor;
mCharacteristics = characteristics;
+ mPhysicalIdsToChars = physicalIdsToChars;
mAppTargetSdkVersion = appTargetSdkVersion;
mContext = ctx;
@@ -1357,11 +1364,71 @@
}
}
+ 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 @@
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 @@
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 0cdf744..aa84b02 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -327,6 +327,10 @@
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 @@
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 @@
});
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 8a0172e..34116aa 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -267,8 +267,8 @@
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 @@
"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 @@
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 @@
* @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 @@
}
/**
+ * 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 @@
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 @@
combTemplate.mDescription);
return null;
}
-
streamsInfo.add(streamInfo);
}
@@ -1220,6 +1326,14 @@
}
/**
+ * 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 e31bd60..84736dc 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -19,6 +19,7 @@
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.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.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 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 @@
mIsShared = false;
mPhysicalCameraId = null;
mIsMultiResolution = false;
+ mSensorPixelModesUsed = new ArrayList<Integer>();
}
/**
@@ -399,6 +412,7 @@
mIsShared = false;
mPhysicalCameraId = null;
mIsMultiResolution = false;
+ mSensorPixelModesUsed = new ArrayList<Integer>();
}
/**
@@ -485,6 +499,81 @@
}
/**
+ * 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 @@
this.mIsShared = other.mIsShared;
this.mPhysicalCameraId = other.mPhysicalCameraId;
this.mIsMultiResolution = other.mIsMultiResolution;
+ this.mSensorPixelModesUsed = other.mSensorPixelModesUsed;
}
/**
@@ -642,7 +732,8 @@
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 @@
}
mPhysicalCameraId = physicalCameraId;
mIsMultiResolution = isMultiResolutionOutput;
+ mSensorPixelModesUsed = sensorPixelModesUsed;
}
/**
@@ -766,6 +858,7 @@
dest.writeTypedList(mSurfaces);
dest.writeString(mPhysicalCameraId);
dest.writeInt(mIsMultiResolution ? 1 : 0);
+ dest.writeList(mSensorPixelModesUsed);
}
/**
@@ -798,7 +891,14 @@
!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 @@
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 @@
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 @@
// 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/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 01fd396..c83ccfa 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -223,6 +223,14 @@
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 3da1227..799ea4a 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -18,6 +18,7 @@
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 @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;
@@ -326,26 +255,24 @@
* @hide
*/
public static final int ALLOWED_METERED_REASON_USER_EXEMPTED = 1 << 16;
+ /**
+ * Flag to indicate that app is exempt from certain metered network restrictions because of it
+ * being a system component.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_METERED_REASON_SYSTEM = 1 << 17;
+ /**
+ * Flag to indicate that app is exempt from certain metered network restrictions because of it
+ * being in the foreground.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_METERED_REASON_FOREGROUND = 1 << 18;
/** @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;
@@ -869,14 +796,15 @@
* {@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;
}
@@ -899,7 +827,7 @@
*/
@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);
}
@@ -963,7 +891,7 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- default void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {}
+ default void onUidBlockedReasonChanged(int uid, int blockedReasons) {}
}
/** @hide */
@@ -978,8 +906,7 @@
}
@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);
}
@@ -987,7 +914,7 @@
}
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 4078b24..74c3ba4 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
@@ -23,6 +23,6 @@
*/
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 bd336dd..7eaa01e 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -26,6 +26,6 @@
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 7639d22..23fc069 100644
--- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
@@ -29,7 +29,8 @@
@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 @@
}
@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 @@
}
/**
- * 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 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 6a3cb42..5b79f73 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -29,7 +29,7 @@
*/
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 b73fdbf..abd41da 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -154,7 +154,7 @@
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 @@
* @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 @@
* 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 @@
@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 fa6472e..c47fc57 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1004,6 +1004,24 @@
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 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.
@@ -6030,7 +6066,7 @@
pw.print(":");
for (int it=0; it<types.size(); it++) {
pw.print(" ");
- pw.print(JobParameters.getReasonCodeDescription(types.keyAt(it)));
+ pw.print(JobParameters.getLegacyReasonCodeDescription(types.keyAt(it)));
pw.print("(");
pw.print(types.valueAt(it));
pw.print("x)");
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index f12eb88..a0721c3 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -34,7 +34,7 @@
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 @@
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 @@
}
/**
- * 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 @@
}
private BatteryUsageStats(@NonNull Parcel source) {
- mStatsStartRealtimeMs = source.readLong();
+ mStatsStartTimestampMs = source.readLong();
mConsumedPower = source.readDouble();
mDischargePercentage = source.readInt();
mDischargedPowerLowerBound = source.readDouble();
@@ -214,7 +215,7 @@
@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 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 @@
/**
* 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 9518bf1..85861bc 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -60,14 +60,18 @@
*/
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 @@
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 @@
dest.writeInt(mFlags);
dest.writeInt(mUserIds.length);
dest.writeIntArray(mUserIds);
+ dest.writeLong(mMaxStatsAgeMs);
}
@Override
@@ -132,6 +146,7 @@
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 @@
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 087568d..34f2c103f 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -99,6 +99,8 @@
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 b42a495..219912c 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -218,7 +218,7 @@
@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 b528eb1..841aad5 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -211,7 +211,7 @@
@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 5069e031..d4de4fa 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -25,6 +25,7 @@
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 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 217f178..cec323f 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -21,6 +21,8 @@
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 @@
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 @@
}
}
+ @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 89478fa..6311760 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 0199fad..c78bf8c 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -21,6 +21,7 @@
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.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 @@
* 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 @@
* @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 @@
* @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 @@
*/
@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 @@
* @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 <T extends VibrationEffect> T scale(float scaleFactor);
/**
- * Scale given vibration intensity by the given factor.
+ * Applies given effect strength to prebaked effects represented by one of
+ * VibrationEffect.EFFECT_*.
*
- * @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
- *
+ * @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 @@
}
}
- /** @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 static final class Composed extends VibrationEffect {
+ private final ArrayList<VibrationEffectSegment> mSegments;
+ private final int mRepeatIndex;
- public OneShot(Parcel in) {
- mDuration = in.readLong();
- mAmplitude = in.readInt();
+ Composed(@NonNull Parcel in) {
+ this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt());
}
- public OneShot(long milliseconds, int amplitude) {
- mDuration = milliseconds;
- mAmplitude = amplitude;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- public int getAmplitude() {
- return mAmplitude;
+ Composed(@NonNull VibrationEffectSegment segment) {
+ this(Arrays.asList(segment), /* repeatIndex= */ -1);
}
/** @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));
+ public Composed(@NonNull List<? extends VibrationEffectSegment> segments, int repeatIndex) {
+ super();
+ mSegments = new ArrayList<>(segments);
+ mRepeatIndex = repeatIndex;
}
- /** @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;
- }
-
- /** @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 void validate() {
+ 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 (!hasNonZeroDuration) {
+ throw new IllegalArgumentException("at least one timing must be non-zero"
+ + " (segments=" + mSegments + ")");
+ }
+ if (!hasNonZeroAmplitude) {
+ throw new IllegalArgumentException("at least one amplitude must be non-zero"
+ + " (segments=" + mSegments + ")");
+ }
+ 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 long getDuration() {
- if (mRepeat >= 0) {
+ if (mRepeatIndex >= 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;
+ 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;
}
- scaled = true;
- scaledAmplitudes[i] = scale(scaledAmplitudes[i], scaleFactor);
+ totalDuration += segmentDuration;
}
- if (!scaled) {
- // Just return this if no scaling was done.
- return this;
- }
- return new Waveform(mTimings, scaledAmplitudes, mRepeat);
+ return totalDuration;
}
- /** @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 + ")");
- }
- if (!hasNonZeroEntry(mTimings)) {
- 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) + ")");
- }
- }
- 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 (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 + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.Waveform)) {
- return false;
- }
- 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;
- }
- }
- 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;
- }
-
- /** @hide */
- @Override
- public Prebaked resolve(int defaultAmplitude) {
- if (mFallbackEffect != null) {
- VibrationEffect resolvedFallback = mFallbackEffect.resolve(defaultAmplitude);
- if (!mFallbackEffect.equals(resolvedFallback)) {
- return new Prebaked(mEffectId, mEffectStrength, resolvedFallback);
- }
- }
- return this;
- }
-
- /** @hide */
- @Override
- public Prebaked scale(float scaleFactor) {
- if (mFallbackEffect != null) {
- VibrationEffect scaledFallback = mFallbackEffect.scale(scaleFactor);
- if (!mFallbackEffect.equals(scaledFallback)) {
- return new Prebaked(mEffectId, mEffectStrength, scaledFallback);
- }
- }
- // 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;
- }
- }
-
- /** @hide */
- @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 + ")");
- }
- }
- if (!isValidEffectStrength(mEffectStrength)) {
- throw new IllegalArgumentException(
- "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
- }
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (!(o instanceof VibrationEffect.Prebaked)) {
- return false;
- }
- VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
- return mEffectId == other.mEffectId
- && mFallback == other.mFallback
- && mEffectStrength == other.mEffectStrength
- && Objects.equals(mFallbackEffect, other.mFallbackEffect);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mEffectId, mFallback, mEffectStrength, mFallbackEffect);
- }
-
- @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;
+ 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));
+ }
+ if (resolvedSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed resolved = new Composed(resolvedSegments, mRepeatIndex);
+ resolved.validate();
+ return resolved;
}
- /** @hide */
- @Override
- public VibrationEffect resolve(int defaultAmplitude) {
- // Primitive effects already have default primitive intensity set, so ignore this.
- return this;
- }
-
- /** @hide */
+ @NonNull
@Override
public Composed scale(float scaleFactor) {
- if (scaleFactor == 1f) {
- // Just return this if there's no scaling to be done.
+ int segmentCount = mSegments.size();
+ ArrayList<VibrationEffectSegment> scaledSegments = new ArrayList<>(segmentCount);
+ for (int i = 0; i < segmentCount; i++) {
+ scaledSegments.add(mSegments.get(i).scale(scaleFactor));
+ }
+ if (scaledSegments.equals(mSegments)) {
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);
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
}
- /** @hide */
+ @NonNull
@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");
+ 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 (scaledSegments.equals(mSegments)) {
+ return this;
+ }
+ Composed scaled = new Composed(scaledSegments, mRepeatIndex);
+ scaled.validate();
+ return scaled;
}
@Override
- public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeInt(PARCEL_TOKEN_COMPOSITION);
- out.writeList(mPrimitiveEffects);
+ public boolean equals(@Nullable Object o) {
+ if (!(o instanceof Composed)) {
+ return false;
+ }
+ Composed other = (Composed) o;
+ return mSegments.equals(other.mSegments) && mRepeatIndex == other.mRepeatIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSegments, mRepeatIndex);
+ }
+
+ @Override
+ public String toString() {
+ return "Composed{segments=" + mSegments
+ + ", repeat=" + mRepeatIndex
+ + "}";
}
@Override
@@ -1063,34 +696,20 @@
}
@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);
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeList(mSegments);
+ out.writeInt(mRepeatIndex);
}
- @Override
- public int hashCode() {
- return Objects.hash(mPrimitiveEffects);
- }
-
- @Override
- public String toString() {
- return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}';
- }
-
- 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 @@
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 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 @@
* @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 @@
* @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 @@
* @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 @@
*/
@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 @@
* @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 @@
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;
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public WaveformBuilder addStep(@FloatRange(from = 0f, to = 1f) float amplitude,
+ @IntRange(from = 0) int duration) {
+ return addStep(amplitude, getPreviousFrequency(), duration);
+ }
- PrimitiveEffect(int id, float scale, int delay) {
- this.id = id;
- this.scale = scale;
- this.delay = 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(id);
- dest.writeFloat(scale);
- dest.writeInt(delay);
+ 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;
+ }
- @Override
- public int describeContents() {
- return 0;
+ 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();
+ }
}
-
- @Override
- public String toString() {
- return "PrimitiveEffect{"
- + "id=" + primitiveToString(id)
- + ", scale=" + scale
- + ", delay=" + delay
- + '}';
- }
-
- @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;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, scale, delay);
- }
-
-
- 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];
- }
- };
+ 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 b90d438..a0f70c8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -467,7 +467,7 @@
*/
@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 @@
* @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 3121b95..64e51e7 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 @@
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 @@
dest.writeLong(mCapabilities);
dest.writeSparseBooleanArray(mSupportedEffects);
dest.writeSparseBooleanArray(mSupportedPrimitives);
- dest.writeFloat(mResonantFrequency);
dest.writeFloat(mQFactor);
+ dest.writeParcelable(mFrequencyMapping, flags);
}
@Override
@@ -92,14 +96,14 @@
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 @@
+ ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
+ ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
- + ", mResonantFrequency=" + mResonantFrequency
+ ", mQFactor=" + mQFactor
+ + ", mFrequencyMapping=" + mFrequencyMapping
+ '}';
}
@@ -153,7 +157,8 @@
* @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 @@
* this vibrator is a composite of multiple physical devices.
*/
public float getResonantFrequency() {
- return mResonantFrequency;
+ return mFrequencyMapping.mResonantFrequencyHz;
}
/**
@@ -189,6 +194,52 @@
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 @@
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 98b4e0b4..9385402 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -201,4 +201,6 @@
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 c967deb..8107168 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2124,6 +2124,52 @@
}
}
+
+ /** @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 @@
}
/**
+ * 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 @@
}
}
+ /**
+ * 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 b5abe2a..36177c4 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 @@
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 0000000..78b4346
--- /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 0000000..2ef29cb
--- /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 0000000..aad87c5
--- /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 0000000..11209e0
--- /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 0000000..5b42845
--- /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 c94c0ff..440d6f2 100644
--- a/core/java/android/permission/PermGroupUsage.java
+++ b/core/java/android/permission/PermGroupUsage.java
@@ -18,6 +18,7 @@
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 @@
*
* @hide
*/
+@TestApi
public final class PermGroupUsage {
private final String mPackageName;
@@ -36,7 +38,19 @@
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 @@
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 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 177e422..baa25f0 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -26,6 +26,7 @@
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 @@
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 @@
* 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 @@
}
/** @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 @@
@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 @@
*
* @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 80a3e16..2d6fa3c 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -71,13 +71,10 @@
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 @@
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 @@
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 @@
}
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 e9bbcc79..03b5a2e 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -108,6 +108,13 @@
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 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
@@ -358,6 +373,38 @@
public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
/**
+ * Namespace for all statsd java features that can be applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
+
+ /**
+ * Namespace for all statsd java features that are applied on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
+
+ /**
+ * Namespace for all statsd native features that can be applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
+
+ /**
+ * Namespace for all statsd native features that are applied on boot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
+
+ /**
* Namespace for storage-related features.
*
* @deprecated Replace storage namespace with storage_native_boot.
@@ -456,7 +503,8 @@
*/
@NonNull
private static final List<String> PUBLIC_NAMESPACES =
- Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME);
+ Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
+ NAMESPACE_STATSD_JAVA_BOOT);
/**
* Privacy related properties definitions.
*
@@ -505,38 +553,6 @@
"connectivity_thermal_power_manager";
/**
- * Namespace for all statsd java features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
-
- /**
- * Namespace for all statsd java features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
-
- /**
- * Namespace for all statsd native features that can be applied immediately.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
-
- /**
- * Namespace for all statsd native features that are applied on boot.
- *
- * @hide
- */
- @SystemApi
- public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
-
- /**
* Namespace for configuration related features.
*
* @hide
@@ -646,7 +662,7 @@
* @param name The name of the property to look up.
* @param defaultValue The value to return if the property does not exist or has no non-null
* value.
- * @return the corresponding value, or defaultValue if none exists.
+ * @return the correspondfing value, or defaultValue if none exists.
* @hide
*/
@SystemApi
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 0fea484..620fa65 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -236,10 +236,21 @@
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 71ffa92..1f3c415 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -470,8 +470,8 @@
* 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 @@
* 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 @@
"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 @@
"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.
@@ -13112,7 +13135,7 @@
* @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 374de9c..38945f5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4559,6 +4559,15 @@
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 @@
* 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 @@
* @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 04a4ca4..13274c6 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.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 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 @@
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 @@
@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 23a1a3f..d88e094 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -31,4 +31,5 @@
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 0000000..6a4d2b8
--- /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 []\[email protected](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 0000000..a47105a
--- /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 0000000..b8a8cde
--- /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 aeeaa97..d9a310f 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -618,7 +618,7 @@
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/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index bf5f24d..09c3b2e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -251,6 +251,10 @@
public static final int REASON_SNOOZED = 18;
/** Notification was canceled due to timeout */
public static final int REASON_TIMEOUT = 19;
+ /** Notification was canceled due to the backing channel being deleted */
+ public static final int REASON_CHANNEL_REMOVED = 20;
+ /** Notification was canceled due to the app's storage being cleared */
+ public static final int REASON_CLEAR_DATA = 21;
/**
* @hide
diff --git a/core/java/android/service/notification/ScheduleCalendar.java b/core/java/android/service/notification/ScheduleCalendar.java
index 6ed966e..314c97d 100644
--- a/core/java/android/service/notification/ScheduleCalendar.java
+++ b/core/java/android/service/notification/ScheduleCalendar.java
@@ -57,25 +57,18 @@
}
/**
- * Sets next alarm of the schedule if the saved next alarm has passed or is further
- * in the future than given nextAlarm
+ * Sets next alarm of the schedule
* @param now current time in milliseconds
* @param nextAlarm time of next alarm in milliseconds
*/
public void maybeSetNextAlarm(long now, long nextAlarm) {
if (mSchedule != null && mSchedule.exitAtAlarm) {
- // alarm canceled
if (nextAlarm == 0) {
+ // alarm canceled
mSchedule.nextAlarm = 0;
- }
- // only allow alarms in the future
- if (nextAlarm > now) {
- if (mSchedule.nextAlarm == 0 || mSchedule.nextAlarm < now) {
- mSchedule.nextAlarm = nextAlarm;
- } else {
- // store earliest alarm
- mSchedule.nextAlarm = Math.min(mSchedule.nextAlarm, nextAlarm);
- }
+ } else if (nextAlarm > now) {
+ // only allow alarms in the future
+ mSchedule.nextAlarm = nextAlarm;
} else if (mSchedule.nextAlarm < now) {
if (DEBUG) {
Log.d(TAG, "All alarms are in the past " + mSchedule.nextAlarm);
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
index 8d798c3..297f00a4 100644
--- a/core/java/android/service/translation/ITranslationService.aidl
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -16,7 +16,8 @@
package android.service.translation;
-import android.view.translation.TranslationSpec;
+import android.os.ResultReceiver;
+import android.view.translation.TranslationContext;
import com.android.internal.os.IResultReceiver;
/**
@@ -31,6 +32,9 @@
oneway interface ITranslationService {
void onConnected();
void onDisconnected();
- void onCreateTranslationSession(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
- int sessionId, in IResultReceiver receiver);
+ void onCreateTranslationSession(in TranslationContext translationContext, int sessionId,
+ in IResultReceiver receiver);
+
+ void onTranslationCapabilitiesRequest(int sourceFormat, int targetFormat,
+ in ResultReceiver receiver);
}
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
index c5f1f04..7edf2e2 100644
--- a/core/java/android/service/translation/TranslationService.java
+++ b/core/java/android/service/translation/TranslationService.java
@@ -34,8 +34,12 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.ArraySet;
import android.util.Log;
import android.view.translation.ITranslationDirectManager;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationManager;
import android.view.translation.TranslationRequest;
import android.view.translation.TranslationResponse;
@@ -43,6 +47,9 @@
import com.android.internal.os.IResultReceiver;
+import java.util.Set;
+import java.util.function.Consumer;
+
/**
* Service for translating text.
* @hide
@@ -92,10 +99,20 @@
}
@Override
- public void onCreateTranslationSession(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ public void onCreateTranslationSession(TranslationContext translationContext,
int sessionId, IResultReceiver receiver) throws RemoteException {
mHandler.sendMessage(obtainMessage(TranslationService::handleOnCreateTranslationSession,
- TranslationService.this, sourceSpec, destSpec, sessionId, receiver));
+ TranslationService.this, translationContext, sessionId, receiver));
+ }
+
+ @Override
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ mHandler.sendMessage(
+ obtainMessage(TranslationService::handleOnTranslationCapabilitiesRequest,
+ TranslationService.this, sourceFormat, targetFormat,
+ resultReceiver));
}
};
@@ -194,14 +211,13 @@
/**
* TODO: fill in javadoc.
*
- * @param sourceSpec
- * @param destSpec
+ * @param translationContext
* @param sessionId
*/
// TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
// solution to make it's safe.
- public abstract void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId);
+ public abstract void onCreateTranslationSession(@NonNull TranslationContext translationContext,
+ int sessionId);
/**
* TODO: fill in javadoc.
@@ -222,12 +238,27 @@
@NonNull CancellationSignal cancellationSignal,
@NonNull OnTranslationResultCallback callback);
+ /**
+ * TODO: fill in javadoc
+ * TODO: make this abstract again once aiai is ready.
+ *
+ * <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p>
+ *
+ * @param sourceFormat
+ * @param targetFormat
+ * @param callback
+ */
+ public abstract void onTranslationCapabilitiesRequest(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull Consumer<Set<TranslationCapability>> callback);
+
// TODO(b/176464808): Need to handle client dying case
- // TODO(b/176464808): Need to handle the failure case. e.g. if the specs does not support
+ // TODO(b/176464808): Need to handle the failure case. e.g. if the context is not supported.
- private void handleOnCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ private void handleOnCreateTranslationSession(@NonNull TranslationContext translationContext,
+ int sessionId, IResultReceiver resultReceiver) {
try {
final Bundle extras = new Bundle();
extras.putBinder(EXTRA_SERVICE_BINDER, mClientInterface.asBinder());
@@ -236,6 +267,24 @@
} catch (RemoteException e) {
Log.w(TAG, "RemoteException sending client interface: " + e);
}
- onCreateTranslationSession(sourceSpec, destSpec, sessionId);
+ onCreateTranslationSession(translationContext, sessionId);
+ }
+
+ private void handleOnTranslationCapabilitiesRequest(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ onTranslationCapabilitiesRequest(sourceFormat, targetFormat,
+ new Consumer<Set<TranslationCapability>>() {
+ @Override
+ public void accept(Set<TranslationCapability> values) {
+ final ArraySet<TranslationCapability> capabilities = new ArraySet<>(values);
+ final Bundle bundle = new Bundle();
+ bundle.putParcelableArray(TranslationManager.EXTRA_CAPABILITIES,
+ capabilities.toArray(new TranslationCapability[0]));
+ resultReceiver.send(TranslationManager.STATUS_SYNC_CALL_SUCCESS, bundle);
+ }
+ });
+
}
}
diff --git a/core/java/android/service/translation/TranslationServiceInfo.java b/core/java/android/service/translation/TranslationServiceInfo.java
index 18cc29d..c7017b2 100644
--- a/core/java/android/service/translation/TranslationServiceInfo.java
+++ b/core/java/android/service/translation/TranslationServiceInfo.java
@@ -100,9 +100,9 @@
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 def13db..94ca68f 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -41,10 +41,11 @@
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 @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 @@
// 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 @@
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 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 @@
* @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 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 @@
mTargetSdkVersion = targetSdkVersion;
mSupportHotwordDetectionService = supportHotwordDetectionService;
if (mSupportHotwordDetectionService) {
- setHotwordDetectionServiceConfig(options, sharedMemory);
+ updateState(options, sharedMemory);
}
try {
Identity identity = new Identity();
@@ -533,30 +573,28 @@
/**
* 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 @@
}
@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 @@
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 fcef26f..db984c2 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -27,11 +27,11 @@
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 @@
}
@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 @@
/**
* 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 @@
}
/**
- * 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 bb95bb8..6f641e1 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 @@
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 8f0874a..0791f1c 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 @@
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 9ba39a1..cb3791d 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -33,6 +33,7 @@
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 @@
* @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 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 @@
@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 d47ae27..1da7dc4 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -343,11 +343,14 @@
/**
* 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}).
+ * 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
+ * @see #onDisplayInfoChanged
* @deprecated Use {@link TelephonyCallback.DisplayInfoListener} instead.
*/
@Deprecated
@@ -981,8 +984,12 @@
* <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 d000000..18949cd 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -1057,7 +1057,6 @@
*
* @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 919c6e5..913ceae 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -71,7 +71,7 @@
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/Slog.java b/core/java/android/util/Slog.java
index d3eaeae..2a43222 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -26,6 +26,10 @@
import java.util.Locale;
/**
+ * API for sending log output to the {@link Log#LOG_ID_SYSTEM} buffer.
+ *
+ * <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
+ *
* @hide
*/
public final class Slog {
@@ -51,6 +55,12 @@
/**
* Logs a {@link Log.VERBOSE} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void v(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.VERBOSE)) return;
@@ -71,6 +81,12 @@
/**
* Logs a {@link Log.DEBUG} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void d(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.DEBUG)) return;
@@ -90,6 +106,12 @@
/**
* Logs a {@link Log.INFO} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void i(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.INFO)) return;
@@ -114,6 +136,12 @@
/**
* Logs a {@link Log.WARN} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void w(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -123,6 +151,12 @@
/**
* Logs a {@link Log.WARN} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.WARN)) return;
@@ -143,6 +177,12 @@
/**
* Logs a {@link Log.ERROR} message.
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void e(String tag, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.ERROR)) return;
@@ -152,6 +192,12 @@
/**
* Logs a {@link Log.ERROR} message with an exception
+ *
+ * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
+ * enabled for the given {@code tag}, but the compiler will still create an intermediate array
+ * of the objects for the {@code vargars}, which could affect garbage collection. So, if you're
+ * calling this method in a critical path, make sure to explicitly do the check before calling
+ * it.
*/
public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
if (!Log.isLoggable(tag, Log.ERROR)) return;
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
new file mode 100644
index 0000000..dc93a47
--- /dev/null
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * SparseDoubleArrays map integers to doubles. Unlike a normal array of doubles,
+ * there can be gaps in the indices. It is intended to be more memory efficient
+ * than using a HashMap to map Integers to Doubles, both because it avoids
+ * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
+ * for each mapping.
+ *
+ * <p>Note that this container keeps its mappings in an array data structure,
+ * using a binary search to find keys. The implementation is not intended to be appropriate for
+ * data structures
+ * that may contain large numbers of items. It is generally slower than a traditional
+ * HashMap, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.</p>
+ *
+ * <p>It is possible to iterate over the items in this container using
+ * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
+ * <code>keyAt(int)</code> with ascending values of the index will return the
+ * keys in ascending order, or the values corresponding to the keys in ascending
+ * order in the case of <code>valueAt(int)</code>.</p>
+ *
+ * @see SparseLongArray
+ *
+ * @hide
+ */
+public class SparseDoubleArray implements Cloneable {
+ /**
+ * The int->double map, but storing the doubles as longs using
+ * {@link Double#doubleToRawLongBits(double)}.
+ */
+ private SparseLongArray mValues;
+
+ /** Creates a new SparseDoubleArray containing no mappings. */
+ public SparseDoubleArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseDoubleArray, containing no mappings, that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings. If you supply an initial capacity of 0, the
+ * sparse array will be initialized with a light-weight representation
+ * not requiring any additional array allocations.
+ */
+ public SparseDoubleArray(int initialCapacity) {
+ mValues = new SparseLongArray(initialCapacity);
+ }
+
+ @Override
+ public SparseDoubleArray clone() {
+ SparseDoubleArray clone = null;
+ try {
+ clone = (SparseDoubleArray) super.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse) {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Gets the double mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public double get(int key) {
+ final int index = mValues.indexOfKey(key);
+ if (index < 0) {
+ return 0.0d;
+ }
+ return valueAt(index);
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(int key, double value) {
+ mValues.put(key, Double.doubleToRawLongBits(value));
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * <b>adding</b> its value to the previous mapping from the specified key if there
+ * was one.
+ *
+ * <p>This differs from {@link #put} because instead of replacing any previous value, it adds
+ * (in the numerical sense) to it.
+ */
+ public void add(int key, double summand) {
+ final double oldValue = get(key);
+ put(key, oldValue + summand);
+ }
+
+ /** Returns the number of key-value mappings that this SparseDoubleArray currently stores. */
+ public int size() {
+ return mValues.size();
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseDoubleArray stores.
+ *
+ * @see SparseLongArray#keyAt(int)
+ */
+ public int keyAt(int index) {
+ return mValues.keyAt(index);
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseDoubleArray stores.
+ *
+ * @see SparseLongArray#valueAt(int)
+ */
+ public double valueAt(int index) {
+ return Double.longBitsToDouble(mValues.valueAt(index));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation composes a string by iterating over its mappings.
+ */
+ @Override
+ public String toString() {
+ if (size() <= 0) {
+ return "{}";
+ }
+
+ StringBuilder buffer = new StringBuilder(size() * 34);
+ buffer.append('{');
+ for (int i = 0; i < size(); i++) {
+ if (i > 0) {
+ buffer.append(", ");
+ }
+ int key = keyAt(i);
+ buffer.append(key);
+ buffer.append('=');
+ double value = valueAt(i);
+ buffer.append(value);
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index b0916d3..f2bc0c5 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -164,7 +164,7 @@
}
/**
- * 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 @@
}
/**
- * 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/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index b28cfb8..2fcaec9 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -130,6 +130,16 @@
public abstract void triggerManagerServiceDump(String where);
/**
+ * Being called while taking a bugreport so that tracing files can be included in the bugreport
+ * when the IME tracing is running. Does nothing otherwise.
+ *
+ * @param pw Print writer
+ */
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ // does nothing by default.
+ }
+
+ /**
* Sets whether ime tracing is enabled.
*
* @param enabled Tells whether ime tracing should be enabled or disabled.
@@ -153,11 +163,6 @@
}
/**
- * Writes the current tracing data to the specific output proto file.
- */
- public abstract void writeTracesToFiles();
-
- /**
* Starts a new IME trace if one is not already started.
*
* @param pw Print writer
@@ -171,14 +176,6 @@
*/
public abstract void stopTrace(@Nullable PrintWriter pw);
- /**
- * Stops the IME trace if one was previously started.
- *
- * @param pw Print writer
- * @param writeToFile If the current buffer should be written to disk or not
- */
- public abstract void stopTrace(@Nullable PrintWriter pw, boolean writeToFile);
-
private static boolean isSystemProcess() {
return ActivityThread.isSystem();
}
diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java
index 35a81b7..17cdc46 100644
--- a/core/java/android/util/imetracing/ImeTracingClientImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java
@@ -99,18 +99,10 @@
}
@Override
- public void writeTracesToFiles() {
- }
-
- @Override
public void startTrace(PrintWriter pw) {
}
@Override
public void stopTrace(PrintWriter pw) {
}
-
- @Override
- public void stopTrace(PrintWriter pw, boolean writeToFile) {
- }
}
diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java
index 77f017a..06e4c50 100644
--- a/core/java/android/util/imetracing/ImeTracingServerImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java
@@ -139,14 +139,6 @@
}
}
- @GuardedBy("mEnabledLock")
- @Override
- public void writeTracesToFiles() {
- synchronized (mEnabledLock) {
- writeTracesToFilesLocked();
- }
- }
-
private void writeTracesToFilesLocked() {
try {
ProtoOutputStream clientsProto = new ProtoOutputStream();
@@ -192,12 +184,6 @@
@Override
public void stopTrace(@Nullable PrintWriter pw) {
- stopTrace(pw, true /* writeToFile */);
- }
-
- @GuardedBy("mEnabledLock")
- @Override
- public void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) {
if (IS_USER) {
Log.w(TAG, "Warn: Tracing is not supported on user builds.");
return;
@@ -213,9 +199,35 @@
+ TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
+ TRACE_FILENAME_IMMS);
sEnabled = false;
- if (writeToFile) {
- writeTracesToFilesLocked();
+ writeTracesToFilesLocked();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ if (IS_USER) {
+ return;
+ }
+ synchronized (mEnabledLock) {
+ if (!isAvailable() || !isEnabled()) {
+ return;
}
+ // Temporarily stop accepting logs from trace event providers. There is a small chance
+ // that we may drop some trace events while writing the file, but we currently need to
+ // live with that. Note that addToBuffer() also has a bug that it doesn't do
+ // read-acquire so flipping sEnabled here doesn't even guarantee that addToBuffer() will
+ // temporarily stop accepting incoming events...
+ // TODO(b/175761228): Implement atomic snapshot to avoid downtime.
+ // TODO(b/175761228): Fix synchronization around sEnabled.
+ sEnabled = false;
+ logAndPrintln(pw, "Writing traces in " + TRACE_DIRNAME + ": "
+ + TRACE_FILENAME_CLIENTS + ", " + TRACE_FILENAME_IMS + ", "
+ + TRACE_FILENAME_IMMS);
+ writeTracesToFilesLocked();
+ sEnabled = true;
}
}
diff --git a/core/java/android/uwb/AngleMeasurement.java b/core/java/android/uwb/AngleMeasurement.java
index 8c771ba..3d60373 100644
--- a/core/java/android/uwb/AngleMeasurement.java
+++ b/core/java/android/uwb/AngleMeasurement.java
@@ -48,7 +48,10 @@
* @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 a42126f..e1f13f2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -777,11 +777,11 @@
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 f4d5a7b..98b7dbf 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -50,10 +50,6 @@
super(ITYPE_IME, state, transactionSupplier, controller);
}
- public void applyImeVisibility(boolean setVisible) {
- mController.applyImeVisibility(setVisible);
- }
-
@Override
public void onWindowFocusGained() {
super.onWindowFocusGained();
diff --git a/core/java/android/view/InputEventAssigner.java b/core/java/android/view/InputEventAssigner.java
index c159a12..7fac6c5 100644
--- a/core/java/android/view/InputEventAssigner.java
+++ b/core/java/android/view/InputEventAssigner.java
@@ -45,13 +45,13 @@
public class InputEventAssigner {
private static final String TAG = "InputEventAssigner";
private boolean mHasUnprocessedDown = false;
- private int mEventId = INVALID_INPUT_EVENT_ID;
+ private int mDownEventId = INVALID_INPUT_EVENT_ID;
/**
- * Notify InputEventAssigner that the Choreographer callback has been processed. This will reset
- * the 'down' state to assign the latest input event to the current frame.
+ * Notify InputEventAssigner that a frame has been processed. We no longer need to keep track of
+ * the DOWN event because a frame has already been produced for it.
*/
- public void onChoreographerCallback() {
+ public void notifyFrameProcessed() {
// Mark completion of this frame. Use newest input event from now on.
mHasUnprocessedDown = false;
}
@@ -62,31 +62,22 @@
* @return the id of the input event to use for the current frame
*/
public int processEvent(InputEvent event) {
- if (event instanceof KeyEvent) {
- // We will not do any special handling for key events
- return event.getId();
- }
-
if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
- final int action = motionEvent.getActionMasked();
-
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- mHasUnprocessedDown = false;
+ if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN)) {
+ final int action = motionEvent.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mHasUnprocessedDown = true;
+ mDownEventId = event.getId();
+ }
+ if (mHasUnprocessedDown && action == MotionEvent.ACTION_MOVE) {
+ return mDownEventId;
+ }
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mHasUnprocessedDown = false;
+ }
}
- if (motionEvent.isFromSource(SOURCE_TOUCHSCREEN) && action == MotionEvent.ACTION_DOWN) {
- mHasUnprocessedDown = true;
- mEventId = event.getId();
- // This will remain 'true' even if we receive a MOVE event, as long as choreographer
- // hasn't invoked the 'CALLBACK_INPUT' callback.
- }
- // Don't update the event id if we haven't processed DOWN yet.
- if (!mHasUnprocessedDown) {
- mEventId = event.getId();
- }
- return mEventId;
}
-
- throw new IllegalArgumentException("Received unexpected " + event);
+ return event.getId();
}
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7a61df8..a67ec10 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -269,7 +269,7 @@
}
mShownOnFinish = shown;
mFinished = true;
- setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */,
+ setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, mPendingAlpha, 1f /* fraction */,
true /* allowWhenFinished */);
if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8bf78db3..a68f528 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -189,16 +189,34 @@
}
private static final String TAG = "InsetsController";
- private static final int ANIMATION_DURATION_SHOW_MS = 275;
- private static final int ANIMATION_DURATION_HIDE_MS = 340;
+ private static final int ANIMATION_DURATION_MOVE_IN_MS = 275;
+ private static final int ANIMATION_DURATION_MOVE_OUT_MS = 340;
+ private static final int ANIMATION_DURATION_FADE_IN_MS = 500;
+ private static final int ANIMATION_DURATION_FADE_OUT_MS = 1500;
+
+ private static final int ANIMATION_DELAY_DIM_MS = 500;
private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
- public static final Interpolator SYSTEM_BARS_INTERPOLATOR =
+ private static final Interpolator SYSTEM_BARS_INSETS_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ private static final Interpolator SYSTEM_BARS_ALPHA_INTERPOLATOR =
+ new PathInterpolator(0.3f, 0f, 1f, 1f);
+ private static final Interpolator SYSTEM_BARS_DIM_INTERPOLATOR = alphaFraction -> {
+ // While playing dim animation, alphaFraction is changed from 1f to 0f. Here changes it to
+ // time-based fraction for computing delay and interpolation.
+ float fraction = 1 - alphaFraction;
+ final float fractionDelay = (float) ANIMATION_DELAY_DIM_MS / ANIMATION_DURATION_FADE_OUT_MS;
+ if (fraction <= fractionDelay) {
+ return 1f;
+ } else {
+ float innerFraction = (fraction - fractionDelay) / (1f - fractionDelay);
+ return 1f - SYSTEM_BARS_ALPHA_INTERPOLATOR.getInterpolation(innerFraction);
+ }
+ };
private static final Interpolator SYNC_IME_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 0f, 1f);
private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
@@ -206,6 +224,9 @@
private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 1f, 1f);
+ /** The amount IME will move up/down when animating in floating mode. */
+ private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;
+
static final boolean DEBUG = false;
static final boolean WARN = false;
@@ -278,14 +299,12 @@
public static class InternalAnimationControlListener
implements WindowInsetsAnimationControlListener {
- /** The amount IME will move up/down when animating in floating mode. */
- protected static final int FLOATING_IME_BOTTOM_INSET = -80;
-
private WindowInsetsAnimationController mController;
private ValueAnimator mAnimator;
private final boolean mShow;
private final boolean mHasAnimationCallbacks;
private final @InsetsType int mRequestedTypes;
+ private final @Behavior int mBehavior;
private final long mDurationMs;
private final boolean mDisable;
private final int mFloatingImeBottomInset;
@@ -301,10 +320,12 @@
};
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
- int requestedTypes, boolean disable, int floatingImeBottomInset) {
+ @InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
+ int floatingImeBottomInset) {
mShow = show;
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
+ mBehavior = behavior;
mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
@@ -335,7 +356,7 @@
Insets end = mShow
? controller.getShownStateInsets()
: hiddenInsets;
- Interpolator insetsInterpolator = getInterpolator();
+ Interpolator insetsInterpolator = getInsetsInterpolator();
Interpolator alphaInterpolator = getAlphaInterpolator();
mAnimator.addUpdateListener(animation -> {
float rawFraction = animation.getAnimatedFraction();
@@ -379,7 +400,7 @@
+ mRequestedTypes);
}
- Interpolator getInterpolator() {
+ protected Interpolator getInsetsInterpolator() {
if ((mRequestedTypes & ime()) != 0) {
if (mHasAnimationCallbacks) {
return SYNC_IME_INTERPOLATOR;
@@ -389,7 +410,12 @@
return FAST_OUT_LINEAR_IN_INTERPOLATOR;
}
} else {
- return SYSTEM_BARS_INTERPOLATOR;
+ if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+ return SYSTEM_BARS_INSETS_INTERPOLATOR;
+ } else {
+ // Makes insets stay at the shown position.
+ return input -> mShow ? 1f : 0f;
+ }
}
}
@@ -405,7 +431,15 @@
return FAST_OUT_LINEAR_IN_INTERPOLATOR;
}
} else {
- return input -> 1f;
+ if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+ return input -> 1f;
+ } else {
+ if (mShow) {
+ return SYSTEM_BARS_ALPHA_INTERPOLATOR;
+ } else {
+ return SYSTEM_BARS_DIM_INTERPOLATOR;
+ }
+ }
}
}
@@ -429,7 +463,11 @@
return ANIMATION_DURATION_UNSYNC_IME_MS;
}
} else {
- return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
+ if (mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) {
+ return mShow ? ANIMATION_DURATION_MOVE_IN_MS : ANIMATION_DURATION_MOVE_OUT_MS;
+ } else {
+ return mShow ? ANIMATION_DURATION_FADE_IN_MS : ANIMATION_DURATION_FADE_OUT_MS;
+ }
}
}
}
@@ -882,7 +920,8 @@
hide(types, false /* fromIme */);
}
- void hide(@InsetsType int types, boolean fromIme) {
+ @VisibleForTesting
+ public void hide(@InsetsType int types, boolean fromIme) {
if (fromIme) {
ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
mHost.getInputMethodManager(), null /* icProto */);
@@ -1274,19 +1313,6 @@
getSourceConsumer(ITYPE_IME).onWindowFocusLost();
}
- /**
- * Used by {@link ImeInsetsSourceConsumer} when IME decides to be shown/hidden.
- * @hide
- */
- @VisibleForTesting
- public void applyImeVisibility(boolean setVisible) {
- if (setVisible) {
- show(Type.IME, true /* fromIme */);
- } else {
- hide(Type.IME);
- }
- }
-
@VisibleForTesting
public @AnimationType int getAnimationType(@InternalInsetsType int type) {
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
@@ -1358,14 +1384,14 @@
boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
final InternalAnimationControlListener listener = new InternalAnimationControlListener(
- show, hasAnimationCallbacks, types, skipAnim || mAnimationsDisabled,
- mHost.dipToPx(InternalAnimationControlListener.FLOATING_IME_BOTTOM_INSET));
+ show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
+ skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
// We are about to playing the default animation (show/hide). Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
controlAnimationUnchecked(
types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
- listener.getDurationMs(), listener.getInterpolator(),
+ listener.getDurationMs(), listener.getInsetsInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
!hasAnimationCallbacks /* useInsetsAnimationThread */);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 6801c27..dea32cd 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 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 @@
* @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 @@
* @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 @@
* @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 @@
* @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/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index 05636de..bd20f5b 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -26,6 +26,9 @@
import com.android.internal.R;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* The top line of content in a notification view.
* This includes the text views and badges but excludes the icon and the expander.
@@ -34,17 +37,23 @@
*/
@RemoteViews.RemoteView
public class NotificationTopLineView extends ViewGroup {
+ private final OverflowAdjuster mOverflowAdjuster = new OverflowAdjuster();
private final int mGravityY;
private final int mChildMinWidth;
+ private final int mChildHideWidth;
@Nullable private View mAppName;
@Nullable private View mTitle;
private View mHeaderText;
+ private View mHeaderTextDivider;
private View mSecondaryHeaderText;
+ private View mSecondaryHeaderTextDivider;
private OnClickListener mFeedbackListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
private View mFeedbackIcon;
private int mHeaderTextMarginEnd;
+ private Set<View> mViewsToDisappear = new HashSet<>();
+
private int mMaxAscent;
private int mMaxDescent;
@@ -66,6 +75,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = getResources();
mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+ mChildHideWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_hide_width);
// NOTE: Implementation only supports TOP, BOTTOM, and CENTER_VERTICAL gravities,
// with CENTER_VERTICAL being the default.
@@ -88,7 +98,9 @@
mAppName = findViewById(R.id.app_name_text);
mTitle = findViewById(R.id.title);
mHeaderText = findViewById(R.id.header_text);
+ mHeaderTextDivider = findViewById(R.id.header_text_divider);
mSecondaryHeaderText = findViewById(R.id.header_text_secondary);
+ mSecondaryHeaderTextDivider = findViewById(R.id.header_text_secondary_divider);
mFeedbackIcon = findViewById(R.id.feedback);
}
@@ -125,48 +137,37 @@
maxChildHeight = Math.max(maxChildHeight, childHeight);
}
+ mViewsToDisappear.clear();
// Ensure that there is at least enough space for the icons
int endMargin = Math.max(mHeaderTextMarginEnd, getPaddingEnd());
if (totalWidth > givenWidth - endMargin) {
int overFlow = totalWidth - givenWidth + endMargin;
- // First shrink the app name, down to a minimum size
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mAppName, mChildMinWidth);
-
- // Next, shrink the header text (this usually has subText)
- // This shrinks the subtext first, but not all the way (yet!)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, mChildMinWidth);
-
- // Next, shrink the secondary header text (this rarely has conversationTitle)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mSecondaryHeaderText, 0);
-
- // Next, shrink the title text (this has contentTitle; only in headerless views)
- overFlow = shrinkViewForOverflow(heightSpec, overFlow, mTitle, mChildMinWidth);
-
- // Finally, if there is still overflow, shrink the header down to 0 if still necessary.
- shrinkViewForOverflow(heightSpec, overFlow, mHeaderText, 0);
+ mOverflowAdjuster.resetForOverflow(overFlow, heightSpec)
+ // First shrink the app name, down to a minimum size
+ .adjust(mAppName, null, mChildMinWidth)
+ // Next, shrink the header text (this usually has subText)
+ // This shrinks the subtext first, but not all the way (yet!)
+ .adjust(mHeaderText, mHeaderTextDivider, mChildMinWidth)
+ // Next, shrink the secondary header text (this rarely has conversationTitle)
+ .adjust(mSecondaryHeaderText, mSecondaryHeaderTextDivider, 0)
+ // Next, shrink the title text (this has contentTitle; only in headerless views)
+ .adjust(mTitle, null, mChildMinWidth)
+ // Next, shrink the header down to 0 if still necessary.
+ .adjust(mHeaderText, mHeaderTextDivider, 0)
+ // Finally, shrink the title to 0 if necessary (media is super cramped)
+ .adjust(mTitle, null, 0)
+ // Clean up
+ .finish();
}
setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
}
- private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView,
- int minimumWidth) {
- if (targetView != null) {
- final int oldWidth = targetView.getMeasuredWidth();
- if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) {
- // we're still too big
- int newSize = Math.max(minimumWidth, oldWidth - overFlow);
- int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
- targetView.measure(childWidthSpec, heightSpec);
- overFlow -= oldWidth - newSize;
- }
- }
- return overFlow;
- }
-
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int left = getPaddingStart();
+ final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ final int width = getWidth();
+ int start = getPaddingStart();
int childCount = getChildCount();
int ownHeight = b - t;
int childSpace = ownHeight - mPaddingTop - mPaddingBottom;
@@ -182,8 +183,6 @@
}
int childHeight = child.getMeasuredHeight();
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
- int layoutLeft;
- int layoutRight;
// Calculate vertical alignment of the views, accounting for the view baselines
int childTop;
@@ -219,19 +218,16 @@
default:
childTop = mPaddingTop;
}
-
- left += params.getMarginStart();
- int right = left + child.getMeasuredWidth();
- layoutLeft = left;
- layoutRight = right;
- left = right + params.getMarginEnd();
-
- if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- int ltrLeft = layoutLeft;
- layoutLeft = getWidth() - layoutRight;
- layoutRight = getWidth() - ltrLeft;
+ if (mViewsToDisappear.contains(child)) {
+ child.layout(start, childTop, start, childTop + childHeight);
+ } else {
+ start += params.getMarginStart();
+ int end = start + child.getMeasuredWidth();
+ int layoutLeft = isRtl ? width - end : start;
+ int layoutRight = isRtl ? width - start : end;
+ start = end + params.getMarginEnd();
+ child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
}
- child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
}
updateTouchListener();
}
@@ -400,4 +396,83 @@
}
return mTouchListener.onTouchUp(upX, upY, downX, downY);
}
+
+ private final class OverflowAdjuster {
+ private int mOverflow;
+ private int mHeightSpec;
+ private View mRegrowView;
+
+ OverflowAdjuster resetForOverflow(int overflow, int heightSpec) {
+ mOverflow = overflow;
+ mHeightSpec = heightSpec;
+ mRegrowView = null;
+ return this;
+ }
+
+ /**
+ * Shrink the targetView's width by up to overFlow, down to minimumWidth.
+ * @param targetView the view to shrink the width of
+ * @param targetDivider a divider view which should be set to 0 width if the targetView is
+ * @param minimumWidth the minimum width allowed for the targetView
+ * @return this object
+ */
+ OverflowAdjuster adjust(View targetView, View targetDivider, int minimumWidth) {
+ if (mOverflow <= 0 || targetView == null || targetView.getVisibility() == View.GONE) {
+ return this;
+ }
+ final int oldWidth = targetView.getMeasuredWidth();
+ if (oldWidth <= minimumWidth) {
+ return this;
+ }
+ // we're too big
+ int newSize = Math.max(minimumWidth, oldWidth - mOverflow);
+ if (minimumWidth == 0 && newSize < mChildHideWidth
+ && mRegrowView != null && mRegrowView != targetView) {
+ // View is so small it's better to hide it entirely (and its divider and margins)
+ // so we can give that space back to another previously shrunken view.
+ newSize = 0;
+ }
+
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ targetView.measure(childWidthSpec, mHeightSpec);
+ mOverflow -= oldWidth - newSize;
+
+ if (newSize == 0) {
+ mViewsToDisappear.add(targetView);
+ mOverflow -= getHorizontalMargins(targetView);
+ if (targetDivider != null && targetDivider.getVisibility() != GONE) {
+ mViewsToDisappear.add(targetDivider);
+ int oldDividerWidth = targetDivider.getMeasuredWidth();
+ int dividerWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.AT_MOST);
+ targetDivider.measure(dividerWidthSpec, mHeightSpec);
+ mOverflow -= (oldDividerWidth + getHorizontalMargins(targetDivider));
+ }
+ }
+ if (mOverflow < 0 && mRegrowView != null) {
+ // We're now under-flowing, so regrow the last view.
+ final int regrowCurrentSize = mRegrowView.getMeasuredWidth();
+ final int maxSize = regrowCurrentSize - mOverflow;
+ int regrowWidthSpec = MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST);
+ mRegrowView.measure(regrowWidthSpec, mHeightSpec);
+ finish();
+ return this;
+ }
+
+ if (newSize != 0) {
+ // if we shrunk this view (but did not completely hide it) store it for potential
+ // re-growth if we proactively shorten a future view.
+ mRegrowView = targetView;
+ }
+ return this;
+ }
+
+ void finish() {
+ resetForOverflow(0, 0);
+ }
+
+ private int getHorizontalMargins(View view) {
+ MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
+ return params.getMarginStart() + params.getMarginEnd();
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 870fd8c..11b161a 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -79,6 +79,26 @@
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 3ffe0c6..82106b09 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1296,7 +1296,7 @@
mBlastBufferQueue.destroy();
}
mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight, mFormat, true /* TODO */);
+ mSurfaceHeight, mFormat);
}
private void onDrawFinished() {
@@ -1389,6 +1389,8 @@
// 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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4fb611..fdbec8b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -330,7 +330,6 @@
private boolean mUseBLASTAdapter;
private boolean mForceDisableBLAST;
- private boolean mEnableTripleBuffering;
private boolean mFastScrollSoundEffectsEnabled;
@@ -474,6 +473,7 @@
FrameInfo frameInfo = mChoreographer.mFrameInfo;
mViewFrameInfo.populateFrameInfo(frameInfo);
mViewFrameInfo.reset();
+ mInputEventAssigner.notifyFrameProcessed();
return frameInfo;
}
@@ -1179,9 +1179,6 @@
if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
mUseBLASTAdapter = true;
}
- if ((res & WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING) != 0) {
- mEnableTripleBuffering = true;
- }
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
@@ -1373,17 +1370,10 @@
// can be used by code on the system process to escape that and enable
// HW accelerated drawing. (This is basically for the lock screen.)
- final boolean fakeHwAccelerated = (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
final boolean forceHwAccelerated = (attrs.privateFlags &
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
- if (fakeHwAccelerated) {
- // This is exclusively for the preview windows the window manager
- // shows for launching applications, so they will look more like
- // the app being launched.
- mAttachInfo.mHardwareAccelerationRequested = true;
- } else if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
+ if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.destroy();
}
@@ -1907,7 +1897,7 @@
Surface ret = null;
if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, width, height,
- format, mEnableTripleBuffering);
+ format);
// We only return the Surface the first time, as otherwise
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
@@ -4303,7 +4293,7 @@
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;
@@ -8502,11 +8492,6 @@
consumedBatches = false;
}
doProcessInputEvents();
- if (consumedBatches) {
- // Must be done after we processed the input events, to mark the completion of the frame
- // from the input point of view
- mInputEventAssigner.onChoreographerCallback();
- }
return consumedBatches;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 04512c9a..93c3cab 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2218,26 +2218,6 @@
public int flags;
/**
- * If the window has requested hardware acceleration, but this is not
- * allowed in the process it is in, then still render it as if it is
- * hardware accelerated. This is used for the starting preview windows
- * in the system process, which don't need to have the overhead of
- * hardware acceleration (they are just a static rendering), but should
- * be rendered as such to match the actual window of the app even if it
- * is hardware accelerated.
- * Even if the window isn't hardware accelerated, still do its rendering
- * as if it was.
- * Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows
- * that need hardware acceleration (e.g. LockScreen), where hardware acceleration
- * is generally disabled. This flag must be specified in addition to
- * {@link #FLAG_HARDWARE_ACCELERATED} to enable hardware acceleration for system
- * windows.
- *
- * @hide
- */
- public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
-
- /**
* In the system process, we globally do not use hardware acceleration
* because there are many threads doing UI there and they conflict.
* If certain parts of the UI that really do want to use hardware
@@ -2463,7 +2443,6 @@
* @hide
*/
@IntDef(flag = true, prefix="PRIVATE_FLAG_", value = {
- PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
@@ -2499,10 +2478,6 @@
@UnsupportedAppUsage
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
- equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
- name = "FAKE_HARDWARE_ACCELERATED"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
name = "FORCE_HARDWARE_ACCELERATED"),
@@ -2967,7 +2942,7 @@
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/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 47ac1ee..18013e8 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -118,7 +118,6 @@
public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
public static final int ADD_FLAG_APP_VISIBLE = 0x2;
- public static final int ADD_FLAG_USE_TRIPLE_BUFFERING = 0x4;
public static final int ADD_FLAG_USE_BLAST = 0x8;
/**
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 8dce852..2bed311 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -36,6 +36,7 @@
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 @@
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 d96c5c8..52e4e15 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -51,7 +51,7 @@
* 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/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 37220fe..c5bce28 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -608,7 +608,12 @@
Preconditions.checkArgumentNonnegative(afterLength);
final Editable content = getEditable();
- if (content == null) return null;
+ // If {@link #getEditable()} is null or {@code mEditable} is equal to {@link #getEditable()}
+ // (a.k.a, a fake editable), it means we cannot get valid content from the editable, so
+ // fallback to retrieve surrounding text from other APIs.
+ if (content == null || mEditable == content) {
+ return InputConnection.super.getSurroundingText(beforeLength, afterLength, flags);
+ }
int selStart = Selection.getSelectionStart(content);
int selEnd = Selection.getSelectionEnd(content);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 34a60bb..38019c9 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -325,16 +325,16 @@
CharSequence textBeforeCursor = getTextBeforeCursor(beforeLength, flags);
if (textBeforeCursor == null) {
- textBeforeCursor = "";
+ return null;
+ }
+ CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
+ if (textAfterCursor == null) {
+ return null;
}
CharSequence selectedText = getSelectedText(flags);
if (selectedText == null) {
selectedText = "";
}
- CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags);
- if (textAfterCursor == null) {
- textAfterCursor = "";
- }
CharSequence surroundingText =
TextUtils.concat(textBeforeCursor, selectedText, textAfterCursor);
return new SurroundingText(surroundingText, textBeforeCursor.length(),
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9872dc0..6edd071 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -511,7 +511,6 @@
static final int MSG_TIMEOUT_INPUT_EVENT = 6;
static final int MSG_FLUSH_INPUT_EVENT = 7;
static final int MSG_REPORT_FULLSCREEN_MODE = 10;
- static final int MSG_APPLY_IME_VISIBILITY = 20;
static final int MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX = 30;
private static boolean isAutofillUIShowing(View servedView) {
@@ -985,17 +984,6 @@
}
return;
}
- case MSG_APPLY_IME_VISIBILITY: {
- synchronized (mH) {
- if (mImeInsetsConsumer != null) {
- ImeTracing.getInstance().triggerClientDump(
- "ImeInsetsSourceConsumer#applyImeVisibility",
- InputMethodManager.this, null /* icProto */);
- mImeInsetsConsumer.applyImeVisibility(msg.arg1 != 0);
- }
- }
- return;
- }
case MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX: {
final float[] matrixValues = (float[]) msg.obj;
final int bindSequence = msg.arg1;
@@ -1090,12 +1078,6 @@
}
@Override
- public void applyImeVisibility(boolean setVisible) {
- mH.obtainMessage(MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, 0)
- .sendToTarget();
- }
-
- @Override
public void updateActivityViewToScreenMatrix(int bindSequence, float[] matrixValues) {
mH.obtainMessage(MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX, bindSequence, 0,
matrixValues).sendToTarget();
@@ -2035,10 +2017,12 @@
}
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,
@@ -2046,37 +2030,37 @@
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 d347f31..9c53f46 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -18,7 +18,9 @@
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.ResultReceiver;
import android.view.autofill.AutofillId;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import com.android.internal.os.IResultReceiver;
@@ -30,18 +32,19 @@
* {@hide}
*/
oneway interface ITranslationManager {
- void getSupportedLocales(in IResultReceiver receiver, int userId);
- void onSessionCreated(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+ void onTranslationCapabilitiesRequest(int sourceFormat, int destFormat,
+ in ResultReceiver receiver, int userId);
+ void onSessionCreated(in TranslationContext translationContext,
int sessionId, in IResultReceiver receiver, int userId);
void updateUiTranslationState(int state, in TranslationSpec sourceSpec,
- in TranslationSpec destSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
+ in TranslationSpec targetSpec, in List<AutofillId> viewIds, IBinder token, int taskId,
int userId);
// deprecated
void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec,
- in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId,
- int userId);
+ in TranslationSpec targetSpec, in List<AutofillId> viewIds, int taskId, int userId);
void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
+ void getServiceSettingsActivity(in IResultReceiver result, int userId);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationCapability.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationCapability.aidl
index cab5ad2..3a80b17 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationCapability.aidl
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package android.view.translation;
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+parcelable TranslationCapability;
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
new file mode 100644
index 0000000..28b2113
--- /dev/null
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Capability class holding information for a pair of {@link TranslationSpec}s.
+ *
+ * <p>Holds information and limitations on how to create a {@link TranslationContext} which can
+ * be used by {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genConstructor = false)
+public final class TranslationCapability implements Parcelable {
+
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_DOWNLOADING = 2;
+ /**
+ * TODO: fill in javadoc
+ */
+ public static final @ModelState int STATE_ON_DEVICE = 3;
+
+ /**
+ * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+ */
+ private final @ModelState int mState;
+
+ /**
+ * {@link TranslationSpec} describing the source data specs for this
+ * capability.
+ */
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+
+ /**
+ * {@link TranslationSpec} describing the target data specs for this
+ * capability.
+ */
+ @NonNull
+ private final TranslationSpec mTargetSpec;
+
+ /**
+ * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+ *
+ * <p>Translation service will still support translation requests for this capability.</p>
+ */
+ private final boolean mUiTranslationEnabled;
+
+ /**
+ * Translation flags for settings that are supported by the
+ * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+ * provided in this capability.
+ */
+ private final @TranslationContext.TranslationFlag int mSupportedTranslationFlags;
+
+ /**
+ * Constructor for creating a {@link TranslationCapability}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public TranslationCapability(@ModelState int state, @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec, boolean uiTranslationEnabled,
+ @TranslationContext.TranslationFlag int supportedTranslationFlags) {
+ Objects.requireNonNull(sourceSpec, "sourceSpec should not be null");
+ Objects.requireNonNull(targetSpec, "targetSpec should not be null");
+
+ this.mState = state;
+ this.mSourceSpec = sourceSpec;
+ this.mTargetSpec = targetSpec;
+ this.mUiTranslationEnabled = uiTranslationEnabled;
+ this.mSupportedTranslationFlags = supportedTranslationFlags;
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationCapability.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "STATE_", value = {
+ STATE_AVAILABLE_TO_DOWNLOAD,
+ STATE_DOWNLOADING,
+ STATE_ON_DEVICE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface ModelState {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String modelStateToString(@ModelState int value) {
+ switch (value) {
+ case STATE_AVAILABLE_TO_DOWNLOAD:
+ return "STATE_AVAILABLE_TO_DOWNLOAD";
+ case STATE_DOWNLOADING:
+ return "STATE_DOWNLOADING";
+ case STATE_ON_DEVICE:
+ return "STATE_ON_DEVICE";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}.
+ */
+ @DataClass.Generated.Member
+ public @ModelState int getState() {
+ return mState;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the source data specs for this
+ * capability.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getSourceSpec() {
+ return mSourceSpec;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the target data specs for this
+ * capability.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getTargetSpec() {
+ return mTargetSpec;
+ }
+
+ /**
+ * Whether ui translation for the source-target {@link TranslationSpec}s is enabled.
+ *
+ * <p>Translation service will still support translation requests for this capability.</p>
+ */
+ @DataClass.Generated.Member
+ public boolean isUiTranslationEnabled() {
+ return mUiTranslationEnabled;
+ }
+
+ /**
+ * Translation flags for settings that are supported by the
+ * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+ * provided in this capability.
+ */
+ @DataClass.Generated.Member
+ public @TranslationContext.TranslationFlag int getSupportedTranslationFlags() {
+ return mSupportedTranslationFlags;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationCapability { " +
+ "state = " + modelStateToString(mState) + ", " +
+ "sourceSpec = " + mSourceSpec + ", " +
+ "targetSpec = " + mTargetSpec + ", " +
+ "uiTranslationEnabled = " + mUiTranslationEnabled + ", " +
+ "supportedTranslationFlags = " + mSupportedTranslationFlags +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mUiTranslationEnabled) flg |= 0x8;
+ dest.writeByte(flg);
+ dest.writeInt(mState);
+ dest.writeTypedObject(mSourceSpec, flags);
+ dest.writeTypedObject(mTargetSpec, flags);
+ dest.writeInt(mSupportedTranslationFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationCapability(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean uiTranslationEnabled = (flg & 0x8) != 0;
+ int state = in.readInt();
+ TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ int supportedTranslationFlags = in.readInt();
+
+ this.mState = state;
+
+ if (!(mState == STATE_AVAILABLE_TO_DOWNLOAD)
+ && !(mState == STATE_DOWNLOADING)
+ && !(mState == STATE_ON_DEVICE)) {
+ throw new java.lang.IllegalArgumentException(
+ "state was " + mState + " but must be one of: "
+ + "STATE_AVAILABLE_TO_DOWNLOAD(" + STATE_AVAILABLE_TO_DOWNLOAD + "), "
+ + "STATE_DOWNLOADING(" + STATE_DOWNLOADING + "), "
+ + "STATE_ON_DEVICE(" + STATE_ON_DEVICE + ")");
+ }
+
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mUiTranslationEnabled = uiTranslationEnabled;
+ this.mSupportedTranslationFlags = supportedTranslationFlags;
+ com.android.internal.util.AnnotationValidations.validate(
+ TranslationContext.TranslationFlag.class, null, mSupportedTranslationFlags);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationCapability> CREATOR
+ = new Parcelable.Creator<TranslationCapability>() {
+ @Override
+ public TranslationCapability[] newArray(int size) {
+ return new TranslationCapability[size];
+ }
+
+ @Override
+ public TranslationCapability createFromParcel(@NonNull android.os.Parcel in) {
+ return new TranslationCapability(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1616438309593L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
+ inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\[email protected](genHiddenConstDefs=true, genToString=true, genConstructor=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/core/java/android/view/translation/TranslationContext.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to core/java/android/view/translation/TranslationContext.aidl
index cab5ad2..cb6c23f 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/core/java/android/view/translation/TranslationContext.aidl
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package android.view.translation;
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+parcelable TranslationContext;
diff --git a/core/java/android/view/translation/TranslationContext.java b/core/java/android/view/translation/TranslationContext.java
new file mode 100644
index 0000000..1d3d182
--- /dev/null
+++ b/core/java/android/view/translation/TranslationContext.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Info class holding information for {@link Translator}s and used by
+ * {@link TranslationManager#createTranslator(TranslationContext)}.
+ */
+@DataClass(genHiddenConstDefs = true, genToString = true, genBuilder = true)
+public final class TranslationContext implements Parcelable {
+
+ /**
+ * This context will perform translations in low latency mode.
+ */
+ public static final @TranslationFlag int FLAG_LOW_LATENCY = 0x1;
+ /**
+ * This context will enable the {@link Translator} to return transliteration results.
+ */
+ public static final @TranslationFlag int FLAG_TRANSLITERATION = 0x2;
+ /**
+ * This context will enable the {@link Translator} to return dictionary results.
+ */
+ public static final @TranslationFlag int FLAG_DICTIONARY_DESCRIPTION = 0x4;
+
+ /**
+ * {@link TranslationSpec} describing the source data to be translated.
+ */
+ @NonNull
+ private final TranslationSpec mSourceSpec;
+
+ /**
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ @NonNull
+ private final TranslationSpec mTargetSpec;
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ private final @TranslationFlag int mTranslationFlags;
+
+ private static int defaultTranslationFlags() {
+ return 0;
+ }
+
+ @DataClass.Suppress({"setSourceSpec", "setTargetSpec"})
+ abstract static class BaseBuilder {
+
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationContext.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @android.annotation.IntDef(flag = true, prefix = "FLAG_", value = {
+ FLAG_LOW_LATENCY,
+ FLAG_TRANSLITERATION,
+ FLAG_DICTIONARY_DESCRIPTION
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TranslationFlag {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String translationFlagToString(@TranslationFlag int value) {
+ return com.android.internal.util.BitUtils.flagsToString(
+ value, TranslationContext::singleTranslationFlagToString);
+ }
+
+ @DataClass.Generated.Member
+ static String singleTranslationFlagToString(@TranslationFlag int value) {
+ switch (value) {
+ case FLAG_LOW_LATENCY:
+ return "FLAG_LOW_LATENCY";
+ case FLAG_TRANSLITERATION:
+ return "FLAG_TRANSLITERATION";
+ case FLAG_DICTIONARY_DESCRIPTION:
+ return "FLAG_DICTIONARY_DESCRIPTION";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ TranslationContext(
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec,
+ @TranslationFlag int translationFlags) {
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mTranslationFlags = translationFlags;
+
+ com.android.internal.util.Preconditions.checkFlagsArgument(
+ mTranslationFlags,
+ FLAG_LOW_LATENCY
+ | FLAG_TRANSLITERATION
+ | FLAG_DICTIONARY_DESCRIPTION);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * {@link TranslationSpec} describing the source data to be translated.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getSourceSpec() {
+ return mSourceSpec;
+ }
+
+ /**
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ @DataClass.Generated.Member
+ public @NonNull TranslationSpec getTargetSpec() {
+ return mTargetSpec;
+ }
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ @DataClass.Generated.Member
+ public @TranslationFlag int getTranslationFlags() {
+ return mTranslationFlags;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "TranslationContext { " +
+ "sourceSpec = " + mSourceSpec + ", " +
+ "targetSpec = " + mTargetSpec + ", " +
+ "translationFlags = " + translationFlagToString(mTranslationFlags) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mSourceSpec, flags);
+ dest.writeTypedObject(mTargetSpec, flags);
+ dest.writeInt(mTranslationFlags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ TranslationContext(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ TranslationSpec targetSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+ int translationFlags = in.readInt();
+
+ this.mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ this.mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ this.mTranslationFlags = translationFlags;
+
+ com.android.internal.util.Preconditions.checkFlagsArgument(
+ mTranslationFlags,
+ FLAG_LOW_LATENCY
+ | FLAG_TRANSLITERATION
+ | FLAG_DICTIONARY_DESCRIPTION);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<TranslationContext> CREATOR
+ = new Parcelable.Creator<TranslationContext>() {
+ @Override
+ public TranslationContext[] newArray(int size) {
+ return new TranslationContext[size];
+ }
+
+ @Override
+ public TranslationContext createFromParcel(@NonNull android.os.Parcel in) {
+ return new TranslationContext(in);
+ }
+ };
+
+ /**
+ * A builder for {@link TranslationContext}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder extends BaseBuilder {
+
+ private @NonNull TranslationSpec mSourceSpec;
+ private @NonNull TranslationSpec mTargetSpec;
+ private @TranslationFlag int mTranslationFlags;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param sourceSpec
+ * {@link TranslationSpec} describing the source data to be translated.
+ * @param targetSpec
+ * {@link TranslationSpec} describing the target translated data.
+ */
+ public Builder(
+ @NonNull TranslationSpec sourceSpec,
+ @NonNull TranslationSpec targetSpec) {
+ mSourceSpec = sourceSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSourceSpec);
+ mTargetSpec = targetSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTargetSpec);
+ }
+
+ /**
+ * Translation flags to be used by the {@link Translator}
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTranslationFlags(@TranslationFlag int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTranslationFlags = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull TranslationContext build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTranslationFlags = defaultTranslationFlags();
+ }
+ TranslationContext o = new TranslationContext(
+ mSourceSpec,
+ mTargetSpec,
+ mTranslationFlags);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1616199021789L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/translation/TranslationContext.java",
+ inputSignatures = "public static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_LOW_LATENCY\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_TRANSLITERATION\npublic static final @android.view.translation.TranslationContext.TranslationFlag int FLAG_DICTIONARY_DESCRIPTION\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mTranslationFlags\nprivate static int defaultTranslationFlags()\nclass TranslationContext extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\[email protected](genHiddenConstDefs=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 6554e1a..dfa7095 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -21,13 +21,15 @@
import android.annotation.RequiresFeature;
import android.annotation.SystemService;
import android.annotation.WorkerThread;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
-import android.service.translation.TranslationService;
+import android.os.SynchronousResultReceiver;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -35,10 +37,12 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.SyncResultReceiver;
+import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
import java.util.Objects;
import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -55,9 +59,9 @@
private static final String TAG = "TranslationManager";
/**
- * Timeout for calls to system_server.
+ * Timeout for calls to system_server, default 1 minute.
*/
- static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+ static final int SYNC_CALLS_TIMEOUT_MS = 60_000;
/**
* The result code from result receiver success.
* @hide
@@ -69,6 +73,17 @@
*/
public static final int STATUS_SYNC_CALL_FAIL = 2;
+ /**
+ * Name of the extra used to pass the translation capabilities.
+ * @hide
+ */
+ public static final String EXTRA_CAPABILITIES = "translation_capabilities";
+
+ // TODO: implement update listeners and propagate updates.
+ @GuardedBy("mLock")
+ private final ArrayMap<Pair<Integer, Integer>, ArrayList<PendingIntent>>
+ mTranslationCapabilityUpdateListeners = new ArrayMap<>();
+
private static final Random ID_GENERATOR = new Random();
private final Object mLock = new Object();
@@ -87,7 +102,7 @@
@NonNull
@GuardedBy("mLock")
- private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Integer> mTranslatorIds =
+ private final ArrayMap<TranslationContext, Integer> mTranslatorIds =
new ArrayMap<>();
@NonNull
@@ -110,23 +125,20 @@
*
* <p><strong>NOTE: </strong>Call on a worker thread.
*
- * @param sourceSpec {@link TranslationSpec} for the data to be translated.
- * @param destSpec {@link TranslationSpec} for the translated data.
+ * @param translationContext {@link TranslationContext} containing the specs for creating the
+ * Translator.
* @return a {@link Translator} to be used for calling translation APIs.
*/
@Nullable
@WorkerThread
- public Translator createTranslator(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec) {
- Objects.requireNonNull(sourceSpec, "sourceSpec cannot be null");
- Objects.requireNonNull(sourceSpec, "destSpec cannot be null");
+ public Translator createTranslator(@NonNull TranslationContext translationContext) {
+ Objects.requireNonNull(translationContext, "translationContext cannot be null");
synchronized (mLock) {
// TODO(b/176464808): Disallow multiple Translator now, it will throw
// IllegalStateException. Need to discuss if we can allow multiple Translators.
- final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
- if (mTranslatorIds.containsKey(specs)) {
- return mTranslators.get(mTranslatorIds.get(specs));
+ if (mTranslatorIds.containsKey(translationContext)) {
+ return mTranslators.get(mTranslatorIds.get(translationContext));
}
int translatorId;
@@ -134,7 +146,7 @@
translatorId = Math.abs(ID_GENERATOR.nextInt());
} while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
- final Translator newTranslator = new Translator(mContext, sourceSpec, destSpec,
+ final Translator newTranslator = new Translator(mContext, translationContext,
translatorId, this, mHandler, mService);
// Start the Translator session and wait for the result
newTranslator.start();
@@ -143,7 +155,7 @@
return null;
}
mTranslators.put(translatorId, newTranslator);
- mTranslatorIds.put(specs, translatorId);
+ mTranslatorIds.put(translationContext, translatorId);
return newTranslator;
} catch (Translator.ServiceBinderReceiver.TimeoutException e) {
// TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor
@@ -155,29 +167,125 @@
}
/**
- * Returns a list of locales supported by the {@link TranslationService}.
+ * Returns a set of {@link TranslationCapability}s describing the capabilities for
+ * {@link Translator}s.
+ *
+ * <p>These translation capabilities contains a source and target {@link TranslationSpec}
+ * representing the data expected for both ends of translation process. The capabilities
+ * provides the information and limitations for generating a {@link TranslationContext}.
+ * The context object can then be used by {@link #createTranslator(TranslationContext)} to
+ * obtain a {@link Translator} for translations.</p>
*
* <p><strong>NOTE: </strong>Call on a worker thread.
*
- * TODO: Change to correct language/locale format
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @return A set of {@link TranslationCapability}s.
*/
@NonNull
@WorkerThread
- public List<String> getSupportedLocales() {
+ public Set<TranslationCapability> getTranslationCapabilities(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat) {
try {
- // TODO: implement it
- final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
- mService.getSupportedLocales(receiver, mContext.getUserId());
- int resutCode = receiver.getIntResult();
- if (resutCode != STATUS_SYNC_CALL_SUCCESS) {
- return Collections.emptyList();
+ final SynchronousResultReceiver receiver = new SynchronousResultReceiver();
+ mService.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, receiver,
+ mContext.getUserId());
+ final SynchronousResultReceiver.Result result =
+ receiver.awaitResult(SYNC_CALLS_TIMEOUT_MS);
+ if (result.resultCode != STATUS_SYNC_CALL_SUCCESS) {
+ return Collections.emptySet();
}
- return receiver.getParcelableResult();
+ return new ArraySet<>(
+ (TranslationCapability[]) result.bundle.getParcelableArray(EXTRA_CAPABILITIES));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timed out getting supported translation capabilities: " + e);
+ return Collections.emptySet();
+ }
+ }
+
+ /**
+ * Registers a {@link PendingIntent} to listen for updates on states of
+ * {@link TranslationCapability}s.
+ *
+ * <p>IMPORTANT: the pending intent must be called to start a service, or a broadcast if it is
+ * an explicit intent.</p>
+ *
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @param pendingIntent the pending intent to invoke when updates are received.
+ */
+ public void addTranslationCapabilityUpdateListener(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+ synchronized (mLock) {
+ final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+ mTranslationCapabilityUpdateListeners.computeIfAbsent(formatPair,
+ (formats) -> new ArrayList<>()).add(pendingIntent);
+ }
+ }
+
+ /**
+ * Unregisters a {@link PendingIntent} to listen for updates on states of
+ * {@link TranslationCapability}s.
+ *
+ * @param sourceFormat data format for the input data to be translated.
+ * @param targetFormat data format for the expected translated output data.
+ * @param pendingIntent the pending intent to unregister
+ */
+ public void removeTranslationCapabilityUpdateListener(
+ @TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(pendingIntent, "pending intent should not be null");
+
+ synchronized (mLock) {
+ final Pair<Integer, Integer> formatPair = new Pair<>(sourceFormat, targetFormat);
+ if (mTranslationCapabilityUpdateListeners.containsKey(formatPair)) {
+ final ArrayList<PendingIntent> intents =
+ mTranslationCapabilityUpdateListeners.get(formatPair);
+ if (intents.contains(pendingIntent)) {
+ intents.remove(pendingIntent);
+ } else {
+ Log.w(TAG, "pending intent=" + pendingIntent + " does not exist in "
+ + "mTranslationCapabilityUpdateListeners");
+ }
+ } else {
+ Log.w(TAG, "format pair=" + formatPair + " does not exist in "
+ + "mTranslationCapabilityUpdateListeners");
+ }
+ }
+ }
+
+ //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, "Timed out getting supported locales: " + e);
- return Collections.emptyList();
+ Log.e(TAG, "Fail to get translation service settings activity.");
+ return null;
}
}
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 0cc26e1..6b26e06 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -44,7 +44,7 @@
import java.util.function.Consumer;
/**
- * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
+ * The {@link Translator} for translation, defined by a {@link TranslationContext}.
*/
@SuppressLint("NotCloseable")
public class Translator {
@@ -62,10 +62,7 @@
private final Context mContext;
@NonNull
- private final TranslationSpec mSourceSpec;
-
- @NonNull
- private final TranslationSpec mDestSpec;
+ private final TranslationContext mTranslationContext;
@NonNull
private final TranslationManager mManager;
@@ -164,13 +161,11 @@
* @hide
*/
public Translator(@NonNull Context context,
- @NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId,
+ @NonNull TranslationContext translationContext, int sessionId,
@NonNull TranslationManager translationManager, @NonNull Handler handler,
@Nullable ITranslationManager systemServerBinder) {
mContext = context;
- mSourceSpec = sourceSpec;
- mDestSpec = destSpec;
+ mTranslationContext = translationContext;
mId = sessionId;
mManager = translationManager;
mHandler = handler;
@@ -183,7 +178,7 @@
*/
void start() {
try {
- mSystemServerBinder.onSessionCreated(mSourceSpec, mDestSpec, mId,
+ mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
mServiceBinderReceiver, mContext.getUserId());
} catch (RemoteException e) {
Log.w(TAG, "RemoteException calling startSession(): " + e);
@@ -223,8 +218,7 @@
/** @hide */
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
- pw.print(prefix); pw.print("sourceSpec: "); pw.println(mSourceSpec);
- pw.print(prefix); pw.print("destSpec: "); pw.println(mDestSpec);
+ pw.print(prefix); pw.print("translationContext: "); pw.println(mTranslationContext);
}
/**
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 7f934c0..d79ecca 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -376,15 +376,17 @@
}
private Translator createTranslatorIfNeeded(
- TranslationSpec sourceSpec, TranslationSpec destSpec) {
+ TranslationSpec sourceSpec, TranslationSpec targetSpec) {
final TranslationManager tm = mContext.getSystemService(TranslationManager.class);
if (tm == null) {
Log.e(TAG, "Can not find TranslationManager when trying to create translator.");
return null;
}
- final Translator translator = tm.createTranslator(sourceSpec, destSpec);
+ final TranslationContext translationContext = new TranslationContext(sourceSpec,
+ targetSpec, /* translationFlags= */ 0);
+ final Translator translator = tm.createTranslator(translationContext);
if (translator != null) {
- final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
+ final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, targetSpec);
mTranslators.put(specs, translator);
}
return translator;
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index 852ffe8..62868ac 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -127,10 +127,13 @@
* @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 @@
* 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 @@
* 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 @@
* 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 58dfc0d..1951194 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -130,11 +130,11 @@
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";
@@ -640,6 +640,12 @@
mWidth,
mHeight
);
+ } else {
+ // This is TYPE_STRETCH and drawing into a Canvas that isn't a Recording Canvas,
+ // so no effect can be shown. Just end the effect.
+ mState = STATE_IDLE;
+ mDistance = 0;
+ mVelocity = 0;
}
boolean oneLastFrame = false;
@@ -771,8 +777,9 @@
* considered at rest or false if it is still animating.
*/
private boolean isAtEquilibrium() {
- double displacement = mDistance * mHeight; // in pixels
- return Math.abs(mVelocity) < VELOCITY_THRESHOLD
+ double displacement = mDistance * mHeight * LINEAR_STRETCH_INTENSITY; // in pixels
+ double velocity = mVelocity * LINEAR_STRETCH_INTENSITY;
+ return Math.abs(velocity) < VELOCITY_THRESHOLD
&& Math.abs(displacement) < VALUE_THRESHOLD;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 319e788..cb2bba1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5476,14 +5476,14 @@
/**
* Object allowing the modification of a context to overload the system's dynamic colors.
*
- * Only colors from {@link android.R.color#system_primary_0} to
- * {@link android.R.color#system_neutral_1000} can be overloaded.
+ * Only colors from {@link android.R.color#system_accent1_0} to
+ * {@link android.R.color#system_neutral2_1000} can be overloaded.
* @hide
*/
public static final class ColorResources {
// Set of valid colors resources.
- private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_primary_0;
- private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_neutral_1000;
+ private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
+ private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000;
// Size, in bytes, of an entry in the array of colors in an ARSC file.
private static final int ARSC_ENTRY_SIZE = 16;
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index 3709aa1..9789d70 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -15,6 +15,7 @@
*/
package android.window;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import android.annotation.ColorInt;
@@ -32,6 +33,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -183,6 +185,7 @@
* Create SplashScreenWindowView object from materials.
*/
public SplashScreenView build() {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#build");
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
final SplashScreenView view = (SplashScreenView)
layoutInflater.inflate(R.layout.splash_screen_view, null, false);
@@ -208,14 +211,14 @@
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;
@@ -226,6 +229,7 @@
+ view.mBrandingImageView + " drawable: " + mBrandingDrawable
+ " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return view;
}
}
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
new file mode 100644
index 0000000..375f4cf
--- /dev/null
+++ b/core/java/android/window/WindowContext.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.window;
+
+import static android.view.WindowManagerImpl.createWindowContextWindowManager;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.ref.Reference;
+
+/**
+ * {@link WindowContext} is a context for non-activity windows such as
+ * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} windows or system
+ * windows. Its resources and configuration are adjusted to the area of the display that will be
+ * used when a new window is added via {@link android.view.WindowManager#addView}.
+ *
+ * @see Context#createWindowContext(int, Bundle)
+ * @hide
+ */
+@UiContext
+public class WindowContext extends ContextWrapper {
+ private final WindowManager mWindowManager;
+ 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
+ * the token.
+ *
+ * @param base Base {@link Context} for this new instance.
+ * @param type Window type to be used with this context.
+ * @hide
+ */
+ public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) {
+ super(base);
+
+ mType = type;
+ mOptions = options;
+ mWindowManager = createWindowContextWindowManager(this);
+ IBinder token = getWindowContextToken();
+ mController = new WindowContextController(token);
+
+ Reference.reachabilityFence(this);
+ }
+
+ /**
+ * 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
+ public Object getSystemService(String name) {
+ if (WINDOW_SERVICE.equals(name)) {
+ return mWindowManager;
+ }
+ return super.getSystemService(name);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ release();
+ super.finalize();
+ }
+
+ /** Used for test to invoke because we can't invoke finalize directly. */
+ @VisibleForTesting
+ public void release() {
+ mController.unregisterListenerIfNeeded();
+ destroy();
+ }
+
+ @Override
+ public void destroy() {
+ mCallbacksController.clearCallbacks();
+ // Called to the base ContextImpl to do final clean-up.
+ getBaseContext().destroy();
+ Reference.reachabilityFence(this);
+ }
+
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
+ void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ mCallbacksController.dispatchConfigurationChanged(newConfig);
+ }
+}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
new file mode 100644
index 0000000..6143414
--- /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
similarity index 74%
rename from core/java/android/app/WindowTokenClient.java
rename to core/java/android/window/WindowTokenClient.java
index 2298e84..b2fe4d9 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 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,13 +57,11 @@
* @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
@@ -72,6 +77,10 @@
if (displayChanged || configChanged) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
+ if (context instanceof WindowContext) {
+ ActivityThread.currentActivityThread().getHandler().post(
+ () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig));
+ }
}
if (displayChanged) {
context.updateDisplay(newDisplayId);
@@ -80,7 +89,7 @@
@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/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index 26af615..b76ef0f 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -28,11 +28,9 @@
import android.content.pm.ResolveInfo;
import android.os.Message;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -62,11 +60,6 @@
// back to using the ResolverRankerService.
private ResolverRankerServiceResolverComparator mResolverRankerService;
- private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- true);
-
AppPredictionServiceResolverComparator(
Context context,
Intent intent,
@@ -183,9 +176,6 @@
if (mResolverRankerService != null) {
return mResolverRankerService.getScore(name);
}
- if (mAppendDirectShareEnabled && !mTargetScores.isEmpty()) {
- return mTargetScores.get(name);
- }
Integer rank = mTargetRanks.get(name);
if (rank == null) {
Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index d4d8536..862c900 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -256,15 +256,6 @@
SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
DEFAULT_SALT_EXPIRATION_DAYS);
- private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- true);
- private boolean mChooserTargetRankingEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.CHOOSER_TARGET_RANKING_ENABLED,
- true);
-
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
private IntentSender mRefinementIntentSender;
@@ -472,16 +463,10 @@
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
- private static final int CHOOSER_TARGET_RANKING_SCORE = 7;
private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
- private static final int DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS = 1500;
- private int mDirectShareTimeout = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.SHARE_SHEET_DIRECT_SHARE_TIMEOUT,
- DEFAULT_DIRECT_SHARE_TIMEOUT_MILLIS);
-
private boolean mMinTimeoutPassed = false;
private void removeAllMessages() {
@@ -491,7 +476,6 @@
removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
- removeMessages(CHOOSER_TARGET_RANKING_SCORE);
}
private void restartServiceRequestTimer() {
@@ -501,14 +485,13 @@
if (DEBUG) {
Log.d(TAG, "queryTargets setting watchdog timer for "
- + mDirectShareTimeout + "-"
+ WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
}
sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
WATCHDOG_TIMEOUT_MIN_MILLIS);
sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
- mAppendDirectShareEnabled ? mDirectShareTimeout : WATCHDOG_TIMEOUT_MAX_MILLIS);
+ WATCHDOG_TIMEOUT_MAX_MILLIS);
}
private void maybeStopServiceRequestTimer() {
@@ -608,17 +591,6 @@
getChooserActivityLogger().logSharesheetDirectLoadComplete();
break;
- case CHOOSER_TARGET_RANKING_SCORE:
- if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_RANKING_SCORE");
- final ChooserTargetRankingInfo scoreInfo = (ChooserTargetRankingInfo) msg.obj;
- ChooserListAdapter adapterForUserHandle =
- mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
- scoreInfo.userHandle);
- if (adapterForUserHandle != null) {
- adapterForUserHandle.addChooserTargetRankingScore(scoreInfo.scores);
- }
- break;
-
default:
super.handleMessage(msg);
}
@@ -878,24 +850,14 @@
final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
new ArrayList<>();
- // Separate ChooserTargets ranking scores and ranked Shortcuts.
List<AppTarget> shortcutResults = new ArrayList<>();
- List<AppTarget> chooserTargetScores = new ArrayList<>();
for (AppTarget appTarget : resultList) {
if (appTarget.getShortcutInfo() == null) {
continue;
}
- if (appTarget.getShortcutInfo().getId().equals(CHOOSER_TARGET)) {
- chooserTargetScores.add(appTarget);
- } else {
- shortcutResults.add(appTarget);
- }
+ shortcutResults.add(appTarget);
}
resultList = shortcutResults;
- if (mChooserTargetRankingEnabled) {
- sendChooserTargetRankingScore(chooserTargetScores,
- chooserListAdapter.getUserHandle());
- }
for (AppTarget appTarget : resultList) {
shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
appTarget.getShortcutInfo(),
@@ -2148,14 +2110,6 @@
return true;
}
- private void sendChooserTargetRankingScore(List<AppTarget> chooserTargetScores,
- UserHandle userHandle) {
- final Message msg = Message.obtain();
- msg.what = ChooserHandler.CHOOSER_TARGET_RANKING_SCORE;
- msg.obj = new ChooserTargetRankingInfo(chooserTargetScores, userHandle);
- mChooserHandler.sendMessage(msg);
- }
-
private void sendShareShortcutInfoList(
List<ShortcutManager.ShareShortcutInfo> resultList,
ChooserListAdapter chooserListAdapter,
@@ -2376,9 +2330,6 @@
}
private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
- if (!mChooserTargetRankingEnabled) {
- return;
- }
AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
if (directShareAppPredictor == null) {
@@ -2399,11 +2350,6 @@
targetIds.add(new AppTargetId(
String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
SHORTCUT_TARGET)));
- } else {
- String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString());
- targetIds.add(new AppTargetId(
- String.format("%s/%s/%s", titleHash, componentName.flattenToString(),
- CHOOSER_TARGET)));
}
}
directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
@@ -2423,29 +2369,6 @@
if (mDirectShareAppTargetCache != null) {
appTarget = mDirectShareAppTargetCache.get(chooserTarget);
}
- if (mChooserTargetRankingEnabled && appTarget == null) {
- // Send ChooserTarget sharing info to AppPredictor.
- ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault(
- chooserTarget.getComponentName(), chooserTarget.getComponentName());
- try {
- appTarget = new AppTarget.Builder(
- new AppTargetId(componentName.flattenToString()),
- new ShortcutInfo.Builder(
- createPackageContextAsUser(
- componentName.getPackageName(),
- 0 /* flags */,
- getUser()),
- CHOOSER_TARGET)
- .setActivity(componentName)
- .setShortLabel(ChooserUtil.md5(chooserTarget.getTitle().toString()))
- .build())
- .setClassName(componentName.getClassName())
- .build();
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not look up service " + componentName
- + "; component name not found");
- }
- }
// This is a direct share click that was provided by the APS
if (appTarget != null) {
directShareAppPredictor.notifyAppTargetEvent(
@@ -3971,9 +3894,7 @@
// until they start to scroll
ChooserListAdapter adapter =
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
- int validTargets =
- mAppendDirectShareEnabled ? adapter.getNumServiceTargetsForExpand()
- : adapter.getSelectableServiceTargetCount();
+ int validTargets = adapter.getSelectableServiceTargetCount();
if (validTargets <= maxTargetsPerRow) {
mHideDirectShareExpansion = true;
return;
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 5700668..cc2b12a 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -21,7 +21,6 @@
import android.app.ActivityManager;
import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -37,7 +36,6 @@
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
import android.util.Log;
-import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
@@ -53,21 +51,13 @@
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.Set;
-import java.util.stream.Collectors;
public class ChooserListAdapter extends ResolverListAdapter {
private static final String TAG = "ChooserListAdapter";
private static final boolean DEBUG = false;
- private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- true);
-
private boolean mEnableStackedApps = true;
public static final int NO_POSITION = -1;
@@ -79,8 +69,6 @@
private static final int MAX_SUGGESTED_APP_TARGETS = 4;
private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
- private static final int MAX_SERVICE_TARGET_APP = 8;
- private static final int DEFAULT_DIRECT_SHARE_RANKING_SCORE = 1000;
/** {@link #getBaseScore} */
public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
@@ -94,17 +82,11 @@
private int mNumShortcutResults = 0;
private Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>();
+ private boolean mApplySharingAppLimits;
// Reserve spots for incoming direct share targets by adding placeholders
private ChooserTargetInfo
mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
- private int mValidServiceTargetsNum = 0;
- private int mAvailableServiceTargetsNum = 0;
- private final Map<ComponentName, Pair<List<ChooserTargetInfo>, Integer>>
- mParkingDirectShareTargets = new HashMap<>();
- private final Map<ComponentName, Map<String, Integer>> mChooserTargetScores = new HashMap<>();
- private Set<ComponentName> mPendingChooserTargetService = new HashSet<>();
- private Set<ComponentName> mShortcutComponents = new HashSet<>();
private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();
@@ -183,6 +165,10 @@
if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
}
}
+ mApplySharingAppLimits = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+ true);
}
AppPredictor getAppPredictor() {
@@ -209,9 +195,6 @@
void refreshListView() {
if (mListViewDataChanged) {
- if (mAppendDirectShareEnabled) {
- appendServiceTargetsWithQuota();
- }
super.notifyDataSetChanged();
}
mListViewDataChanged = false;
@@ -221,10 +204,6 @@
private void createPlaceHolders() {
mNumShortcutResults = 0;
mServiceTargets.clear();
- mValidServiceTargetsNum = 0;
- mParkingDirectShareTargets.clear();
- mPendingChooserTargetService.clear();
- mShortcutComponents.clear();
for (int i = 0; i < mChooserListCommunicator.getMaxRankedTargets(); i++) {
mServiceTargets.add(mPlaceHolderTargetInfo);
}
@@ -517,33 +496,30 @@
+ targets.size()
+ " targets");
}
- if (mAppendDirectShareEnabled) {
- parkTargetIntoMemory(origTarget, targets, targetType, directShareToShortcutInfos,
- pendingChooserTargetServiceConnections);
- return;
- }
if (targets.size() == 0) {
return;
}
-
final float baseScore = getBaseScore(origTarget, targetType);
Collections.sort(targets, mBaseTargetComparator);
-
final boolean isShortcutResult =
(targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
|| targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
: MAX_CHOOSER_TARGETS_PER_APP;
+ final int targetsLimit = mApplySharingAppLimits ? Math.min(targets.size(), maxTargets)
+ : targets.size();
float lastScore = 0;
boolean shouldNotify = false;
- for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
+ for (int i = 0, count = targetsLimit; i < count; i++) {
final ChooserTarget target = targets.get(i);
float targetScore = target.getScore();
- targetScore *= baseScore;
- if (i > 0 && targetScore >= lastScore) {
- // Apply a decay so that the top app can't crowd out everything else.
- // This incents ChooserTargetServices to define what's truly better.
- targetScore = lastScore * 0.95f;
+ if (mApplySharingAppLimits) {
+ targetScore *= baseScore;
+ if (i > 0 && targetScore >= lastScore) {
+ // Apply a decay so that the top app can't crowd out everything else.
+ // This incents ChooserTargetServices to define what's truly better.
+ targetScore = lastScore * 0.95f;
+ }
}
UserHandle userHandle = getUserHandle();
Context contextAsUser = mContext.createContextAsUser(userHandle, 0 /* flags */);
@@ -561,7 +537,8 @@
Log.d(TAG, " => " + target.toString() + " score=" + targetScore
+ " base=" + target.getScore()
+ " lastScore=" + lastScore
- + " baseScore=" + baseScore);
+ + " baseScore=" + baseScore
+ + " applyAppLimit=" + mApplySharingAppLimits);
}
lastScore = targetScore;
@@ -573,217 +550,13 @@
}
/**
- * Store ChooserTarget ranking scores info wrapped in {@code targets}.
- */
- public void addChooserTargetRankingScore(List<AppTarget> targets) {
- Log.i(TAG, "addChooserTargetRankingScore " + targets.size() + " targets score.");
- for (AppTarget target : targets) {
- if (target.getShortcutInfo() == null) {
- continue;
- }
- ShortcutInfo shortcutInfo = target.getShortcutInfo();
- if (!shortcutInfo.getId().equals(ChooserActivity.CHOOSER_TARGET)
- || shortcutInfo.getActivity() == null) {
- continue;
- }
- ComponentName componentName = shortcutInfo.getActivity();
- if (!mChooserTargetScores.containsKey(componentName)) {
- mChooserTargetScores.put(componentName, new HashMap<>());
- }
- mChooserTargetScores.get(componentName).put(shortcutInfo.getShortLabel().toString(),
- target.getRank());
- }
- mChooserTargetScores.keySet().forEach(key -> rankTargetsWithinComponent(key));
- }
-
- /**
- * Rank chooserTargets of the given {@code componentName} in mParkingDirectShareTargets as per
- * available scores stored in mChooserTargetScores.
- */
- private void rankTargetsWithinComponent(ComponentName componentName) {
- if (!mParkingDirectShareTargets.containsKey(componentName)
- || !mChooserTargetScores.containsKey(componentName)) {
- return;
- }
- Map<String, Integer> scores = mChooserTargetScores.get(componentName);
- Collections.sort(mParkingDirectShareTargets.get(componentName).first, (o1, o2) -> {
- // The score has been normalized between 0 and 2000, the default is 1000.
- int score1 = scores.getOrDefault(
- ChooserUtil.md5(o1.getChooserTarget().getTitle().toString()),
- DEFAULT_DIRECT_SHARE_RANKING_SCORE);
- int score2 = scores.getOrDefault(
- ChooserUtil.md5(o2.getChooserTarget().getTitle().toString()),
- DEFAULT_DIRECT_SHARE_RANKING_SCORE);
- return score2 - score1;
- });
- }
-
- /**
- * Park {@code targets} into memory for the moment to surface them later when view is refreshed.
- * Components pending on ChooserTargetService query are also recorded.
- */
- private void parkTargetIntoMemory(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
- @ChooserActivity.ShareTargetType int targetType,
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
- List<ChooserActivity.ChooserTargetServiceConnection>
- pendingChooserTargetServiceConnections) {
- ComponentName origComponentName = origTarget != null ? origTarget.getResolvedComponentName()
- : !targets.isEmpty() ? targets.get(0).getComponentName() : null;
- Log.i(TAG,
- "parkTargetIntoMemory " + origComponentName + ", " + targets.size() + " targets");
- mPendingChooserTargetService = pendingChooserTargetServiceConnections.stream()
- .map(ChooserActivity.ChooserTargetServiceConnection::getComponentName)
- .filter(componentName -> !componentName.equals(origComponentName))
- .collect(Collectors.toSet());
- // Park targets in memory
- if (!targets.isEmpty()) {
- final boolean isShortcutResult =
- (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
- || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
- Context contextAsUser = mContext.createContextAsUser(getUserHandle(),
- 0 /* flags */);
- List<ChooserTargetInfo> parkingTargetInfos = targets.stream()
- .map(target ->
- new SelectableTargetInfo(
- contextAsUser, origTarget, target, target.getScore(),
- mSelectableTargetInfoCommunicator,
- (isShortcutResult ? directShareToShortcutInfos.get(target)
- : null))
- )
- .collect(Collectors.toList());
- Pair<List<ChooserTargetInfo>, Integer> parkingTargetInfoPair =
- mParkingDirectShareTargets.getOrDefault(origComponentName,
- new Pair<>(new ArrayList<>(), 0));
- for (ChooserTargetInfo target : parkingTargetInfos) {
- if (!checkDuplicateTarget(target, parkingTargetInfoPair.first)
- && !checkDuplicateTarget(target, mServiceTargets)) {
- parkingTargetInfoPair.first.add(target);
- mAvailableServiceTargetsNum++;
- }
- }
- mParkingDirectShareTargets.put(origComponentName, parkingTargetInfoPair);
- rankTargetsWithinComponent(origComponentName);
- if (isShortcutResult) {
- mShortcutComponents.add(origComponentName);
- }
- }
- notifyDataSetChanged();
- }
-
- /**
- * Append targets of top ranked share app into direct share row with quota limit. Remove
- * appended ones from memory.
- */
- private void appendServiceTargetsWithQuota() {
- int maxRankedTargets = mChooserListCommunicator.getMaxRankedTargets();
- List<ComponentName> topComponentNames = getTopComponentNames(maxRankedTargets);
- float totalScore = 0f;
- for (ComponentName component : topComponentNames) {
- if (!mPendingChooserTargetService.contains(component)
- && !mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- totalScore += super.getScore(component);
- }
- boolean shouldWaitPendingService = false;
- for (ComponentName component : topComponentNames) {
- if (!mPendingChooserTargetService.contains(component)
- && !mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- float score = super.getScore(component);
- int quota = Math.round(maxRankedTargets * score / totalScore);
- if (mPendingChooserTargetService.contains(component) && quota >= 1) {
- shouldWaitPendingService = true;
- }
- if (!mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- // Append targets into direct share row as per quota.
- Pair<List<ChooserTargetInfo>, Integer> parkingTargetsItem =
- mParkingDirectShareTargets.get(component);
- List<ChooserTargetInfo> parkingTargets = parkingTargetsItem.first;
- int insertedNum = parkingTargetsItem.second;
- while (insertedNum < quota && !parkingTargets.isEmpty()) {
- if (!checkDuplicateTarget(parkingTargets.get(0), mServiceTargets)) {
- mServiceTargets.add(mValidServiceTargetsNum, parkingTargets.get(0));
- mValidServiceTargetsNum++;
- insertedNum++;
- }
- parkingTargets.remove(0);
- }
- Log.i(TAG, " appendServiceTargetsWithQuota component=" + component
- + " appendNum=" + (insertedNum - parkingTargetsItem.second));
- if (DEBUG) {
- Log.d(TAG, " appendServiceTargetsWithQuota component=" + component
- + " score=" + score
- + " totalScore=" + totalScore
- + " quota=" + quota);
- }
- mParkingDirectShareTargets.put(component, new Pair<>(parkingTargets, insertedNum));
- }
- if (!shouldWaitPendingService) {
- fillAllServiceTargets();
- }
- }
-
- /**
- * Append all remaining targets (parking in memory) into direct share row as per their ranking.
- */
- private void fillAllServiceTargets() {
- if (mParkingDirectShareTargets.isEmpty()) {
- return;
- }
- Log.i(TAG, " fillAllServiceTargets");
- List<ComponentName> topComponentNames = getTopComponentNames(MAX_SERVICE_TARGET_APP);
- // Append all remaining targets of top recommended components into direct share row.
- for (ComponentName component : topComponentNames) {
- if (!mParkingDirectShareTargets.containsKey(component)) {
- continue;
- }
- mParkingDirectShareTargets.get(component).first.stream()
- .filter(target -> !checkDuplicateTarget(target, mServiceTargets))
- .forEach(target -> {
- mServiceTargets.add(mValidServiceTargetsNum, target);
- mValidServiceTargetsNum++;
- });
- mParkingDirectShareTargets.remove(component);
- }
- // Append all remaining shortcuts targets into direct share row.
- mParkingDirectShareTargets.entrySet().stream()
- .filter(entry -> mShortcutComponents.contains(entry.getKey()))
- .map(entry -> entry.getValue())
- .map(pair -> pair.first)
- .forEach(targets -> {
- for (ChooserTargetInfo target : targets) {
- if (!checkDuplicateTarget(target, mServiceTargets)) {
- mServiceTargets.add(mValidServiceTargetsNum, target);
- mValidServiceTargetsNum++;
- }
- }
- });
- mParkingDirectShareTargets.clear();
- }
-
- private boolean checkDuplicateTarget(ChooserTargetInfo target,
- List<ChooserTargetInfo> destination) {
- // Check for duplicates and abort if found
- for (ChooserTargetInfo otherTargetInfo : destination) {
- if (target.isSimilar(otherTargetInfo)) {
- return true;
- }
- }
- return false;
- }
-
- /**
* The return number have to exceed a minimum limit to make direct share area expandable. When
* append direct share targets is enabled, return count of all available targets parking in the
* memory; otherwise, it is shortcuts count which will help reduce the amount of visible
* shuffling due to older-style direct share targets.
*/
int getNumServiceTargetsForExpand() {
- return mAppendDirectShareEnabled ? mAvailableServiceTargetsNum : mNumShortcutResults;
+ return mNumShortcutResults;
}
/**
@@ -801,16 +574,11 @@
if (target == null) {
return CALLER_TARGET_SCORE_BOOST;
}
-
- if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
- return SHORTCUT_TARGET_SCORE_BOOST;
- }
-
float score = super.getScore(target);
- if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
+ if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
+ || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
return score * SHORTCUT_TARGET_SCORE_BOOST;
}
-
return score;
}
@@ -820,9 +588,6 @@
*/
public void completeServiceTargetLoading() {
mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
- if (mAppendDirectShareEnabled) {
- fillAllServiceTargets();
- }
if (mServiceTargets.isEmpty()) {
mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
}
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index dd837fc..922f96e 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -194,12 +194,12 @@
if (mIsSendAction) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_share_with_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_share_with_work_apps_explanation);
} else {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_work_apps_explanation);
}
}
@@ -209,12 +209,12 @@
if (mIsSendAction) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_share_with_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_share_with_personal_apps_explanation);
} else {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_personal_apps_explanation);
}
}
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index 65bd841..23314e7 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 @@
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 592f7c7..e273286 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.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 @@
/**
* 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 2464fc7..a2f014c 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -205,7 +205,7 @@
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 @@
protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_personal_apps_explanation);
}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 762297d..86f29a8 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -106,13 +106,17 @@
}
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 @@
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 52801fa..c7a36ee 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -130,12 +130,6 @@
// Flags related to media notifications
/**
- * (boolean) If {@code true}, enables the seekbar in compact media notifications.
- */
- public static final String COMPACT_MEDIA_SEEKBAR_ENABLED =
- "compact_media_notification_seekbar_enabled";
-
- /**
* (int) Maximum number of days to retain the salt for hashing direct share targets in logging
*/
public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days";
@@ -380,11 +374,6 @@
*/
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
/**
@@ -415,6 +404,12 @@
public static final String CHOOSER_TARGET_RANKING_ENABLED = "chooser_target_ranking_enabled";
/**
+ * (boolean) Whether dark launch of remote prediction service is enabled.
+ */
+ public static final String DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED =
+ "dark_launch_remote_prediction_service_enabled";
+
+ /**
* (boolean) Whether to enable pinch resizing for PIP.
*/
public static final String PIP_PINCH_RESIZE = "pip_pinch_resize";
@@ -473,6 +468,14 @@
*/
public static final String SHARE_USE_SERVICE_TARGETS = "share_use_service_targets";
+ /**
+ * (boolean) If true, SysUI provides guardrails for app usage of Direct Share by enforcing
+ * limits on number of targets per app & adjusting scores for apps providing many targets. If
+ * false, this step is skipped. This should be true unless the ranking provider configured by
+ * [some other flag] is expected to manage these incentives.
+ */
+ public static final String APPLY_SHARING_APP_LIMITS_IN_SYSUI =
+ "apply_sharing_app_limits_in_sysui";
/*
* (long) The duration that the home button must be pressed before triggering Assist
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 7f87885..b0920b0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -89,6 +89,7 @@
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
@@ -169,7 +170,7 @@
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
@@ -1013,6 +1014,9 @@
@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;
@@ -1194,6 +1198,7 @@
public BatteryStatsImpl(Clocks clocks) {
init(clocks);
+ mStartClockTimeMs = System.currentTimeMillis();
mStatsFile = null;
mCheckinFile = null;
mDailyFile = null;
@@ -6981,6 +6986,16 @@
}
@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);
}
@@ -7840,6 +7855,16 @@
}
@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);
}
@@ -7878,6 +7903,27 @@
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,
@@ -11821,7 +11867,7 @@
* 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);
}
@@ -11852,6 +11898,16 @@
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(
@@ -11896,7 +11952,7 @@
mTmpRailStats.resetCellularTotalEnergyUsed();
}
}
- long radioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
+ long totalAppRadioTimeUs = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000);
mMobileRadioActivePerAppTimer.setMark(elapsedRealtimeMs);
@@ -11956,12 +12012,21 @@
// 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) {
@@ -11986,12 +12051,51 @@
}
}
- 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;
}
@@ -12451,7 +12555,7 @@
// '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)
@@ -12466,6 +12570,50 @@
}
/**
+ * 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.
@@ -12548,81 +12696,6 @@
}
/**
- * SparseDoubleArray map integers to doubles.
- * Its implementation is the same as that of {@link SparseLongArray}; see there for details.
- *
- * @see SparseLongArray
- */
- private static class SparseDoubleArray {
- /**
- * The int->double map, but storing the doubles as longs using
- * {@link Double.doubleToRawLongBits(double)}.
- */
- private final SparseLongArray mValues = new SparseLongArray();
-
- /**
- * Gets the double mapped from the specified key, or <code>0</code>
- * if no such mapping has been made.
- */
- public double get(int key) {
- if (mValues.indexOfKey(key) >= 0) {
- return Double.longBitsToDouble(mValues.get(key));
- }
- return 0;
- }
-
- /**
- * Adds a mapping from the specified key to the specified value,
- * replacing the previous mapping from the specified key if there
- * was one.
- */
- public void put(int key, double value) {
- mValues.put(key, Double.doubleToRawLongBits(value));
- }
-
- /**
- * Adds a mapping from the specified key to the specified value,
- * <b>adding</b> to the previous mapping from the specified key if there
- * was one.
- */
- public void add(int key, double summand) {
- final double oldValue = get(key);
- put(key, oldValue + summand);
- }
-
- /**
- * Returns the number of key-value mappings that this SparseDoubleArray
- * currently stores.
- */
- public int size() {
- return mValues.size();
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the key from the <code>index</code>th key-value mapping that this
- * SparseDoubleArray stores.
- *
- * @see SparseLongArray#keyAt(int)
- */
- public int keyAt(int index) {
- return mValues.keyAt(index);
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the value from the <code>index</code>th key-value mapping that this
- * SparseDoubleArray stores.
- *
- * @see SparseLongArray#valueAt(int)
- */
- public double valueAt(int index) {
- return Double.longBitsToDouble(mValues.valueAt(index));
- }
-
- }
-
- /**
* Read and record Rail Energy data.
*/
public void updateRailStatsLocked() {
@@ -14467,6 +14540,9 @@
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);
}
@@ -16598,6 +16674,8 @@
// 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 6dd612e..f8ae0c1 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.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 @@
}
/**
+ * Returns true if the last update was too long ago for the tolerances specified
+ * by the supplied queries.
+ */
+ 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);
+ allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge());
+ }
+
+ return mStats.mClocks.elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge;
+ }
+
+ /**
* Returns snapshots of battery attribution data, one per supplied query.
*/
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++) {
- BatteryUsageStatsQuery query = queries.get(i);
- for (int userId : query.getUserIds()) {
- UserHandle userHandle = UserHandle.of(userId);
- if (!users.contains(userHandle)) {
- users.add(userHandle);
- }
- }
- }
- batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users);
-
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 @@
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 df25cda..97c4fd8 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -61,7 +61,17 @@
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 @@
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 22001d4..498e1f2 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -96,10 +96,12 @@
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 @@
}
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 @@
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 @@
}
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 @@
}
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 @@
}
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 @@
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 @@
}
/**
+ * 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 39bde74..611fe29 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.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 @@
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 845b3e5..00a0627 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -54,7 +54,9 @@
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 @@
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/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index ec9a0a2..49dbbaa 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -28,7 +28,6 @@
void setActive(boolean active, boolean fullscreen, boolean reportToImeController);
void scheduleStartInputIfNecessary(boolean fullscreen);
void reportFullscreenMode(boolean fullscreen);
- void applyImeVisibility(boolean setVisible);
void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues);
void setImeTraceEnabled(boolean enabled);
}
diff --git a/core/java/com/android/internal/widget/MediaNotificationView.java b/core/java/com/android/internal/widget/MediaNotificationView.java
index f42d5da..8ff3c10 100644
--- a/core/java/com/android/internal/widget/MediaNotificationView.java
+++ b/core/java/com/android/internal/widget/MediaNotificationView.java
@@ -19,31 +19,19 @@
import android.annotation.Nullable;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.NotificationHeaderView;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.RemoteViews;
import java.util.ArrayList;
/**
- * A TextView that can float around an image on the end.
+ * The Layout class which handles template details for the Notification.MediaStyle
*
* @hide
*/
@RemoteViews.RemoteView
public class MediaNotificationView extends FrameLayout {
- private final int mNotificationContentMarginEnd;
- private final int mNotificationContentImageMarginEnd;
- private ImageView mRightIcon;
- private View mActions;
- private NotificationHeaderView mHeader;
- private View mMainColumn;
- private View mMediaContent;
- private int mImagePushIn;
private ArrayList<VisibilityChangeListener> mListeners;
public MediaNotificationView(Context context) {
@@ -58,120 +46,14 @@
this(context, attrs, defStyleAttr, 0);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- boolean hasIcon = mRightIcon.getVisibility() != GONE;
- if (!hasIcon) {
- resetHeaderIndention();
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int mode = MeasureSpec.getMode(widthMeasureSpec);
- boolean reMeasure = false;
- mImagePushIn = 0;
- if (hasIcon && mode != MeasureSpec.UNSPECIFIED) {
- int size = MeasureSpec.getSize(widthMeasureSpec);
- size = size - mActions.getMeasuredWidth();
- ViewGroup.MarginLayoutParams layoutParams =
- (MarginLayoutParams) mRightIcon.getLayoutParams();
- int imageEndMargin = layoutParams.getMarginEnd();
- size -= imageEndMargin;
- int fullHeight = mMediaContent.getMeasuredHeight();
- if (size > fullHeight) {
- size = fullHeight;
- } else if (size < fullHeight) {
- size = Math.max(0, size);
- mImagePushIn = fullHeight - size;
- }
- if (layoutParams.width != fullHeight || layoutParams.height != fullHeight) {
- layoutParams.width = fullHeight;
- layoutParams.height = fullHeight;
- mRightIcon.setLayoutParams(layoutParams);
- reMeasure = true;
- }
-
- // lets ensure that the main column doesn't run into the image
- ViewGroup.MarginLayoutParams params
- = (MarginLayoutParams) mMainColumn.getLayoutParams();
- int marginEnd = size + imageEndMargin + mNotificationContentMarginEnd;
- if (marginEnd != params.getMarginEnd()) {
- params.setMarginEnd(marginEnd);
- mMainColumn.setLayoutParams(params);
- reMeasure = true;
- }
- // TODO(b/172652345): validate all this logic (especially positioning of expand button)
- // margin for the entire header line
- int headerMarginEnd = imageEndMargin;
- // margin for the header text (not including the expand button and other icons)
- int headerExtraMarginEnd = Math.max(0,
- size + imageEndMargin - mHeader.getTopLineBaseMarginEnd());
- if (headerExtraMarginEnd != mHeader.getTopLineExtraMarginEnd()) {
- mHeader.setTopLineExtraMarginEnd(headerExtraMarginEnd);
- reMeasure = true;
- }
- params = (MarginLayoutParams) mHeader.getLayoutParams();
- if (params.getMarginEnd() != headerMarginEnd) {
- params.setMarginEnd(headerMarginEnd);
- mHeader.setLayoutParams(params);
- reMeasure = true;
- }
- if (mHeader.getPaddingEnd() != mNotificationContentImageMarginEnd) {
- mHeader.setPaddingRelative(mHeader.getPaddingStart(),
- mHeader.getPaddingTop(),
- mNotificationContentImageMarginEnd,
- mHeader.getPaddingBottom());
- reMeasure = true;
- }
- }
- if (reMeasure) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mImagePushIn > 0) {
- if (this.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- mImagePushIn *= -1;
- }
- mRightIcon.layout(mRightIcon.getLeft() + mImagePushIn, mRightIcon.getTop(),
- mRightIcon.getRight() + mImagePushIn, mRightIcon.getBottom());
- }
- }
-
- private void resetHeaderIndention() {
- if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) {
- mHeader.setPaddingRelative(mHeader.getPaddingStart(),
- mHeader.getPaddingTop(),
- mNotificationContentMarginEnd,
- mHeader.getPaddingBottom());
- }
- ViewGroup.MarginLayoutParams headerParams =
- (MarginLayoutParams) mHeader.getLayoutParams();
- headerParams.setMarginEnd(0);
- if (headerParams.getMarginEnd() != 0) {
- headerParams.setMarginEnd(0);
- mHeader.setLayoutParams(headerParams);
- }
- }
-
public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mNotificationContentMarginEnd = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_end);
- mNotificationContentImageMarginEnd = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_image_margin_end);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mRightIcon = findViewById(com.android.internal.R.id.right_icon);
- mActions = findViewById(com.android.internal.R.id.media_actions);
- mHeader = findViewById(com.android.internal.R.id.notification_header);
- mMainColumn = findViewById(com.android.internal.R.id.notification_main_column);
- mMediaContent = findViewById(com.android.internal.R.id.notification_media_content);
}
@Override
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f49a834..f1fa5db 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -238,6 +238,7 @@
"[email protected]",
"media_permission-aidl-cpp",
"libandroidicu",
+ "libandroid_net",
"libbpf_android",
"libnetdbpf",
"libnetdutils",
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index b790056..b46b5a2 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -68,7 +68,7 @@
};
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
- jlong width, jlong height, jint format, jboolean enableTripleBuffering) {
+ jlong width, jlong height, jint format) {
String8 str8;
if (jName) {
const jchar* str16 = env->GetStringCritical(jName, nullptr);
@@ -81,7 +81,7 @@
std::string name = str8.string();
sp<BLASTBufferQueue> queue =
new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
- height, format, enableTripleBuffering);
+ height, format);
queue->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(queue.get());
}
@@ -140,7 +140,7 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
- {"nativeCreate", "(Ljava/lang/String;JJJIZ)J", (void*)nativeCreate},
+ {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
{"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
{"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction},
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index d4b5c2b..dd62bb1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -52,6 +52,7 @@
#include <string.h>
#include <sys/epoll.h>
#include <sys/errno.h>
+#include <sys/pidfd.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
@@ -1316,14 +1317,8 @@
return removeAllProcessGroups();
}
-// Wrapper function to the syscall pidfd_open, which creates a file
-// descriptor that refers to the process whose PID is specified in pid.
-static inline int sys_pidfd_open(pid_t pid, unsigned int flags) {
- return syscall(__NR_pidfd_open, pid, flags);
-}
-
static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
- int fd = sys_pidfd_open(pid, flags);
+ int fd = pidfd_open(pid, flags);
if (fd < 0) {
jniThrowErrnoException(env, "nativePidFdOpen", errno);
return -1;
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 52d21a8..10927b9 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 <log/log.h>
-#include <utils/Looper.h>
#include <input/InputTransport.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Looper.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 @@
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 @@
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,45 +213,55 @@
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;
- }
-
- 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());
- }
-
- 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;
- }
- }
-
- 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;
- }
+ const bool notified = notifyFinishedSignal(env, senderObj.get(), *result, skipCallbacks);
+ if (!notified) {
+ skipCallbacks = true;
}
}
}
+/**
+ * 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 (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(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) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f54ffc5..419dc6e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1658,10 +1658,12 @@
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 dca6002..f0badbe 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -296,6 +296,7 @@
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/biometrics.proto b/core/proto/android/server/biometrics.proto
index bbb0edd..4f3ae28b 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -178,4 +178,6 @@
CM_DETECT_INTERACTION = 13;
CM_INVALIDATION_REQUESTER = 14;
CM_INVALIDATE = 15;
+ CM_STOP_USER = 16;
+ CM_START_USER = 17;
}
\ No newline at end of file
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 0d23946..acb7429 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -58,6 +58,9 @@
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 @@
// 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 @@
// 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 aab054f..7b97524 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -21,40 +21,49 @@
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 WaveformProto {
+message StepSegmentProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- repeated int32 timings = 1;
- repeated int32 amplitudes = 2;
- required bool repeat = 3;
+ optional int32 duration = 1;
+ optional float amplitude = 2;
+ optional float frequency = 3;
}
-message PrebakedProto {
+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 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;
- repeated int32 effect_ids = 1;
- repeated float effect_scales = 2;
- repeated int32 delays = 3;
+ optional int32 primitive_id = 1;
+ optional float scale = 2;
+ optional int32 delay = 3;
+}
+
+message SegmentProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ 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 45fd96a..b14a373 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,9 +2689,10 @@
<permission android:name="android.permission.CREATE_USERS"
android:protectionLevel="signature" />
- <!-- @TestApi @hide Allows an application to query user info for all users on the device. -->
- <permission android:name="android.permission.QUERY_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.-->
@@ -2943,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.
@@ -3447,6 +3487,14 @@
<permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
android:protectionLevel="signature" />
+ <!-- Allows an application to avoid all toast rate limiting restrictions.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.UNLIMITED_TOASTS"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
<!-- @SystemApi Allows an application to use
{@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
to hide non-system-overlay windows.
@@ -3550,7 +3598,7 @@
@hide
-->
<permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|recents" />
<!-- Allows an application to retrieve the current state of keys and
switches.
@@ -4184,6 +4232,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
@@ -4397,6 +4450,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 -->
@@ -4484,7 +4544,7 @@
<permission android:name="android.permission.FACTORY_TEST"
android:protectionLevel="signature" />
- <!-- @hide @TestApi Allows an application to broadcast the intent {@link
+ <!-- @hide @TestApi @SystemApi Allows an application to broadcast the intent {@link
android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
<p>Not for use by third-party applications.
-->
@@ -4907,6 +4967,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" />
@@ -5369,12 +5434,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"
@@ -5504,6 +5563,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/remote_color_resources_res/values/colors.xml b/core/res/remote_color_resources_res/values/colors.xml
index 295f16e..e4bcae43 100644
--- a/core/res/remote_color_resources_res/values/colors.xml
+++ b/core/res/remote_color_resources_res/values/colors.xml
@@ -1,40 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Note: the values of the colors doesn't really matter (they will always be overwritten before used), but they help a lot debugging, to find out which color is where in the ARSC file. -->
- <color name="system_primary_0">#01010101</color>
- <color name="system_primary_50">#02020202</color>
- <color name="system_primary_100">#03030303</color>
- <color name="system_primary_200">#04040404</color>
- <color name="system_primary_300">#05050505</color>
- <color name="system_primary_400">#06060606</color>
- <color name="system_primary_500">#07070707</color>
- <color name="system_primary_600">#08080808</color>
- <color name="system_primary_700">#09090909</color>
- <color name="system_primary_800">#0a0a0a0a</color>
- <color name="system_primary_900">#0b0b0b0b</color>
- <color name="system_primary_1000">#0c0c0c0c</color>
- <color name="system_secondary_0">#10101010</color>
- <color name="system_secondary_50">#20202020</color>
- <color name="system_secondary_100">#30303030</color>
- <color name="system_secondary_200">#40404040</color>
- <color name="system_secondary_300">#50505050</color>
- <color name="system_secondary_400">#60606060</color>
- <color name="system_secondary_500">#70707070</color>
- <color name="system_secondary_600">#80808080</color>
- <color name="system_secondary_700">#90909090</color>
- <color name="system_secondary_800">#a0a0a0a0</color>
- <color name="system_secondary_900">#b0b0b0b0</color>
- <color name="system_secondary_1000">#c0c0c0c0</color>
- <color name="system_neutral_0">#1f1f1f1f</color>
- <color name="system_neutral_50">#2f2f2f2f</color>
- <color name="system_neutral_100">#3f3f3f3f</color>
- <color name="system_neutral_200">#4f4f4f4f</color>
- <color name="system_neutral_300">#5f5f5f5f</color>
- <color name="system_neutral_400">#6f6f6f6f</color>
- <color name="system_neutral_500">#7f7f7f7f</color>
- <color name="system_neutral_600">#8f8f8f8f</color>
- <color name="system_neutral_700">#9f9f9f9f</color>
- <color name="system_neutral_800">#afafafaf</color>
- <color name="system_neutral_900">#bfbfbfbf</color>
- <color name="system_neutral_1000">#cfcfcfcf</color>
+ <color name="system_accent1_0">#ffffff</color>
+ <color name="system_accent1_50">#91fff4</color>
+ <color name="system_accent1_100">#83f6e5</color>
+ <color name="system_accent1_200">#65d9c9</color>
+ <color name="system_accent1_300">#45bdae</color>
+ <color name="system_accent1_400">#1fa293</color>
+ <color name="system_accent1_500">#008377</color>
+ <color name="system_accent1_600">#006d61</color>
+ <color name="system_accent1_700">#005449</color>
+ <color name="system_accent1_800">#003c33</color>
+ <color name="system_accent1_900">#00271e</color>
+ <color name="system_accent1_1000">#000000</color>
+ <color name="system_accent2_0">#ffffff</color>
+ <color name="system_accent2_50">#91fff4</color>
+ <color name="system_accent2_100">#83f6e5</color>
+ <color name="system_accent2_200">#65d9c9</color>
+ <color name="system_accent2_300">#45bdae</color>
+ <color name="system_accent2_400">#1fa293</color>
+ <color name="system_accent2_500">#008377</color>
+ <color name="system_accent2_600">#006d61</color>
+ <color name="system_accent2_700">#005449</color>
+ <color name="system_accent2_800">#003c33</color>
+ <color name="system_accent2_900">#00271e</color>
+ <color name="system_accent2_1000">#000000</color>
+ <color name="system_accent3_0">#ffffff</color>
+ <color name="system_accent3_50">#91fff4</color>
+ <color name="system_accent3_100">#83f6e5</color>
+ <color name="system_accent3_200">#65d9c9</color>
+ <color name="system_accent3_300">#45bdae</color>
+ <color name="system_accent3_400">#1fa293</color>
+ <color name="system_accent3_500">#008377</color>
+ <color name="system_accent3_600">#006d61</color>
+ <color name="system_accent3_700">#005449</color>
+ <color name="system_accent3_800">#003c33</color>
+ <color name="system_accent3_900">#00271e</color>
+ <color name="system_accent3_1000">#000000</color>
+ <color name="system_neutral1_0">#ffffff</color>
+ <color name="system_neutral1_50">#f0f0f0</color>
+ <color name="system_neutral1_100">#e2e2e2</color>
+ <color name="system_neutral1_200">#c6c6c6</color>
+ <color name="system_neutral1_300">#ababab</color>
+ <color name="system_neutral1_400">#909090</color>
+ <color name="system_neutral1_500">#757575</color>
+ <color name="system_neutral1_600">#5e5e5e</color>
+ <color name="system_neutral1_700">#464646</color>
+ <color name="system_neutral1_800">#303030</color>
+ <color name="system_neutral1_900">#1b1b1b</color>
+ <color name="system_neutral1_1000">#000000</color>
+ <color name="system_neutral2_0">#ffffff</color>
+ <color name="system_neutral2_50">#f0f0f0</color>
+ <color name="system_neutral2_100">#e2e2e2</color>
+ <color name="system_neutral2_200">#c6c6c6</color>
+ <color name="system_neutral2_300">#ababab</color>
+ <color name="system_neutral2_400">#909090</color>
+ <color name="system_neutral2_500">#757575</color>
+ <color name="system_neutral2_600">#5e5e5e</color>
+ <color name="system_neutral2_700">#464646</color>
+ <color name="system_neutral2_800">#303030</color>
+ <color name="system_neutral2_900">#1b1b1b</color>
+ <color name="system_neutral2_1000">#000000</color>
</resources>
diff --git a/core/res/remote_color_resources_res/values/public.xml b/core/res/remote_color_resources_res/values/public.xml
index e628f09..9616628 100644
--- a/core/res/remote_color_resources_res/values/public.xml
+++ b/core/res/remote_color_resources_res/values/public.xml
@@ -1,41 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<public-group type="color" first-id="0x0106001d">
- <public name="system_primary_0" />
- <public name="system_primary_50" />
- <public name="system_primary_100" />
- <public name="system_primary_200" />
- <public name="system_primary_300" />
- <public name="system_primary_400" />
- <public name="system_primary_500" />
- <public name="system_primary_600" />
- <public name="system_primary_700" />
- <public name="system_primary_800" />
- <public name="system_primary_900" />
- <public name="system_primary_1000" />
- <public name="system_secondary_0" />
- <public name="system_secondary_50" />
- <public name="system_secondary_100" />
- <public name="system_secondary_200" />
- <public name="system_secondary_300" />
- <public name="system_secondary_400" />
- <public name="system_secondary_500" />
- <public name="system_secondary_600" />
- <public name="system_secondary_700" />
- <public name="system_secondary_800" />
- <public name="system_secondary_900" />
- <public name="system_secondary_1000" />
- <public name="system_neutral_0" />
- <public name="system_neutral_50" />
- <public name="system_neutral_100" />
- <public name="system_neutral_200" />
- <public name="system_neutral_300" />
- <public name="system_neutral_400" />
- <public name="system_neutral_500" />
- <public name="system_neutral_600" />
- <public name="system_neutral_700" />
- <public name="system_neutral_800" />
- <public name="system_neutral_900" />
- <public name="system_neutral_1000" />
+ <public name="system_accent1_0" />
+ <public name="system_accent1_50" />
+ <public name="system_accent1_100" />
+ <public name="system_accent1_200" />
+ <public name="system_accent1_300" />
+ <public name="system_accent1_400" />
+ <public name="system_accent1_500" />
+ <public name="system_accent1_600" />
+ <public name="system_accent1_700" />
+ <public name="system_accent1_800" />
+ <public name="system_accent1_900" />
+ <public name="system_accent1_1000" />
+ <public name="system_accent2_0" />
+ <public name="system_accent2_50" />
+ <public name="system_accent2_100" />
+ <public name="system_accent2_200" />
+ <public name="system_accent2_300" />
+ <public name="system_accent2_400" />
+ <public name="system_accent2_500" />
+ <public name="system_accent2_600" />
+ <public name="system_accent2_700" />
+ <public name="system_accent2_800" />
+ <public name="system_accent2_900" />
+ <public name="system_accent2_1000" />
+ <public name="system_accent3_0" />
+ <public name="system_accent3_50" />
+ <public name="system_accent3_100" />
+ <public name="system_accent3_200" />
+ <public name="system_accent3_300" />
+ <public name="system_accent3_400" />
+ <public name="system_accent3_500" />
+ <public name="system_accent3_600" />
+ <public name="system_accent3_700" />
+ <public name="system_accent3_800" />
+ <public name="system_accent3_900" />
+ <public name="system_accent3_1000" />
+ <public name="system_neutral1_0" />
+ <public name="system_neutral1_50" />
+ <public name="system_neutral1_100" />
+ <public name="system_neutral1_200" />
+ <public name="system_neutral1_300" />
+ <public name="system_neutral1_400" />
+ <public name="system_neutral1_500" />
+ <public name="system_neutral1_600" />
+ <public name="system_neutral1_700" />
+ <public name="system_neutral1_800" />
+ <public name="system_neutral1_900" />
+ <public name="system_neutral1_1000" />
+ <public name="system_neutral2_0" />
+ <public name="system_neutral2_50" />
+ <public name="system_neutral2_100" />
+ <public name="system_neutral2_200" />
+ <public name="system_neutral2_300" />
+ <public name="system_neutral2_400" />
+ <public name="system_neutral2_500" />
+ <public name="system_neutral2_600" />
+ <public name="system_neutral2_700" />
+ <public name="system_neutral2_800" />
+ <public name="system_neutral2_900" />
+ <public name="system_neutral2_1000" />
</public-group>
</resources>
diff --git a/core/res/res/color/text_color_primary_device_default_dark.xml b/core/res/res/color/text_color_primary_device_default_dark.xml
index 90d6b07..5926fde 100644
--- a/core/res/res/color/text_color_primary_device_default_dark.xml
+++ b/core/res/res/color/text_color_primary_device_default_dark.xml
@@ -17,7 +17,6 @@
<!-- Please see primary_text_material_dark.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_50"/>
- <item android:color="@color/system_primary_50"/>
+ android:color="@color/system_neutral1_500"/>
+ <item android:color="@color/system_neutral1_50"/>
</selector>
diff --git a/core/res/res/color/text_color_primary_device_default_light.xml b/core/res/res/color/text_color_primary_device_default_light.xml
index bdc4fa9..1379523 100644
--- a/core/res/res/color/text_color_primary_device_default_light.xml
+++ b/core/res/res/color/text_color_primary_device_default_light.xml
@@ -17,7 +17,6 @@
<!-- Please see primary_text_material_light.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_900"/>
- <item android:color="@color/system_primary_900"/>
+ android:color="@color/system_neutral1_400"/>
+ <item android:color="@color/system_neutral1_900"/>
</selector>
diff --git a/core/res/res/color/text_color_secondary_device_default_dark.xml b/core/res/res/color/text_color_secondary_device_default_dark.xml
index 799636ad..f79fd62 100644
--- a/core/res/res/color/text_color_secondary_device_default_dark.xml
+++ b/core/res/res/color/text_color_secondary_device_default_dark.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_200"/>
- <item android:color="@color/system_primary_200"/>
+ android:color="@color/system_neutral2_200"/>
+ <item android:color="@color/system_neutral2_200"/>
</selector>
diff --git a/core/res/res/color/text_color_secondary_device_default_light.xml b/core/res/res/color/text_color_secondary_device_default_light.xml
index 4793bb8..c58f565 100644
--- a/core/res/res/color/text_color_secondary_device_default_light.xml
+++ b/core/res/res/color/text_color_secondary_device_default_light.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_700"/>
- <item android:color="@color/system_primary_700"/>
+ android:color="@color/system_neutral2_700"/>
+ <item android:color="@color/system_neutral2_700"/>
</selector>
diff --git a/core/res/res/color/text_color_tertiary_device_default_dark.xml b/core/res/res/color/text_color_tertiary_device_default_dark.xml
index c828631..63fdc81 100644
--- a/core/res/res/color/text_color_tertiary_device_default_dark.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_dark.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_400"/>
- <item android:color="@color/system_primary_400"/>
+ android:color="@color/system_neutral2_400"/>
+ <item android:color="@color/system_neutral2_400"/>
</selector>
diff --git a/core/res/res/color/text_color_tertiary_device_default_light.xml b/core/res/res/color/text_color_tertiary_device_default_light.xml
index 82c420a..1ad6f6a 100644
--- a/core/res/res/color/text_color_tertiary_device_default_light.xml
+++ b/core/res/res/color/text_color_tertiary_device_default_light.xml
@@ -18,6 +18,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:alpha="?attr/disabledAlpha"
- android:color="@color/system_primary_500"/>
- <item android:color="@color/system_primary_500"/>
+ android:color="@color/system_neutral2_500"/>
+ <item android:color="@color/system_neutral2_500"/>
</selector>
diff --git a/core/res/res/drawable/bottomsheet_background.xml b/core/res/res/drawable/bottomsheet_background.xml
index 3a8ad71..00d53004 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 0dd9e9c7..18bbd93 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 6af7937..16ffaa4 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 c0de693..10683b1 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 2a39215..d8c1d17 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 62df165..0d04d7f3 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 1d18648..bc4f327 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 86dc71c..912173c 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/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index dd79a0b..5f1b60e 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -24,7 +24,6 @@
android:paddingTop="8dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
- android:layout_marginEnd="2dp"
android:gravity="center"
android:background="@drawable/notification_material_media_action_background"
android:visibility="gone"
diff --git a/core/res/res/layout/notification_material_media_seekbar.xml b/core/res/res/layout/notification_material_media_seekbar.xml
deleted file mode 100644
index 4aa8acc..0000000
--- a/core/res/res/layout/notification_material_media_seekbar.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?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
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/notification_media_progress"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_alignParentBottom="true"
- >
- <SeekBar android:id="@+id/notification_media_progress_bar"
- style="@style/Widget.ProgressBar.Horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxHeight="3dp"
- android:paddingTop="24dp"
- android:paddingBottom="24dp"
- android:clickable="true"
- android:layout_marginBottom="-24dp"
- android:layout_marginTop="-12dp"
- android:splitTrack="false"
- />
- <FrameLayout
- android:id="@+id/notification_media_progress_time"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginBottom="11dp"
- >
-
- <!-- width is set to "match_parent" to avoid extra layout calls -->
- <TextView android:id="@+id/notification_media_elapsed_time"
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:gravity="start"
- />
-
- <TextView android:id="@+id/notification_media_total_time"
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:gravity="end"
- />
- </FrameLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index bad9a6b..e644cd5 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -46,20 +46,6 @@
android:padding="@dimen/notification_icon_circle_padding"
/>
- <ImageView
- android:id="@+id/right_icon"
- android:layout_width="@dimen/notification_right_icon_size"
- android:layout_height="@dimen/notification_right_icon_size"
- android:layout_gravity="center_vertical|end"
- android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
- android:layout_marginEnd="@dimen/notification_header_expand_icon_size"
- android:background="@drawable/notification_large_icon_outline"
- android:clipToOutline="true"
- android:importantForAccessibility="no"
- android:scaleType="centerCrop"
- />
-
<FrameLayout
android:id="@+id/alternate_expand_target"
android:layout_width="@dimen/notification_content_margin_start"
@@ -68,95 +54,116 @@
android:importantForAccessibility="no"
/>
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="end">
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
- />
-
- </FrameLayout>
-
<LinearLayout
- android:id="@+id/notification_headerless_view_column"
+ android:id="@+id/notification_headerless_view_row"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
- android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
- android:orientation="vertical"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
>
- <!-- extends ViewGroup -->
- <NotificationTopLineView
- android:id="@+id/notification_top_line"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_headerless_line_height"
- android:layout_marginEnd="@dimen/notification_heading_margin_end"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:clipChildren="false"
- android:theme="@style/Theme.DeviceDefault.Notification"
- >
-
- <!--
- NOTE: The notification_top_line_views layout contains the app_name_text.
- In order to include the title view at the beginning, the Notification.Builder
- has logic to hide that view whenever this title view is to be visible.
- -->
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_header_separating_margin"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:singleLine="true"
- android:textAlignment="viewStart"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- />
-
- <include layout="@layout/notification_top_line_views" />
-
- </NotificationTopLineView>
-
<LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/notification_heading_margin_end"
- android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
android:orientation="vertical"
>
- <com.android.internal.widget.NotificationVanishingFrameLayout
- android:layout_width="match_parent"
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
>
- <!-- This is the simplest way to keep this text vertically centered without using
- gravity="center_vertical" which causes jumpiness in expansion animations. -->
- <include
- layout="@layout/notification_template_text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="center_vertical"
- android:layout_marginTop="0dp"
- />
- </com.android.internal.widget.NotificationVanishingFrameLayout>
- <include
- layout="@layout/notification_template_progress"
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_headerless_line_height"
- />
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <com.android.internal.widget.NotificationVanishingFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ />
+
+ </LinearLayout>
</LinearLayout>
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
</LinearLayout>
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index aa20ad36..ff64315 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -20,17 +20,8 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="#00000000"
android:tag="bigMediaNarrow"
>
- <!-- The size will actually be determined at runtime -->
- <ImageView
- android:id="@+id/right_icon"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_gravity="top|end"
- android:scaleType="centerCrop"
- />
<include
layout="@layout/notification_template_header"
@@ -49,46 +40,27 @@
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="46dp"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginBottom="@dimen/notification_content_margin"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
>
- <!-- TODO(b/172652345): fix the media style -->
- <!--<include layout="@layout/notification_template_part_line1"/>-->
- <!--<include layout="@layout/notification_template_text"/>-->
-
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart"
- />
-
- <com.android.internal.widget.ImageFloatingTextView
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="top"
- android:layout_marginTop="0.5dp"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:gravity="top"
- android:singleLine="true"
- android:textAlignment="viewStart"
- />
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_text"/>
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-21dp"
+ android:paddingStart="44dp"
+ android:paddingEnd="44dp"
+ android:paddingBottom="@dimen/media_notification_actions_padding_bottom"
+ android:gravity="top"
android:orientation="horizontal"
android:layoutDirection="ltr"
- style="@style/NotificationMediaActionContainer"
>
<include
@@ -117,10 +89,8 @@
/>
</LinearLayout>
- <ViewStub
- android:id="@+id/notification_media_seekbar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
</LinearLayout>
+
+ <include layout="@layout/notification_template_right_icon" />
+
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 542e59d..2991b17 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -19,101 +19,173 @@
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#00000000"
+ android:layout_height="@dimen/notification_min_height"
android:tag="media"
>
- <ImageView android:id="@+id/right_icon"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:adjustViewBounds="true"
- android:layout_gravity="top|end"
+
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_left_icon_size"
+ android:layout_height="@dimen/notification_left_icon_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
android:scaleType="centerCrop"
- />
- <include layout="@layout/notification_template_header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/media_notification_header_height"
+ android:visibility="gone"
/>
+
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ />
+
<LinearLayout
+ android:id="@+id/notification_headerless_view_row"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:id="@+id/notification_media_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
>
+
<LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:layout_marginTop="46dp"
- android:layout_alignParentTop="true"
- android:tag="media"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:orientation="vertical"
>
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
<LinearLayout
- android:id="@+id/notification_content_container"
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="fill_vertical"
- android:layout_weight="1"
- android:paddingBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
- <!-- TODO(b/172652345): fix the media style -->
- <!--<include layout="@layout/notification_template_part_line1"/>-->
- <!--<include layout="@layout/notification_template_text"/>-->
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ <com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:textAlignment="viewStart"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
/>
- <com.android.internal.widget.ImageFloatingTextView
- style="@style/Widget.DeviceDefault.Notification.Text"
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_text_height"
- android:layout_gravity="top"
- android:layout_marginTop="0.5dp"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:gravity="top"
- android:singleLine="true"
- android:textAlignment="viewStart"
- />
</LinearLayout>
- <LinearLayout
- android:id="@+id/media_actions"
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layoutDirection="ltr"
+ android:orientation="horizontal"
+ >
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action0"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action1"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action2"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|end"
- android:layout_marginStart="10dp"
- android:layoutDirection="ltr"
- android:orientation="horizontal"
- >
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action0"
+ android:layout_gravity="center_vertical|end"
/>
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action1"
- />
- <include
- layout="@layout/notification_material_media_action"
- android:id="@+id/action2"
- />
- </LinearLayout>
- </LinearLayout>
- <ViewStub android:id="@+id/notification_media_seekbar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- />
+
+ </FrameLayout>
+
</LinearLayout>
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index bdcfeb2..8594c33 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 6fde1df..d791598 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 9410301..d6ca7ab 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 4a5aa02..7610e73 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 4410e94..baffa5a 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 5e95f94..4b15e01 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 140163e..986bb82 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 22467e4..0213c60 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -237,121 +237,194 @@
<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>
<color name="conversation_important_highlight">#F9AB00</color>
- <!-- Lightest shade of the primary color used by the system. White.
+ <!-- Lightest shade of the accent color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_0">#ffffff</color>
- <!-- Shade of the primary system color at 95% lightness.
+ <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_primary_50">#f2f2f2</color>
- <!-- Shade of the primary system color at 90% lightness.
+ <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_primary_100">#e3e3e3</color>
- <!-- Shade of the primary system color at 80% lightness.
+ <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_primary_200">#c7c7c7</color>
- <!-- Shade of the primary system color at 70% lightness.
+ <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_primary_300">#ababab</color>
- <!-- Shade of the primary system color at 60% lightness.
+ <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_primary_400">#8f8f8f</color>
- <!-- Shade of the primary system color at 50% lightness.
+ <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_primary_500">#757575</color>
- <!-- Shade of the primary system color at 40% lightness.
+ <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_primary_600">#5e5e5e</color>
- <!-- Shade of the primary system color at 30% lightness.
+ <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_primary_700">#474747</color>
- <!-- Shade of the primary system color at 20% lightness.
+ <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_primary_800">#303030</color>
- <!-- Shade of the primary system color at 10% lightness.
+ <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_primary_900">#1f1f1f</color>
- <!-- Darkest shade of the primary color used by the system. Black.
+ <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_primary_1000">#000000</color>
+ <color name="system_accent1_1000">#000000</color>
- <!-- Lightest shade of the secondary color used by the system. White.
+ <!-- Lightest shade of the secondary accent color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_secondary_0">#ffffff</color>
- <!-- Shade of the secondary system color at 95% lightness.
+ <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_secondary_50">#91fff4</color>
- <!-- Shade of the secondary system color at 90% lightness.
+ <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_secondary_100">#83f6e5</color>
- <!-- Shade of the secondary system color at 80% lightness.
+ <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_secondary_200">#65d9c9</color>
- <!-- Shade of the secondary system color at 70% lightness.
+ <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_secondary_300">#45bdae</color>
- <!-- Shade of the secondary system color at 60% lightness.
+ <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_secondary_400">#1fa293</color>
- <!-- Shade of the secondary system color at 50% lightness.
+ <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_secondary_500">#008377</color>
- <!-- Shade of the secondary system color at 40% lightness.
+ <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_secondary_600">#006d61</color>
- <!-- Shade of the secondary system color at 30% lightness.
+ <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_secondary_700">#005449</color>
- <!-- Shade of the secondary system color at 20% lightness.
+ <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_secondary_800">#003c33</color>
- <!-- Shade of the secondary system color at 10% lightness.
+ <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_secondary_900">#00271e</color>
- <!-- Darkest shade of the secondary color used by the system. Black.
+ <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_secondary_1000">#000000</color>
+ <color name="system_accent2_1000">#000000</color>
+
+ <!-- Lightest shade of the tertiary accent color used by the system. White.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <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">#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">#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">#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">#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">#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">#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">#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">#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">#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">#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>
<!-- Lightest shade of the neutral color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_0">#ffffff</color>
+ <color name="system_neutral1_0">#ffffff</color>
<!-- Shade of the neutral system color at 95% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_50">#f0f0f0</color>
+ <color name="system_neutral1_50">#f0f0f0</color>
<!-- Shade of the neutral system color at 90% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_100">#e2e2e2</color>
+ <color name="system_neutral1_100">#e2e2e2</color>
<!-- Shade of the neutral system color at 80% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_200">#c6c6c6</color>
+ <color name="system_neutral1_200">#c6c6c6</color>
<!-- Shade of the neutral system color at 70% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_300">#ababab</color>
+ <color name="system_neutral1_300">#ababab</color>
<!-- Shade of the neutral system color at 60% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_400">#909090</color>
- <!-- Shade of the neutral system color at 50% lightness.
+ <color name="system_neutral1_400">#909090</color>
+ <!-- Shade of the neutral system color at 49% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_500">#757575</color>
+ <color name="system_neutral1_500">#757575</color>
<!-- Shade of the neutral system color at 40% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_600">#5e5e5e</color>
+ <color name="system_neutral1_600">#5e5e5e</color>
<!-- Shade of the neutral system color at 30% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_700">#464646</color>
+ <color name="system_neutral1_700">#464646</color>
<!-- Shade of the neutral system color at 20% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_800">#303030</color>
+ <color name="system_neutral1_800">#303030</color>
<!-- Shade of the neutral system color at 10% lightness.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_900">#1b1b1b</color>
+ <color name="system_neutral1_900">#1b1b1b</color>
<!-- Darkest shade of the neutral color used by the system. Black.
This value can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_neutral_1000">#000000</color>
+ <color name="system_neutral1_1000">#000000</color>
+
+ <!-- Lightest shade of the secondary neutral color used by the system. White.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_0">#ffffff</color>
+ <!-- Shade of the secondary neutral system color at 95% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_50">#f0f0f0</color>
+ <!-- Shade of the secondary neutral system color at 90% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_100">#e2e2e2</color>
+ <!-- Shade of the secondary neutral system color at 80% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_200">#c6c6c6</color>
+ <!-- Shade of the secondary neutral system color at 70% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_300">#ababab</color>
+ <!-- Shade of the secondary neutral system color at 60% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_400">#909090</color>
+ <!-- Shade of the secondary neutral system color at 49% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_500">#757575</color>
+ <!-- Shade of the secondary neutral system color at 40% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_600">#5e5e5e</color>
+ <!-- Shade of the secondary neutral system color at 30% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_700">#464646</color>
+ <!-- Shade of the secondary neutral system color at 20% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_800">#303030</color>
+ <!-- Shade of the secondary neutral system color at 10% lightness.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_900">#1b1b1b</color>
+ <!-- Darkest shade of the secondary neutral color used by the system. Black.
+ This value can be overlaid at runtime by OverlayManager RROs. -->
+ <color name="system_neutral2_1000">#000000</color>
</resources>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 9b56321..0b41769 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -17,9 +17,9 @@
<!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
overlaying new theme colors. -->
<resources>
- <color name="primary_device_default_dark">@color/system_primary_800</color>
- <color name="primary_device_default_light">@color/system_primary_50</color>
- <color name="primary_device_default_settings">@color/system_primary_800</color>
+ <color name="primary_device_default_dark">@color/system_neutral1_800</color>
+ <color name="primary_device_default_light">@color/system_neutral1_50</color>
+ <color name="primary_device_default_settings">@color/system_neutral1_800</color>
<color name="primary_device_default_settings_light">@color/primary_device_default_light</color>
<color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color>
<color name="primary_dark_device_default_light">@color/primary_device_default_light</color>
@@ -33,14 +33,17 @@
<color name="tertiary_device_default_settings">@color/tertiary_material_settings</color>
<color name="quaternary_device_default_settings">@color/quaternary_material_settings</color>
- <color name="accent_device_default_light">@color/system_secondary_600</color>
- <color name="accent_device_default_dark">@color/system_secondary_200</color>
+ <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_primary_800</color>
- <color name="background_device_default_light">@color/system_primary_50</color>
- <color name="background_floating_device_default_dark">@color/system_primary_900</color>
- <color name="background_floating_device_default_light">@color/system_primary_100</color>
+ <color name="background_device_default_dark">@color/system_neutral1_800</color>
+ <color name="background_device_default_light">@color/system_neutral1_50</color>
+ <color name="background_floating_device_default_dark">@color/system_neutral1_900</color>
+ <color name="background_floating_device_default_light">@color/system_neutral1_100</color>
<!-- Please refer to text_color_[primary]_device_default_[light].xml for text colors-->
<color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color>
@@ -50,8 +53,8 @@
<color name="error_color_device_default_dark">@color/error_color_material_dark</color>
<color name="error_color_device_default_light">@color/error_color_material_light</color>
- <color name="list_divider_color_light">@color/system_primary_500</color>
- <color name="list_divider_color_dark">@color/system_primary_400</color>
+ <color name="list_divider_color_light">@color/system_neutral1_200</color>
+ <color name="list_divider_color_dark">@color/system_neutral1_700</color>
<color name="list_divider_opacity_device_default_light">@android:color/white</color>
<color name="list_divider_opacity_device_default_dark">@android:color/white</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b4ef63f..b6c22bb 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/dimens.xml b/core/res/res/values/dimens.xml
index 43dbd38..afbbe46 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -226,9 +226,6 @@
<!-- The margin on the end of the top-line content views (accommodates the expander) -->
<dimen name="notification_heading_margin_end">56dp</dimen>
- <!-- The margin for text at the end of the image view for media notifications -->
- <dimen name="notification_media_image_margin_end">72dp</dimen>
-
<!-- The height of the notification action list -->
<dimen name="notification_action_list_height">60dp</dimen>
@@ -345,6 +342,9 @@
<!-- The minimum width of the app name in the header if it shrinks -->
<dimen name="notification_header_shrink_min_width">72dp</dimen>
+ <!-- The minimum width of optional header fields below which the view is simply hidden -->
+ <dimen name="notification_header_shrink_hide_width">24sp</dimen>
+
<!-- The size of the media actions in the media notification. -->
<dimen name="media_notification_action_button_size">48dp</dimen>
@@ -360,9 +360,6 @@
<!-- The absolute height for the header in a media notification. -->
<dimen name="media_notification_header_height">@dimen/notification_header_height</dimen>
- <!-- The margin of the content to an image-->
- <dimen name="notification_content_image_margin_end">8dp</dimen>
-
<!-- The padding at the end of actions when the snooze and bubble buttons are gone-->
<dimen name="snooze_and_bubble_gone_padding_end">12dp</dimen>
@@ -483,9 +480,6 @@
<!-- Top padding for notification when text is large and narrow (i.e. it has 3 lines -->
<dimen name="notification_top_pad_large_text_narrow">-4dp</dimen>
- <!-- Padding for notification icon when drawn with circle around it -->
- <dimen name="notification_large_icon_circle_padding">11dp</dimen>
-
<!-- The margin on top of the text of the notification -->
<dimen name="notification_text_margin_top">6dp</dimen>
@@ -736,12 +730,10 @@
<dimen name="notification_big_picture_max_height">284dp</dimen>
<!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. This value is determined by the standard panel size -->
<dimen name="notification_big_picture_max_width">416dp</dimen>
- <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. This value is determined by the expanded media template-->
- <dimen name="notification_media_image_max_height">140dp</dimen>
- <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
- <dimen name="notification_media_image_max_width">280dp</dimen>
<!-- The size of the right icon -->
<dimen name="notification_right_icon_size">48dp</dimen>
+ <!-- The margin between the right icon and the content. -->
+ <dimen name="notification_right_icon_content_margin">12dp</dimen>
<!-- The top and bottom margin of the right icon in the normal notification states -->
<dimen name="notification_right_icon_headerless_margin">20dp</dimen>
<!-- The top margin of the right icon in the "big" notification states -->
@@ -762,10 +754,6 @@
<dimen name="notification_big_picture_max_height_low_ram">208dp</dimen>
<!-- The maximum width of a big picture in a notification. The images will be reduced to that width in case they are bigger. -->
<dimen name="notification_big_picture_max_width_low_ram">294dp</dimen>
- <!-- The maximum height of a image in a media notification. The images will be reduced to that height in case they are bigger. -->
- <dimen name="notification_media_image_max_height_low_ram">100dp</dimen>
- <!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
- <dimen name="notification_media_image_max_width_low_ram">100dp</dimen>
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen>
<!-- The maximum size of the grayscale icon -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 3e5fad8..bc49818 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">
@@ -3102,45 +3105,68 @@
<!-- color definitions go here -->
<!-- Material design dynamic system palette:-->
- <!-- Primary color -->
- <public name="system_primary_0" />
- <public name="system_primary_50" />
- <public name="system_primary_100" />
- <public name="system_primary_200" />
- <public name="system_primary_300" />
- <public name="system_primary_400" />
- <public name="system_primary_500" />
- <public name="system_primary_600" />
- <public name="system_primary_700" />
- <public name="system_primary_800" />
- <public name="system_primary_900" />
- <public name="system_primary_1000" />
- <!-- Secondary color -->
- <public name="system_secondary_0" />
- <public name="system_secondary_50" />
- <public name="system_secondary_100" />
- <public name="system_secondary_200" />
- <public name="system_secondary_300" />
- <public name="system_secondary_400" />
- <public name="system_secondary_500" />
- <public name="system_secondary_600" />
- <public name="system_secondary_700" />
- <public name="system_secondary_800" />
- <public name="system_secondary_900" />
- <public name="system_secondary_1000" />
- <!-- Neutral color -->
- <public name="system_neutral_0" />
- <public name="system_neutral_50" />
- <public name="system_neutral_100" />
- <public name="system_neutral_200" />
- <public name="system_neutral_300" />
- <public name="system_neutral_400" />
- <public name="system_neutral_500" />
- <public name="system_neutral_600" />
- <public name="system_neutral_700" />
- <public name="system_neutral_800" />
- <public name="system_neutral_900" />
- <public name="system_neutral_1000" />
+ <!-- Neutral colors for background and text -->
+ <public name="system_neutral1_0" />
+ <public name="system_neutral1_50" />
+ <public name="system_neutral1_100" />
+ <public name="system_neutral1_200" />
+ <public name="system_neutral1_300" />
+ <public name="system_neutral1_400" />
+ <public name="system_neutral1_500" />
+ <public name="system_neutral1_600" />
+ <public name="system_neutral1_700" />
+ <public name="system_neutral1_800" />
+ <public name="system_neutral1_900" />
+ <public name="system_neutral1_1000" />
+ <public name="system_neutral2_0" />
+ <public name="system_neutral2_50" />
+ <public name="system_neutral2_100" />
+ <public name="system_neutral2_200" />
+ <public name="system_neutral2_300" />
+ <public name="system_neutral2_400" />
+ <public name="system_neutral2_500" />
+ <public name="system_neutral2_600" />
+ <public name="system_neutral2_700" />
+ <public name="system_neutral2_800" />
+ <public name="system_neutral2_900" />
+ <public name="system_neutral2_1000" />
+ <!-- Accent colors, for buttons and UI decorations -->
+ <public name="system_accent1_0" />
+ <public name="system_accent1_50" />
+ <public name="system_accent1_100" />
+ <public name="system_accent1_200" />
+ <public name="system_accent1_300" />
+ <public name="system_accent1_400" />
+ <public name="system_accent1_500" />
+ <public name="system_accent1_600" />
+ <public name="system_accent1_700" />
+ <public name="system_accent1_800" />
+ <public name="system_accent1_900" />
+ <public name="system_accent1_1000" />
+ <public name="system_accent2_0" />
+ <public name="system_accent2_50" />
+ <public name="system_accent2_100" />
+ <public name="system_accent2_200" />
+ <public name="system_accent2_300" />
+ <public name="system_accent2_400" />
+ <public name="system_accent2_500" />
+ <public name="system_accent2_600" />
+ <public name="system_accent2_700" />
+ <public name="system_accent2_800" />
+ <public name="system_accent2_900" />
+ <public name="system_accent2_1000" />
+ <public name="system_accent3_0" />
+ <public name="system_accent3_50" />
+ <public name="system_accent3_100" />
+ <public name="system_accent3_200" />
+ <public name="system_accent3_300" />
+ <public name="system_accent3_400" />
+ <public name="system_accent3_500" />
+ <public name="system_accent3_600" />
+ <public name="system_accent3_700" />
+ <public name="system_accent3_800" />
+ <public name="system_accent3_900" />
+ <public name="system_accent3_1000" />
</public-group>
<public-group type="dimen" first-id="0x01050008">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7ea762c..e79fbb5 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,30 +5643,24 @@
<!-- Accessibility label for the work tab button. [CHAR LIMIT=NONE] -->
<string name="resolver_work_tab_accessibility">Work view</string>
- <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with work apps. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_work_apps">Can\u2019t share this with work apps</string>
+ <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this content across profiles. [CHAR LIMIT=NONE] -->
+ <string name="resolver_cross_profile_blocked">Blocked by your IT admin</string>
<!-- Error message. This text is explaining that the user's IT admin doesn't allow this specific content to be shared with apps in the work profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_work_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your work profile</string>
+ <string name="resolver_cant_share_with_work_apps_explanation">This content can\u2019t be shared with work apps</string>
- <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a work app. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_work_apps">Can\u2019t open this with work apps</string>
<!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their work profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_work_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your work profile</string>
+ <string name="resolver_cant_access_work_apps_explanation">This content can\u2019t be opened with work apps</string>
- <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with personal apps. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_personal_apps">Can\u2019t share this with personal apps</string>
<!-- Error message. This text is explaining that the user's IT admin doesn't allow them to share this specific content with apps in their personal profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_personal_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your personal profile</string>
+ <string name="resolver_cant_share_with_personal_apps_explanation">This content can\u2019t be shared with personal apps</string>
- <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a personal app. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_personal_apps">Can\u2019t open this with personal apps</string>
<!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their personal profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_personal_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your personal profile</string>
+ <string name="resolver_cant_access_personal_apps_explanation">This content can\u2019t be opened with personal apps</string>
<!-- Error message. This text lets the user know that they need to turn on work apps in order to share or open content. There's also a button a user can tap to turn on the apps. [CHAR LIMIT=NONE] -->
<string name="resolver_turn_on_work_apps">Work profile is paused</string>
<!-- Button text. This button turns on a user's work profile so they can access their work apps and data. [CHAR LIMIT=NONE] -->
- <string name="resolver_switch_on_work">Turn on</string>
+ <string name="resolver_switch_on_work">Tap to turn on</string>
<!-- Error message. This text lets the user know that their current work apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] -->
<string name="resolver_no_work_apps_available_share">No work apps can support this content</string>
@@ -5648,6 +5672,15 @@
<!-- 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] -->
<string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY">SIM network unlock PIN</string>
@@ -5893,9 +5926,9 @@
<!-- 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/styles.xml b/core/res/res/values/styles.xml
index c7ded0c..fbf67e0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1481,17 +1481,6 @@
<item name="android:windowExitAnimation">@anim/slide_out_down</item>
</style>
- <!-- The style for the container of media actions in a notification. -->
- <!-- @hide -->
- <style name="NotificationMediaActionContainer">
- <item name="layout_width">wrap_content</item>
- <item name="layout_height">wrap_content</item>
- <item name="layout_marginTop">-21dp</item>
- <item name="paddingStart">8dp</item>
- <item name="paddingBottom">@dimen/media_notification_actions_padding_bottom</item>
- <item name="gravity">top</item>
- </style>
-
<!-- The style for normal action button on notification -->
<style name="NotificationAction" parent="Widget.Material.Light.Button.Borderless.Small">
<item name="textColor">@color/notification_action_button_text_color</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1d74d85..b5f4764 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -200,12 +200,7 @@
<java-symbol type="id" name="action2" />
<java-symbol type="id" name="action3" />
<java-symbol type="id" name="action4" />
- <java-symbol type="id" name="notification_media_seekbar_container" />
<java-symbol type="id" name="notification_media_content" />
- <java-symbol type="id" name="notification_media_progress" />
- <java-symbol type="id" name="notification_media_progress_bar" />
- <java-symbol type="id" name="notification_media_elapsed_time" />
- <java-symbol type="id" name="notification_media_total_time" />
<java-symbol type="id" name="big_picture" />
<java-symbol type="id" name="big_text" />
<java-symbol type="id" name="chronometer" />
@@ -436,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" />
@@ -525,7 +521,6 @@
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
<java-symbol type="dimen" name="notification_top_pad_large_text_narrow" />
- <java-symbol type="dimen" name="notification_large_icon_circle_padding" />
<java-symbol type="dimen" name="notification_badge_size" />
<java-symbol type="dimen" name="immersive_mode_cling_width" />
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
@@ -560,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" />
@@ -1564,7 +1562,6 @@
<java-symbol type="layout" name="immersive_mode_cling" />
<java-symbol type="layout" name="user_switching_dialog" />
<java-symbol type="layout" name="common_tab_settings" />
- <java-symbol type="layout" name="notification_material_media_seekbar" />
<java-symbol type="layout" name="resolver_list_per_profile" />
<java-symbol type="layout" name="chooser_list_per_profile" />
<java-symbol type="layout" name="resolver_empty_states" />
@@ -2921,6 +2918,7 @@
<java-symbol type="drawable" name="ic_expand_bundle" />
<java-symbol type="drawable" name="ic_collapse_bundle" />
<java-symbol type="dimen" name="notification_header_shrink_min_width" />
+ <java-symbol type="dimen" name="notification_header_shrink_hide_width" />
<java-symbol type="dimen" name="notification_content_margin_start" />
<java-symbol type="dimen" name="notification_content_margin_end" />
<java-symbol type="dimen" name="notification_heading_margin_end" />
@@ -3010,7 +3008,6 @@
<java-symbol type="string" name="new_sms_notification_content" />
<java-symbol type="dimen" name="media_notification_expanded_image_margin_bottom" />
- <java-symbol type="dimen" name="notification_content_image_margin_end" />
<java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
@@ -3019,8 +3016,6 @@
<java-symbol type="id" name="aerr_wait" />
- <java-symbol type="id" name="notification_content_container" />
-
<java-symbol type="plurals" name="duration_minutes_shortest" />
<java-symbol type="plurals" name="duration_hours_shortest" />
<java-symbol type="plurals" name="duration_days_shortest" />
@@ -3138,7 +3133,6 @@
<java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
- <java-symbol type="dimen" name="notification_media_image_margin_end" />
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_action_disabled_alpha" />
<java-symbol type="id" name="tag_margin_end_when_icon_visible" />
@@ -3461,17 +3455,14 @@
<java-symbol type="dimen" name="notification_big_picture_max_height"/>
<java-symbol type="dimen" name="notification_big_picture_max_width"/>
- <java-symbol type="dimen" name="notification_media_image_max_width"/>
- <java-symbol type="dimen" name="notification_media_image_max_height"/>
<java-symbol type="dimen" name="notification_right_icon_size"/>
+ <java-symbol type="dimen" name="notification_right_icon_content_margin"/>
<java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
<java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
<java-symbol type="dimen" name="notification_big_picture_max_width_low_ram"/>
- <java-symbol type="dimen" name="notification_media_image_max_width_low_ram"/>
- <java-symbol type="dimen" name="notification_media_image_max_height_low_ram"/>
<java-symbol type="dimen" name="notification_right_icon_size_low_ram"/>
<java-symbol type="dimen" name="notification_grayscale_icon_max_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height_low_ram"/>
@@ -3545,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" />
@@ -3967,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" />
@@ -4103,13 +4096,10 @@
<java-symbol type="id" name="resolver_empty_state_container" />
<java-symbol type="id" name="button_bar_container" />
<java-symbol type="id" name="title_container" />
- <java-symbol type="string" name="resolver_cant_share_with_work_apps" />
+ <java-symbol type="string" name="resolver_cross_profile_blocked" />
<java-symbol type="string" name="resolver_cant_share_with_work_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_share_with_personal_apps" />
<java-symbol type="string" name="resolver_cant_share_with_personal_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_access_work_apps" />
<java-symbol type="string" name="resolver_cant_access_work_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_access_personal_apps" />
<java-symbol type="string" name="resolver_cant_access_personal_apps_explanation" />
<java-symbol type="string" name="resolver_turn_on_work_apps" />
<java-symbol type="string" name="resolver_no_work_apps_available_share" />
@@ -4232,6 +4222,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 16d720b..e40e31e 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -214,6 +214,9 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 @@
<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 6c28607..6a01abe 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 0c96411..baecc8c 100644
--- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -37,9 +37,6 @@
@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 @@
// 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 @@
@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 6849a90..f8c69ac 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 f31233b..408624a 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/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 252938a..0ea6364 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,8 +16,6 @@
package android.app;
-import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -99,23 +97,6 @@
}
@Test
- public void testColorSatisfiedWhenBgDarkTextDarker() {
- Notification.Builder builder = getMediaNotification();
- Notification n = builder.build();
-
- assertTrue(n.isColorized());
-
- // An initial guess where the foreground color is actually darker than an already dark bg
- int backgroundColor = 0xff585868;
- int initialForegroundColor = 0xff505868;
- builder.setColorPalette(backgroundColor, initialForegroundColor);
- int primaryTextColor = builder.getPrimaryTextColor(builder.mParams);
- assertTrue(satisfiesTextContrast(primaryTextColor, backgroundColor));
- int secondaryTextColor = builder.getSecondaryTextColor(builder.mParams);
- assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor));
- }
-
- @Test
public void testHasCompletedProgress_noProgress() {
Notification n = new Notification.Builder(mContext).build();
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index 7ef1d5e..56c685a 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 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 @@
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 7cb6804..36da927 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 @@
.setStatuses(statusList).setNotificationKey("key")
.setNotificationContent("content")
.setNotificationDataUri(Uri.parse("data"))
+ .setMessagesCount(2)
.setIntent(new Intent())
.build();
@@ -256,6 +257,7 @@
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 @@
}
@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/ComponentCallbacksControllerTest.java b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
new file mode 100644
index 0000000..09985a8
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ComponentCallbacksControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ComponentCallbacksControllerTest {
+ private ComponentCallbacksController mController;
+
+ @Before
+ public void setUp() {
+ mController = new ComponentCallbacksController();
+ }
+
+ @Test
+ public void testUnregisterCallbackWithoutRegistrationNoCrash() {
+ mController.unregisterCallbacks(new FakeComponentCallbacks());
+ }
+
+ @Test
+ public void testDispatchWithEmptyCallbacksNoCrash() {
+ mController.dispatchConfigurationChanged(new Configuration());
+ mController.dispatchLowMemory();
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+ }
+
+ @Test
+ public void testClearCallbacksNoCrash() {
+ mController.clearCallbacks();
+ }
+
+ @Test
+ public void testDispatchTrimMemoryWithoutComponentCallbacks2NoCrash() {
+ // Register a ComponentCallbacks instead of ComponentCallbacks2
+ mController.registerCallbacks(new FakeComponentCallbacks());
+
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+ }
+
+ @Test
+ public void testDispatchConfigurationChanged() throws Exception {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+ mController.dispatchConfigurationChanged(config);
+
+ assertThat(callback.mConfiguration).isEqualTo(config);
+
+ mController.dispatchConfigurationChanged(Configuration.EMPTY);
+
+ assertThat(callback.mConfiguration).isEqualTo(Configuration.EMPTY);
+ }
+
+ @Test
+ public void testDispatchLowMemory() {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ mController.dispatchLowMemory();
+
+ assertThat(callback.mLowMemoryCalled).isTrue();
+ }
+
+ @Test
+ public void testDispatchTrimMemory() {
+ final TestComponentCallbacks2 callback = new TestComponentCallbacks2();
+ mController.registerCallbacks(callback);
+
+ mController.dispatchTrimMemory(TRIM_MEMORY_BACKGROUND);
+
+ assertThat(callback.mLevel).isEqualTo(TRIM_MEMORY_BACKGROUND);
+ }
+
+ private static class FakeComponentCallbacks implements ComponentCallbacks {
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {}
+
+ @Override
+ public void onLowMemory() {}
+ }
+
+ private static class TestComponentCallbacks2 implements ComponentCallbacks2 {
+ private Configuration mConfiguration;
+ private boolean mLowMemoryCalled;
+ private int mLevel;
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mConfiguration = newConfig;
+ }
+
+ @Override
+ public void onLowMemory() {
+ mLowMemoryCalled = true;
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mLevel = level;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index c61a4b5..0b94589 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -2,3 +2,5 @@
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 = [email protected]
diff --git a/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java b/core/tests/coretests/src/android/content/pm/AppSearchShortcutInfoTest.java
index ffe93bc..21eb44a 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 @@
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 11239db..30b2d8e 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.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 d555cd9..009665f 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -18,13 +18,9 @@
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.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 @@
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 @@
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 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 @@
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 @@
.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);
- }
-
- @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());
- }
-
- @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);
-
- // 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());
- }
-
- @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());
+ VibrationEffect.Composed resolved = DEFAULT_ONE_SHOT.resolve(51);
+ assertEquals(0.2f, ((StepSegment) resolved.getSegments().get(0)).getAmplitude());
- // Ignores input when amplitude already set.
- VibrationEffect.OneShot resolved2 = resolved.resolve(10);
- assertSame(resolved, resolved2);
- assertEquals(239, resolved2.getAmplitude());
- }
-
- @Test
- public void testResolveOneshotFailsWhenMaxAmplitudeAboveThreshold() {
- try {
- TEST_ONE_SHOT.resolve(1000);
- fail("Max amplitude above threshold, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testResolveOneshotFailsWhenAmplitudeNonPositive() {
- try {
- TEST_ONE_SHOT.resolve(0);
- fail("Amplitude is set to zero, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @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());
+ assertThrows(IllegalArgumentException.class, () -> DEFAULT_ONE_SHOT.resolve(1000));
}
@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());
+ VibrationEffect.Composed resolved = TEST_WAVEFORM.resolve(102);
+ assertEquals(0.4f, ((StepSegment) resolved.getSegments().get(2)).getAmplitude());
- // Ignores input when amplitude already set.
- VibrationEffect.Waveform resolved2 = resolved.resolve(10);
- assertSame(resolved, resolved2);
- assertArrayEquals(new int[]{255, 0, 123}, resolved2.getAmplitudes());
+ assertThrows(IllegalArgumentException.class, () -> TEST_WAVEFORM.resolve(1000));
}
@Test
- public void testResolveWaveformFailsWhenMaxAmplitudeAboveThreshold() {
- try {
- TEST_WAVEFORM.resolve(1000);
- fail("Max amplitude above threshold, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- }
+ public void testResolvePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ assertEquals(effect, effect.resolve(51));
+ }
+
+ @Test
+ 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 testApplyEffectStrengthOneShot() {
+ VibrationEffect.Composed applied = DEFAULT_ONE_SHOT.applyEffectStrength(
+ VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(DEFAULT_ONE_SHOT, applied);
+ }
+
+ @Test
+ public void testApplyEffectStrengthWaveform() {
+ VibrationEffect.Composed applied = TEST_WAVEFORM.applyEffectStrength(
+ VibrationEffect.EFFECT_STRENGTH_LIGHT);
+ assertEquals(TEST_WAVEFORM, applied);
+ }
+
+ @Test
+ 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 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 testScaleOneShot() {
+ VibrationEffect.Composed scaledUp = TEST_ONE_SHOT.scale(1.5f);
+ assertTrue(100 / 255f < ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude());
+
+ VibrationEffect.Composed scaledDown = TEST_ONE_SHOT.scale(0.5f);
+ assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScaleWaveform() {
+ VibrationEffect.Composed scaledUp = TEST_WAVEFORM.scale(1.5f);
+ assertEquals(1f, ((StepSegment) scaledUp.getSegments().get(0)).getAmplitude(), 1e-5f);
+
+ VibrationEffect.Composed scaledDown = TEST_WAVEFORM.scale(0.5f);
+ assertTrue(1f > ((StepSegment) scaledDown.getSegments().get(0)).getAmplitude());
+ }
+
+ @Test
+ public void testScalePrebaked() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ VibrationEffect.Composed scaledUp = effect.scale(1.5f);
+ assertEquals(effect, scaledUp);
+
+ VibrationEffect.Composed scaledDown = effect.scale(0.5f);
+ assertEquals(effect, scaledDown);
}
@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);
- }
-
- @Test
- public void testResolveComposed_ignoresDefaultAmplitudeAndReturnsSameEffect() {
- VibrationEffect initial = TEST_COMPOSED;
- assertSame(initial, initial.resolve(1000));
- }
-
- @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 effect =
(VibrationEffect.Composed) VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK,
- TEST_AMPLITUDE / 255f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+ .addEffect(TEST_ONE_SHOT)
.compose();
- 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 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 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();
-
- // 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);
- }
-
- @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());
- }
-
- @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());
- }
-
- @Test
- public void getFallbackEffect_withoutEffectSet_isNull() {
- VibrationEffect.Prebaked effect = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK,
- true, VibrationEffect.EFFECT_STRENGTH_LIGHT);
- Assert.assertNull(effect.getFallbackEffect());
- }
-
- @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 @@
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 09c36dd..40fc00a 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -23,6 +23,7 @@
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 @@
@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 @@
}
@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 @@
.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 @@
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 @@
.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 @@
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 @@
return this;
}
- public InfoBuilder setResonantFrequency(float resonantFrequency) {
- mResonantFrequency = resonantFrequency;
- return this;
- }
-
public InfoBuilder setQFactor(float qFactor) {
mQFactor = qFactor;
return this;
}
+ 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 0000000..de80812
--- /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 0000000..538655b
--- /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 0000000..174b4a7
--- /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 0000000..79529b8
--- /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/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
new file mode 100644
index 0000000..2dd3f69
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.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.util;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Internal tests for {@link SparseDoubleArray}.
+ *
+ * Run using:
+ * atest FrameworksCoreTests:android.util.SparseDoubleArrayTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseDoubleArrayTest {
+ private static final double EXACT_PRECISION = 0;
+ private static final double PRECISION = 0.000000001;
+
+ @Test
+ public void testPutGet() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+ assertEquals("Array should be empty", 0, sda.size());
+
+ final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+ final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+ Double.NaN,
+ 4311236312.0 / 3431470161413514334123.0,
+ 431123636434132151313412.0 / 34323.0,
+ 0};
+ for (int i = 0; i < keys.length; i++) {
+ sda.put(keys[i], values[i]);
+ }
+
+ assertEquals("Wrong size array", keys.length, sda.size());
+ // Due to the implementation, we actually expect EXACT double equality.
+ for (int i = 0; i < keys.length; i++) {
+ assertEquals("Wrong value at index " + i, values[i], sda.get(keys[i]), EXACT_PRECISION);
+ }
+
+ // Now check something that was never put in
+ assertEquals("Wrong value for absent index", 0, sda.get(100000), EXACT_PRECISION);
+ }
+
+ @Test
+ public void testAdd() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+
+ sda.put(4, 6.1);
+ sda.add(4, -1.2);
+ sda.add(2, -1.2);
+
+ assertEquals(6.1 - 1.2, sda.get(4), PRECISION);
+ assertEquals(-1.2, sda.get(2), PRECISION);
+ }
+
+ @Test
+ public void testKeyValueAt() {
+ final SparseDoubleArray sda = new SparseDoubleArray();
+
+ final int[] keys = {1, 6, -14, 53251, 5, -13412, 12, 0, 2};
+ final double[] values = {7, -12.4, 7, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+ Double.NaN,
+ 4311236312.0 / 3431470161413514334123.0,
+ 431123636434132151313412.0 / 34323.0,
+ 0};
+ for (int i = 0; i < keys.length; i++) {
+ sda.put(keys[i], values[i]);
+ }
+
+ // Sort the sample data.
+ final ArrayMap<Integer, Double> map = new ArrayMap<>(keys.length);
+ for (int i = 0; i < keys.length; i++) {
+ map.put(keys[i], values[i]);
+ }
+ final int[] sortedKeys = Arrays.copyOf(keys, keys.length);
+ Arrays.sort(sortedKeys);
+
+ for (int i = 0; i < sortedKeys.length; i++) {
+ final int expectedKey = sortedKeys[i];
+ final double expectedValue = map.get(expectedKey);
+
+ assertEquals("Wrong key at index " + i, expectedKey, sda.keyAt(i), PRECISION);
+ assertEquals("Wrong value at index " + i, expectedValue, sda.valueAt(i), PRECISION);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 5de55d7..47556c3 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -98,12 +98,12 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// test if setVisibility can show IME
mImeConsumer.onWindowFocusGained();
- mImeConsumer.applyImeVisibility(true);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test if setVisibility can hide IME
- mImeConsumer.applyImeVisibility(false);
+ mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
@@ -117,7 +117,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained();
- mImeConsumer.applyImeVisibility(true /* setVisible */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */);
// set control and verify visibility is applied.
InsetsSourceControl control =
@@ -136,7 +136,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// Request IME visible before control is available.
mImeConsumer.onWindowFocusGained();
- mImeConsumer.applyImeVisibility(true /* setVisible */);
+ mController.show(WindowInsets.Type.ime(), true /* fromIme */);
// set control and verify visibility is applied.
InsetsSourceControl control = Mockito.spy(
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ff505c4..4390546 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -236,7 +236,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
// since there is no focused view, forcefully make IME visible.
- mController.applyImeVisibility(true /* setVisible */);
+ mController.show(Type.ime(), true /* fromIme */);
mController.show(Type.all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
@@ -244,7 +244,7 @@
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.applyImeVisibility(false /* setVisible */);
+ mController.hide(Type.ime(), true /* fromIme */);
mController.hide(Type.all());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
@@ -261,10 +261,10 @@
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
- mController.applyImeVisibility(true);
+ mController.show(Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.applyImeVisibility(false);
+ mController.hide(Type.ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index f3a6f9e..22c71b52 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.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 @@
* 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 cc229e1..dc43204 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.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.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 41cd4c5..2833ea3 100644
--- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
@@ -49,7 +49,6 @@
*/
@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 96df9dd..39ea8af 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -73,17 +73,17 @@
// Check get metrics do not crash.
WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics();
WindowMetrics maxMetrics = mWm.getMaximumWindowMetrics();
- verifyMetricsSanity(currentMetrics, maxMetrics);
+ verifyMetricsValidity(currentMetrics, maxMetrics);
mWm.removeViewImmediate(view);
// Check get metrics do not crash.
currentMetrics = mWm.getCurrentWindowMetrics();
maxMetrics = mWm.getMaximumWindowMetrics();
- verifyMetricsSanity(currentMetrics, maxMetrics);
+ verifyMetricsValidity(currentMetrics, maxMetrics);
}, 0);
}
- private static void verifyMetricsSanity(WindowMetrics currentMetrics,
+ private static void verifyMetricsValidity(WindowMetrics currentMetrics,
WindowMetrics maxMetrics) {
Rect currentBounds = currentMetrics.getBounds();
Rect maxBounds = maxMetrics.getBounds();
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
new file mode 100644
index 0000000..e4fc19c
--- /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
similarity index 97%
rename from core/tests/coretests/src/android/app/WindowContextTest.java
rename to core/tests/coretests/src/android/window/WindowContextTest.java
index 48b58c6..614e7c1 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.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 @@
* {@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 d9012f64..80d47a9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -154,8 +154,8 @@
sOverrides.reset();
sOverrides.createPackageManager = mPackageManagerOverride;
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- Boolean.toString(false),
+ SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+ Boolean.toString(true),
true /* makeDefault*/);
}
@@ -1017,7 +1017,7 @@
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
- is(SHORTCUT_TARGET_SCORE_BOOST));
+ is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
}
@@ -1262,6 +1262,126 @@
.getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(0));
}
+ @Test
+ public void testShortcutTargetWithApplyAppLimits() throws InterruptedException {
+ // Set up resources
+ sOverrides.resources = Mockito.spy(
+ InstrumentationRegistry.getInstrumentation().getContext().getResources());
+ when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
+ .thenReturn(1);
+ Intent sendIntent = createSendTextIntent();
+ // We need app targets for direct targets to get displayed
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ // Create direct share target
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // Start activity
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+
+ // Insert the direct share target
+ Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+ List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
+ directShareToShortcutInfos.put(serviceTargets.get(0),
+ shortcutInfos.get(0).getShortcutInfo());
+ directShareToShortcutInfos.put(serviceTargets.get(1),
+ shortcutInfos.get(1).getShortcutInfo());
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activity.getAdapter().addServiceResults(
+ activity.createTestDisplayResolveInfo(sendIntent,
+ ri,
+ "testLabel",
+ "testInfo",
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
+ serviceTargets,
+ TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
+ directShareToShortcutInfos,
+ List.of())
+ );
+ // Thread.sleep shouldn't be a thing in an integration test but it's
+ // necessary here because of the way the code is structured
+ // TODO: restructure the tests b/129870719
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
+ activity.getAdapter().getCount(), is(3));
+ assertThat("Chooser should have exactly one selectable direct target",
+ activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+ assertThat("The resolver info must match the resolver info used to create the target",
+ activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+ assertThat("The display label must match",
+ activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
+ }
+
+ @Test
+ public void testShortcutTargetWithoutApplyAppLimits() throws InterruptedException {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+ Boolean.toString(false),
+ true /* makeDefault*/);
+ // Set up resources
+ sOverrides.resources = Mockito.spy(
+ InstrumentationRegistry.getInstrumentation().getContext().getResources());
+ when(sOverrides.resources.getInteger(R.integer.config_maxShortcutTargetsPerApp))
+ .thenReturn(1);
+ Intent sendIntent = createSendTextIntent();
+ // We need app targets for direct targets to get displayed
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ // Create direct share target
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // Start activity
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+
+ // Insert the direct share target
+ Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+ List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
+ directShareToShortcutInfos.put(serviceTargets.get(0),
+ shortcutInfos.get(0).getShortcutInfo());
+ directShareToShortcutInfos.put(serviceTargets.get(1),
+ shortcutInfos.get(1).getShortcutInfo());
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activity.getAdapter().addServiceResults(
+ activity.createTestDisplayResolveInfo(sendIntent,
+ ri,
+ "testLabel",
+ "testInfo",
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
+ serviceTargets,
+ TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
+ directShareToShortcutInfos,
+ List.of())
+ );
+ // Thread.sleep shouldn't be a thing in an integration test but it's
+ // necessary here because of the way the code is structured
+ // TODO: restructure the tests b/129870719
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ assertThat("Chooser should have 4 targets (2 apps, 2 direct)",
+ activity.getAdapter().getCount(), is(4));
+ assertThat("Chooser should have exactly two selectable direct target",
+ activity.getAdapter().getSelectableServiceTargetCount(), is(2));
+ assertThat("The resolver info must match the resolver info used to create the target",
+ activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+ assertThat("The display label must match",
+ activity.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
+ assertThat("The display label must match",
+ activity.getAdapter().getItem(1).getDisplayLabel(), is("testTitle1"));
+ }
+
// This test is too long and too slow and should not be taken as an example for future tests.
@Test
public void testDirectTargetLoggingWithAppTargetNotRankedPortrait()
@@ -1488,14 +1608,13 @@
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 =
@@ -1507,6 +1626,7 @@
Intent sendIntent = createSendTextIntent();
sendIntent.setType("TestType");
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
final ChooserWrapperActivity activity =
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
@@ -1566,7 +1686,7 @@
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()));
}
@@ -1996,7 +2116,7 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 7dc5a8b..68287ca 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 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@@ -726,7 +726,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 0f59143..d36f06a 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 @@
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 33b8aed..fc3be13 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 @@
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 @@
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 bd7f1e2..eed61cb 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 @@
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 @@
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 @@
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 @@
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 66a8379..ae59a54 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 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 @@
.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,7 +92,59 @@
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(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
+
+ SystemBatteryConsumer consumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.44440);
+
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.8);
+ }
+
+ @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);
@@ -99,11 +155,13 @@
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(1.44440);
+ .isWithin(PRECISION).of(2.77777);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(0.8);
+ .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 0898fae..b3b34ef 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 98f7177..42d9407 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/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml
index ac16af3..abde232 100644
--- a/data/etc/car/com.android.car.carlauncher.xml
+++ b/data/etc/car/com.android.car.carlauncher.xml
@@ -18,6 +18,7 @@
<privapp-permissions package="com.android.car.carlauncher">
<permission name="android.permission.ACTIVITY_EMBEDDING"/>
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 27bf4ef..3213390 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/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 3aaf11c..4534d36 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -28,7 +28,7 @@
public long mNativeObject; // BLASTBufferQueue*
private static native long nativeCreate(String name, long surfaceControl, long width,
- long height, int format, boolean tripleBufferingEnabled);
+ long height, int format);
private static native void nativeDestroy(long ptr);
private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
private static native void nativeSetNextTransaction(long ptr, long transactionPtr);
@@ -53,9 +53,8 @@
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
- @PixelFormat.Format int format, boolean tripleBufferingEnabled) {
- mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format,
- tripleBufferingEnabled);
+ @PixelFormat.Format int format) {
+ mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
}
public void destroy() {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 2a6bbf3..c48fd8b 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -34,9 +34,11 @@
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.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 @@
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 @@
/** @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 24d73ef..3616a4d 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -276,7 +276,11 @@
*/
@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 5dd250c..98b9584 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -60,17 +60,13 @@
+ " 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 @@
+ " 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 @@
}
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 35b1c16..72cea0c 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -139,4 +139,18 @@
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 0000000..a1a7aa8
--- /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/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/keystore/java/android/security/GenerateRkpKeyException.java
similarity index 62%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to keystore/java/android/security/GenerateRkpKeyException.java
index cab5ad2..a2d65e4 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/keystore/java/android/security/GenerateRkpKeyException.java
@@ -14,17 +14,18 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package android.security;
-import android.annotation.NonNull;
+/**
+ * Thrown on problems in attempting to attest to a key using a remotely provisioned key.
+ *
+ * @hide
+ */
+public class GenerateRkpKeyException extends Exception {
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
+ /**
+ * 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 0000000..5f1d669
--- /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 a08f390..b05149e 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -1204,6 +1204,7 @@
* 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 6ac3821d..75e248e 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -18,8 +18,7 @@
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 @@
* 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 cd77d9c..ec3b102 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -254,6 +254,8 @@
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 5cb2c3b..9ca551b 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -288,7 +288,7 @@
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 KeyGenParameterSpec(
String keyStoreAlias,
- int namespace,
+ @KeyProperties.Namespace int namespace,
int keySize,
AlgorithmParameterSpec spec,
X500Principal certificateSubject,
@@ -472,7 +472,7 @@
* @hide
*/
@SystemApi
- public int getNamespace() {
+ public @KeyProperties.Namespace int getNamespace() {
return mNamespace;
}
@@ -896,7 +896,7 @@
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 @@
*/
@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 7b0fa91..682d12a 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -892,6 +892,22 @@
}
/**
+ * 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 @@
* 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 @@
* 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 e401add..2d8901a 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.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 @@
@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 @@
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 0c6744f..25b1c86 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 @@
*/
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 @@
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 39607ae..32f98a2 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -100,7 +100,7 @@
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 @@
@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/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index d2b3cf6..6bd0e0a 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.wm.shell">
+ <!-- System permission required by WM Shell Task Organizer. -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
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 3708e15..34c66a4 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 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 @@
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 @@
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/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 4768cf5..fa94ec5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -126,7 +126,7 @@
*/
public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
return Settings.Secure.getInt(resolver,
- Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1;
+ Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0 /* Default OFF */) == 1;
}
void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
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 3f46fee..28c8f53 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
@@ -16,6 +16,8 @@
package com.android.wm.shell.startingsurface;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.app.ActivityThread;
@@ -31,6 +33,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
+import android.os.Trace;
import android.util.Slog;
import android.view.SurfaceControl;
import android.window.SplashScreenView;
@@ -70,6 +73,7 @@
private int mIconNormalExitDistance;
private int mIconEarlyExitDistance;
private final TransactionPool mTransactionPool;
+ private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
@@ -109,49 +113,82 @@
return new ColorDrawable(getSystemBGColor());
}
- SplashScreenView makeSplashScreenContentView(Context context, int iconRes,
- int splashscreenContentResId) {
- updateDensity();
- // splash screen content will be deprecated after S.
- final SplashScreenView ssc =
- makeSplashscreenContentDrawable(context, splashscreenContentResId);
-
- if (ssc != null) {
- return ssc;
- }
-
- final SplashScreenWindowAttrs attrs =
- SplashScreenWindowAttrs.createWindowAttrs(context);
- final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
+ private @ColorInt int peekWindowBGColor(Context context) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
final Drawable themeBGDrawable;
-
- if (attrs.mWindowBgColor != 0) {
- themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
- } else if (attrs.mWindowBgResId != 0) {
- themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+ if (mTmpAttrs.mWindowBgColor != 0) {
+ themeBGDrawable = new ColorDrawable(mTmpAttrs.mWindowBgColor);
+ } 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);
+ return estimatedWindowBGColor;
+ }
+
+ private int estimateWindowBGColor(Drawable themeBGDrawable) {
+ final DrawableColorTester themeBGTester =
+ new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
+ if (themeBGTester.nonTransparentRatio() == 0) {
+ // the window background is transparent, unable to draw
+ Slog.w(TAG, "Window background is transparent, fill background with black color");
+ return getSystemBGColor();
+ } else {
+ return themeBGTester.getDominateColor();
+ }
+ }
+
+ SplashScreenView makeSplashScreenContentView(Context context, int iconRes) {
+ updateDensity();
+
+ getWindowAttrs(context, mTmpAttrs);
+ final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
final int animationDuration;
final Drawable iconDrawable;
- if (attrs.mReplaceIcon != null) {
- iconDrawable = attrs.mReplaceIcon;
+ if (mTmpAttrs.mReplaceIcon != null) {
+ iconDrawable = mTmpAttrs.mReplaceIcon;
animationDuration = Math.max(0,
- Math.min(attrs.mAnimationDuration, mMaxAnimatableIconDuration));
+ Math.min(mTmpAttrs.mAnimationDuration, mMaxAnimatableIconDuration));
} else {
iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
: context.getPackageManager().getDefaultActivityIcon();
animationDuration = 0;
}
+ final int themeBGColor = peekWindowBGColor(context);
// TODO (b/173975965) Tracking the performance on improved splash screen.
return builder
.setContext(context)
- .setThemeDrawable(themeBGDrawable)
+ .setWindowBGColor(themeBGColor)
.setIconDrawable(iconDrawable)
.setIconAnimationDuration(animationDuration)
- .setBrandingDrawable(attrs.mBrandingImage)
- .setIconBackground(attrs.mIconBgColor).build();
+ .setBrandingDrawable(mTmpAttrs.mBrandingImage)
+ .setIconBackground(mTmpAttrs.mIconBgColor).build();
+ }
+
+ private static void getWindowAttrs(Context context, SplashScreenWindowAttrs attrs) {
+ final TypedArray typedArray = context.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ attrs.mWindowBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+ attrs.mReplaceIcon = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenAnimatedIcon);
+ attrs.mAnimationDuration = typedArray.getInt(
+ R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+ attrs.mBrandingImage = typedArray.getDrawable(
+ R.styleable.Window_windowSplashScreenBrandingImage);
+ attrs.mIconBgColor = typedArray.getColor(
+ R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
+ typedArray.recycle();
+ if (DEBUG) {
+ Slog.d(TAG, "window attributes color: "
+ + Integer.toHexString(attrs.mWindowBgColor)
+ + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+ + " brandImage " + attrs.mBrandingImage);
+ }
}
static class SplashScreenWindowAttrs {
@@ -161,35 +198,9 @@
private Drawable mBrandingImage = null;
private int mIconBgColor = Color.TRANSPARENT;
private int mAnimationDuration = 0;
-
- static SplashScreenWindowAttrs createWindowAttrs(Context context) {
- final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
- final TypedArray typedArray = context.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- attrs.mWindowBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
- attrs.mReplaceIcon = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenAnimatedIcon);
- attrs.mAnimationDuration = typedArray.getInt(
- R.styleable.Window_windowSplashScreenAnimationDuration, 0);
- attrs.mBrandingImage = typedArray.getDrawable(
- R.styleable.Window_windowSplashScreenBrandingImage);
- attrs.mIconBgColor = typedArray.getColor(
- R.styleable.Window_windowSplashScreenIconBackgroundColor, Color.TRANSPARENT);
- typedArray.recycle();
- if (DEBUG) {
- Slog.d(TAG, "window attributes color: "
- + Integer.toHexString(attrs.mWindowBgColor)
- + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
- + " brandImage " + attrs.mBrandingImage);
- }
- return attrs;
- }
}
private class StartingWindowViewBuilder {
- private Drawable mThemeBGDrawable;
private Drawable mIconDrawable;
private int mIconAnimationDuration;
private Context mContext;
@@ -203,8 +214,8 @@
private Drawable mFinalIconDrawable;
private float mScale = 1f;
- StartingWindowViewBuilder setThemeDrawable(Drawable background) {
- mThemeBGDrawable = background;
+ StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+ mThemeColor = background;
mBuildComplete = false;
return this;
}
@@ -247,18 +258,14 @@
Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
return null;
}
- if (mThemeBGDrawable == null) {
- Slog.w(TAG, "Theme Background Drawable is null, forget to set Theme Drawable?");
- mThemeBGDrawable = createDefaultBackgroundDrawable();
- }
- processThemeColor();
+
if (!processAdaptiveIcon() && mIconDrawable != null) {
if (DEBUG) {
Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
}
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
mIconBackground != Color.TRANSPARENT
- ? mIconBackground : mThemeColor, mIconDrawable);
+ ? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
}
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
@@ -266,22 +273,10 @@
return mCachedResult;
}
- private void createIconDrawable(Drawable iconDrawable) {
+ private void createIconDrawable(Drawable iconDrawable, int iconSize) {
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
mIconBackground != Color.TRANSPARENT
- ? mIconBackground : mThemeColor, iconDrawable);
- }
-
- private void processThemeColor() {
- final DrawableColorTester themeBGTester =
- new DrawableColorTester(mThemeBGDrawable, true /* filterTransparent */);
- if (themeBGTester.nonTransparentRatio() == 0) {
- // the window background is transparent, unable to draw
- Slog.w(TAG, "Window background is transparent, fill background with black color");
- mThemeColor = getSystemBGColor();
- } else {
- mThemeColor = themeBGTester.getDominateColor();
- }
+ ? mIconBackground : mThemeColor, iconDrawable, iconSize);
}
private boolean processAdaptiveIcon() {
@@ -289,6 +284,7 @@
return false;
}
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "processAdaptiveIcon");
final AdaptiveIconDrawable adaptiveIconDrawable = (AdaptiveIconDrawable) mIconDrawable;
final DrawableColorTester backIconTester =
new DrawableColorTester(adaptiveIconDrawable.getBackground());
@@ -325,29 +321,28 @@
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: choose fg icon");
}
- // Using AdaptiveIconDrawable here can help keep the shape consistent with the
- // current settings.
- createIconDrawable(iconForeground);
// Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
// should enlarge the size 108/72 if we only draw adaptiveIcon's foreground.
if (foreIconTester.nonTransparentRatio() < ENLARGE_FOREGROUND_ICON_THRESHOLD) {
mScale = 1.5f;
}
+ // Using AdaptiveIconDrawable here can help keep the shape consistent with the
+ // current settings.
+ final int iconSize = (int) (0.5f + mIconSize * mScale);
+ createIconDrawable(iconForeground, iconSize);
} else {
if (DEBUG) {
Slog.d(TAG, "makeSplashScreenContentView: draw whole icon");
}
- if (mIconBackground != Color.TRANSPARENT) {
- createIconDrawable(adaptiveIconDrawable);
- } else {
- mFinalIconDrawable = adaptiveIconDrawable;
- }
+ createIconDrawable(adaptiveIconDrawable, mIconSize);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return true;
}
private SplashScreenView fillViewWithIcon(Context context,
int iconSize, Drawable iconDrawable) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
builder.setIconSize(iconSize).setBackgroundColor(mThemeColor)
.setIconBackground(mIconBackground);
@@ -364,6 +359,7 @@
Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
}
splashScreenView.makeSystemUIColorsTransparent();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return splashScreenView;
}
}
@@ -395,7 +391,7 @@
return root < 0.1;
}
- private static SplashScreenView makeSplashscreenContentDrawable(Context ctx,
+ static SplashScreenView makeSplashscreenContent(Context ctx,
int splashscreenContentResId) {
// doesn't support windowSplashscreenContent after S
// TODO add an allowlist to skip some packages if needed
@@ -525,6 +521,7 @@
new TransparentFilterQuantizer();
ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
final Rect initialBounds = drawable.copyBounds();
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
@@ -559,6 +556,7 @@
}
mPalette = builder.generate();
bitmap.recycle();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 8626dbc..a4a83eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -16,11 +16,15 @@
package com.android.wm.shell.startingsurface;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
@@ -28,11 +32,13 @@
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.PathParser;
import android.window.SplashScreenView;
@@ -47,12 +53,57 @@
public class SplashscreenIconDrawableFactory {
static Drawable makeIconDrawable(@ColorInt int backgroundColor,
- @NonNull Drawable foregroundDrawable) {
+ @NonNull Drawable foregroundDrawable, int iconSize) {
if (foregroundDrawable instanceof Animatable) {
return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
+ } else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
+ return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
} else {
- // TODO make a light weight drawable instead of AdaptiveIconDrawable
- return new AdaptiveIconDrawable(new ColorDrawable(backgroundColor), foregroundDrawable);
+ return new ImmobileIconDrawable(new AdaptiveIconDrawable(
+ new ColorDrawable(backgroundColor), foregroundDrawable), iconSize);
+ }
+ }
+
+ private static class ImmobileIconDrawable extends Drawable {
+ private Shader mLayersShader;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
+ | Paint.FILTER_BITMAP_FLAG);
+
+ ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) {
+ cachePaint(drawable, iconSize, iconSize);
+ }
+
+ private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
+ final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(layersBitmap);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(canvas);
+ mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP);
+ mPaint.setShader(mLayersShader);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+ canvas.drawRect(bounds, mPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 1;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index b592121..b500813 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -181,11 +181,7 @@
}
int windowFlags = 0;
- final boolean enableHardAccelerated =
- (activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0;
- if (enableHardAccelerated) {
- windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- }
+ windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
final boolean[] showWallpaper = new boolean[1];
final int[] splashscreenContentResId = new int[1];
@@ -246,8 +242,6 @@
params.packageName = activityInfo.packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- params.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
@@ -267,21 +261,31 @@
final int taskId = taskInfo.taskId;
SplashScreenView sView = null;
try {
- sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes,
- splashscreenContentResId[0]);
final View view = win.getDecorView();
final WindowManager wm = mContext.getSystemService(WindowManager.class);
- if (postAddWindow(taskId, appToken, view, wm, params)) {
+ // splash screen content will be deprecated after S.
+ sView = SplashscreenContentDrawer.makeSplashscreenContent(
+ context, splashscreenContentResId[0]);
+ final boolean splashscreenContentCompatible = sView != null;
+ if (splashscreenContentCompatible) {
+ win.setContentView(sView);
+ } else {
+ sView = mSplashscreenContentDrawer.makeSplashScreenContentView(context, iconRes);
win.setContentView(sView);
sView.cacheRootWindow(win);
}
+ postAddWindow(taskId, appToken, view, wm, params);
} catch (RuntimeException e) {
// don't crash if something else bad happens, for example a
// failure loading resources because we are loading from an app
// on external storage that has been unmounted.
- Slog.w(TAG, " failed creating starting window", e);
+ Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e);
+ sView = null;
} finally {
- setSplashScreenRecord(taskId, sView);
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null) {
+ record.setSplashScreenView(sView);
+ }
}
}
@@ -328,7 +332,7 @@
ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
}
- protected boolean postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
+ protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm,
WindowManager.LayoutParams params) {
boolean shouldSaveView = true;
try {
@@ -349,7 +353,6 @@
removeWindowNoAnimate(taskId);
saveSplashScreenRecord(taskId, view);
}
- return shouldSaveView;
}
private void saveSplashScreenRecord(int taskId, View view) {
@@ -358,13 +361,6 @@
mStartingWindowRecords.put(taskId, tView);
}
- private void setSplashScreenRecord(int taskId, SplashScreenView splashScreenView) {
- final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
- if (record != null) {
- record.setSplashScreenView(splashScreenView);
- }
- }
-
private void removeWindowNoAnimate(int taskId) {
removeWindowSynced(taskId, null, null, false);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 207db9e..8ae0a73 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -83,12 +83,11 @@
}
@Override
- protected boolean postAddWindow(int taskId, IBinder appToken,
+ protected void postAddWindow(int taskId, IBinder appToken,
View view, WindowManager wm, WindowManager.LayoutParams params) {
// listen for addView
mAddWindowForTask = taskId;
mViewThemeResId = view.getContext().getThemeResId();
- return true;
}
@Override
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index b71bb07..1455269 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -120,12 +120,6 @@
int imgHeight = image->height();
sk_sp<GrDirectContext> grContext = sk_ref_sp(mRenderThread.getGrContext());
- if (bitmap->colorType() == kRGBA_F16_SkColorType &&
- !grContext->colorTypeSupportedAsSurface(bitmap->colorType())) {
- ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
- return CopyResult::DestinationInvalid;
- }
-
CopyResult copyResult = CopyResult::UnknownError;
int displayedWidth = imgWidth, displayedHeight = imgHeight;
@@ -159,12 +153,10 @@
bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect,
SkBitmap* bitmap) {
- /* This intermediate surface is present to work around a bug in SwiftShader that
- * prevents us from reading the contents of the layer's texture directly. The
- * workaround involves first rendering that texture into an intermediate buffer and
- * then reading from the intermediate buffer into the bitmap.
- * Another reason to render in an offscreen buffer is to scale and to avoid an issue b/62262733
- * with reading incorrect data from EGLImage backed SkImage (likely a driver bug).
+ /* This intermediate surface is present to work around limitations that LayerDrawable expects
+ * to render into a GPU backed canvas. Additionally, the offscreen buffer solution works around
+ * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
+ * software buffer.
*/
sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
SkBudgeted::kYes, bitmap->info(), 0,
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index ade63e5..5d9fad5 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -45,7 +45,8 @@
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 @@
mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() }
: mDecodeSize;
this->rewind();
+ mOptions.fZeroInitialized = zeroInit;
}
ImageDecoder::~ImageDecoder() = default;
@@ -446,10 +448,17 @@
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 cbfffd5..cef2233 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -34,8 +34,8 @@
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 ad7741b..f7b8c01 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -141,7 +141,8 @@
}
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 a5e2815..fdf0f59 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -61,6 +61,8 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
}
/**
+ * 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 5e2e559..5f8d795 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -1246,20 +1246,42 @@
* 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/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
index 50ed046..092ec67f 100644
--- a/location/java/android/location/provider/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -24,7 +24,7 @@
* @hide
*/
interface ILocationProviderManager {
- void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String packageName, @nullable String attributionTag);
+ void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String attributionTag);
void onSetAllowed(boolean allowed);
void onSetProperties(in ProviderProperties properties);
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index ae6395d..eada22c 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
"com.android.location.service.FusedLocationProvider";
private final String mTag;
- private final @Nullable String mPackageName;
private final @Nullable String mAttributionTag;
private final IBinder mBinder;
@@ -108,7 +107,6 @@
public LocationProviderBase(@NonNull Context context, @NonNull String tag,
@NonNull ProviderProperties properties) {
mTag = tag;
- mPackageName = context.getPackageName();
mAttributionTag = context.getAttributionTag();
mBinder = new Service();
@@ -305,7 +303,7 @@
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
- manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+ manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index 85a083e..ade0ea4 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -55,6 +55,20 @@
}
/**
+ * Returns a CallerIdentity with PID and listener ID information stripped. This is mostly
+ * useful for aggregating information for debug purposes, and should not be used in any API with
+ * security requirements.
+ */
+ public static CallerIdentity forAggregation(CallerIdentity callerIdentity) {
+ if (callerIdentity.getPid() == 0 && callerIdentity.getListenerId() == null) {
+ return callerIdentity;
+ }
+
+ return new CallerIdentity(callerIdentity.getUid(), 0, callerIdentity.getPackageName(),
+ callerIdentity.getAttributionTag(), null);
+ }
+
+ /**
* Creates a CallerIdentity for the current process and context.
*/
public static CallerIdentity fromContext(Context context) {
@@ -180,17 +194,6 @@
}
}
- /**
- * Returns a CallerIdentity corrosponding to this CallerIdentity but with a null listener id.
- */
- public CallerIdentity stripListenerId() {
- if (mListenerId == null) {
- return this;
- } else {
- return new CallerIdentity(mUid, mPid, mPackageName, mAttributionTag, null);
- }
- }
-
@Override
public String toString() {
int length = 10 + mPackageName.length();
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 7f1cf6d..95f6c2f 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -96,7 +96,6 @@
public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
final String mTag;
- @Nullable final String mPackageName;
@Nullable final String mAttributionTag;
final IBinder mBinder;
@@ -133,7 +132,6 @@
public LocationProviderBase(Context context, String tag,
ProviderPropertiesUnbundled properties) {
mTag = tag;
- mPackageName = context != null ? context.getPackageName() : null;
mAttributionTag = context != null ? context.getAttributionTag() : null;
mBinder = new Service();
@@ -370,7 +368,7 @@
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
- manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
+ manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f957a73..9aeef07 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -568,6 +568,42 @@
public static final int FLAG_FROM_KEY = 1 << 12;
/** @hide */
+ @IntDef(prefix = {"ENCODED_SURROUND_OUTPUT_"}, value = {
+ ENCODED_SURROUND_OUTPUT_AUTO,
+ ENCODED_SURROUND_OUTPUT_NEVER,
+ ENCODED_SURROUND_OUTPUT_ALWAYS,
+ ENCODED_SURROUND_OUTPUT_MANUAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncodedSurroundOutputMode {}
+
+ /**
+ * The surround sound formats are available for use if they are detected. This is the default
+ * mode.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0;
+
+ /**
+ * The surround sound formats are NEVER available, even if they are detected by the hardware.
+ * Those formats will not be reported.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1;
+
+ /**
+ * The surround sound formats are ALWAYS available, even if they are not detected by the
+ * hardware. Those formats will be reported as part of the HDMI output capability.
+ * Applications are then free to use either PCM or encoded output.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2;
+
+ /**
+ * Surround sound formats are available according to the choice of user, even if they are not
+ * detected by the hardware. Those formats will be reported as part of the HDMI output
+ * capability. Applications are then free to use either PCM or encoded output.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3;
+
+ /** @hide */
@IntDef(flag = true, prefix = "FLAG", value = {
FLAG_SHOW_UI,
FLAG_ALLOW_RINGER_MODES,
@@ -6754,6 +6790,34 @@
}
/**
+ * Sets the surround sound mode.
+ *
+ * @return true if successful, otherwise false
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public boolean setEncodedSurroundMode(@EncodedSurroundOutputMode int mode) {
+ try {
+ return getService().setEncodedSurroundMode(mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the surround sound mode.
+ *
+ * @return true if successful, otherwise false
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public @EncodedSurroundOutputMode int getEncodedSurroundMode() {
+ try {
+ return getService().getEncodedSurroundMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Returns all surround formats.
* @return a map where the key is a surround format and
@@ -6771,7 +6835,6 @@
}
/**
- * @hide
* Set a certain surround format as enabled or not.
* @param audioFormat a surround format, the value is one of
* {@link AudioFormat#ENCODING_AC3}, {@link AudioFormat#ENCODING_E_AC3},
@@ -6785,10 +6848,29 @@
* @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 3cd615b..ac96e6f 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -18,6 +18,9 @@
import android.annotation.NonNull;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
/**
* An AudioProfile is specific to an audio format and lists supported sampling rates and
* channel masks for that format. An {@link AudioDeviceInfo} has a list of supported AudioProfiles.
@@ -63,4 +66,29 @@
public @NonNull int[] getSampleRates() {
return mSamplingRates;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{");
+ sb.append(AudioFormat.toLogFriendlyEncoding(mFormat));
+ if (mSamplingRates != null && mSamplingRates.length > 0) {
+ sb.append(", sampling rates=").append(Arrays.toString(mSamplingRates));
+ }
+ if (mChannelMasks != null && mChannelMasks.length > 0) {
+ sb.append(", channel masks=").append(toHexString(mChannelMasks));
+ }
+ if (mChannelIndexMasks != null && mChannelIndexMasks.length > 0) {
+ sb.append(", channel index masks=").append(Arrays.toString(mChannelIndexMasks));
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String toHexString(int[] ints) {
+ if (ints == null || ints.length == 0) {
+ return "";
+ }
+ 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 0d61399..3399377 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -32,6 +32,7 @@
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 @@
/**
* 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 @@
}
/**
- * 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 bccefdf..7a2b022 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -26,6 +26,7 @@
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.ByteOrder;
import java.nio.NioUtils;
import java.util.LinkedList;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -567,9 +569,9 @@
/**
* 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 @@
}
/**
- * 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 4f87fe6..ee945d5 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -158,6 +158,14 @@
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 cf31e41..b51777c 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -25,7 +25,6 @@
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.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 @@
</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 @@
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 @@
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 @@
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 @@
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 @@
mEventHandler = null;
}
mCallbackHandler = mEventHandler;
+ mOnFirstTunnelFrameReadyHandler = mEventHandler;
mOnFrameRenderedHandler = mEventHandler;
mBufferLock = new Object();
@@ -2277,6 +2275,9 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 06d0eb0..a9e8c26 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -3434,11 +3434,14 @@
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 ae64c02..10b99dc 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -27,7 +27,7 @@
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.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 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 @@
/**
* 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 @@
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 8f60330..283f1f1 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,6 +22,7 @@
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.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 @@
* <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 @@
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 @@
}
private MediaCas mMediaCas;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
private long mNativeContext;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 3de78bb..644afb7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -99,9 +99,7 @@
/**
- * MediaPlayer class can be used to control playback
- * of audio/video files and streams. An example on how to use the methods in
- * this class can be found in {@link android.widget.VideoView}.
+ * MediaPlayer class can be used to control playback of audio/video files and streams.
*
* <p>MediaPlayer is not thread-safe. Creation of and all access to player instances
* should be on the same thread. If registering <a href="#Callbacks">callbacks</a>,
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index dd08d8a..f960ff2 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -29,6 +29,7 @@
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 @@
private int mChannelCount;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
+
/**
* Default constructor.
*
@@ -165,6 +168,27 @@
}
/**
+ * 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 02fa040..1f6855a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -194,6 +194,14 @@
/**
* 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 @@
/**
* 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 6fefbe1..758a813 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -148,6 +148,14 @@
/**
* 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 @@
/**
* 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 2f95247..37fee84 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -18,6 +18,7 @@
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 @@
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/Event.java b/media/java/android/media/metrics/Event.java
index 96b61d2..17218f0 100644
--- a/media/java/android/media/metrics/Event.java
+++ b/media/java/android/media/metrics/Event.java
@@ -17,6 +17,7 @@
package android.media.metrics;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.os.Bundle;
/**
@@ -24,7 +25,7 @@
*/
public abstract class Event {
final long mTimeSinceCreatedMillis;
- Bundle mExtras;
+ Bundle mMetricsBundle = new Bundle();
// hide default constructor
/* package */ Event() {
@@ -38,7 +39,7 @@
/* package */ Event(long timeSinceCreatedMillis, Bundle extras) {
mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- mExtras = extras;
+ mMetricsBundle = extras;
}
/**
@@ -50,8 +51,12 @@
return mTimeSinceCreatedMillis;
}
- /** @hide */
- public Bundle getExtras() {
- return mExtras;
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
}
}
diff --git a/media/java/android/media/metrics/LogSessionId.java b/media/java/android/media/metrics/LogSessionId.java
index 7ddb259..41f3093 100644
--- a/media/java/android/media/metrics/LogSessionId.java
+++ b/media/java/android/media/metrics/LogSessionId.java
@@ -16,19 +16,50 @@
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.
- * @hide
*/
-public class LogSessionId {
- private final String mSessionId;
+public final class LogSessionId {
+ @NonNull private final String mSessionId;
- /* package */ LogSessionId(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;
}
+
+ @Override
+ public String toString() {
+ return mSessionId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LogSessionId that = (LogSessionId) o;
+ return Objects.equals(mSessionId, that.mSessionId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSessionId);
+ }
}
diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java
index 9710e88..b4a74a3 100644
--- a/media/java/android/media/metrics/MediaMetricsManager.java
+++ b/media/java/android/media/metrics/MediaMetricsManager.java
@@ -104,7 +104,6 @@
/**
* Creates a recording session.
- * @hide
*/
@NonNull
public RecordingSession createRecordingSession() {
diff --git a/media/java/android/media/metrics/NetworkEvent.java b/media/java/android/media/metrics/NetworkEvent.java
index 18660682..0e80543 100644
--- a/media/java/android/media/metrics/NetworkEvent.java
+++ b/media/java/android/media/metrics/NetworkEvent.java
@@ -32,10 +32,7 @@
* Media network event.
*/
public final class NetworkEvent extends Event implements Parcelable {
- /** Network type is not specified. Default type. */
- public static final int NETWORK_TYPE_NONE = 0;
- // TODO: replace NONE with UNKNOWN
- /** @hide */
+ /** Network type is not known. Default type. */
public static final int NETWORK_TYPE_UNKNOWN = 0;
/** Other network type */
public static final int NETWORK_TYPE_OTHER = 1;
@@ -54,7 +51,6 @@
/** 5G SA network */
public static final int NETWORK_TYPE_5G_SA = 8;
/** Not network connected */
- /** @hide */
public static final int NETWORK_TYPE_OFFLINE = 9;
private final int mNetworkType;
@@ -62,7 +58,6 @@
/** @hide */
@IntDef(prefix = "NETWORK_TYPE_", value = {
- NETWORK_TYPE_NONE,
NETWORK_TYPE_UNKNOWN,
NETWORK_TYPE_OTHER,
NETWORK_TYPE_WIFI,
@@ -83,8 +78,8 @@
*/
public static String networkTypeToString(@NetworkType int value) {
switch (value) {
- case NETWORK_TYPE_NONE:
- return "NETWORK_TYPE_NONE";
+ case NETWORK_TYPE_UNKNOWN:
+ return "NETWORK_TYPE_UNKNOWN";
case NETWORK_TYPE_OTHER:
return "NETWORK_TYPE_OTHER";
case NETWORK_TYPE_WIFI:
@@ -114,10 +109,10 @@
* @hide
*/
public NetworkEvent(@NetworkType int type, long timeSinceCreatedMillis,
- @Nullable Bundle extras) {
+ @NonNull Bundle extras) {
this.mNetworkType = type;
this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- this.mExtras = extras == null ? null : extras.deepCopy();
+ this.mMetricsBundle = extras == null ? null : extras.deepCopy();
}
/**
@@ -138,6 +133,16 @@
return mTimeSinceCreatedMillis;
}
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @Override
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
+ }
+
@Override
public String toString() {
return "NetworkEvent { "
@@ -162,12 +167,9 @@
@Override
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- byte flg = 0;
- if (mExtras != null) flg |= 0x1;
- dest.writeByte(flg);
dest.writeInt(mNetworkType);
dest.writeLong(mTimeSinceCreatedMillis);
- if (mExtras != null) dest.writeBundle(mExtras);
+ dest.writeBundle(mMetricsBundle);
}
@Override
@@ -177,14 +179,13 @@
/** @hide */
/* package-private */ NetworkEvent(@NonNull android.os.Parcel in) {
- byte flg = in.readByte();
int type = in.readInt();
long timeSinceCreatedMillis = in.readLong();
- Bundle extras = (flg & 0x2) == 0 ? null : in.readBundle();
+ Bundle extras = in.readBundle();
this.mNetworkType = type;
this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- this.mExtras = extras;
+ this.mMetricsBundle = extras;
}
/**
@@ -207,9 +208,9 @@
* A builder for {@link NetworkEvent}
*/
public static final class Builder {
- private int mNetworkType = NETWORK_TYPE_NONE;
+ private int mNetworkType = NETWORK_TYPE_UNKNOWN;
private long mTimeSinceCreatedMillis = -1;
- private Bundle mExtras;
+ private Bundle mMetricsBundle = new Bundle();
/**
* Creates a new Builder.
@@ -236,18 +237,20 @@
}
/**
- * Set extras for compatibility.
- * <p>Should be used by support library only.
- * @hide
+ * Sets metrics-related information that is not supported by dedicated
+ * methods.
+ * <p>It is intended to be used for backwards compatibility by the
+ * metrics infrastructure.
*/
- public @NonNull Builder setExtras(@NonNull Bundle extras) {
- mExtras = extras;
+ public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+ mMetricsBundle = metricsBundle;
return this;
}
/** Builds the instance. */
public @NonNull NetworkEvent build() {
- NetworkEvent o = new NetworkEvent(mNetworkType, mTimeSinceCreatedMillis, mExtras);
+ NetworkEvent o =
+ new NetworkEvent(mNetworkType, mTimeSinceCreatedMillis, mMetricsBundle);
return o;
}
}
diff --git a/media/java/android/media/metrics/PlaybackErrorEvent.java b/media/java/android/media/metrics/PlaybackErrorEvent.java
index ccf848b..f36c04e 100644
--- a/media/java/android/media/metrics/PlaybackErrorEvent.java
+++ b/media/java/android/media/metrics/PlaybackErrorEvent.java
@@ -33,11 +33,77 @@
*/
public final class PlaybackErrorEvent extends Event implements Parcelable {
/** Unknown error code. */
- public static final int ERROR_CODE_UNKNOWN = 0;
+ public static final int ERROR_UNKNOWN = 0;
/** Error code for other errors */
- public static final int ERROR_CODE_OTHER = 1;
+ public static final int ERROR_OTHER = 1;
/** Error code for runtime errors */
- public static final int ERROR_CODE_RUNTIME = 2;
+ public static final int ERROR_RUNTIME = 2;
+
+ /** No network */
+ public static final int ERROR_NETWORK_OFFLINE = 3;
+ /** Connection opening error */
+ public static final int ERROR_NETWORK_CONNECT = 4;
+ /** Bad HTTP status code */
+ public static final int ERROR_NETWORK_BAD_STATUS = 5;
+ /** DNS resolution error */
+ public static final int ERROR_NETWORK_DNS = 6;
+ /** Network socket timeout */
+ public static final int ERROR_NETWORK_TIMEOUT = 7;
+ /** Connection closed */
+ public static final int ERROR_NETWORK_CLOSED = 8;
+ /** Other network errors */
+ public static final int ERROR_NETWORK_OTHER = 9;
+
+ /** Manifest parsing error */
+ public static final int ERROR_MEDIA_MANIFEST = 10;
+ /**
+ * Media bitstream (audio, video, text, metadata) parsing error, either malformed or
+ * unsupported.
+ */
+ public static final int ERROR_MEDIA_PARSER = 11;
+ /** Other media errors */
+ public static final int ERROR_MEDIA_OTHER = 12;
+
+ /** Codec initialization failed */
+ public static final int ERROR_DECODER_INIT = 13;
+ /** Decoding failed */
+ public static final int ERROR_DECODER_DECODE = 14;
+ /** Out of memory */
+ public static final int ERROR_DECODER_OOM = 15;
+ /** Other decoder errors */
+ public static final int ERROR_DECODER_OTHER = 16;
+
+ /** AudioTrack initialization failed */
+ public static final int ERROR_AUDIOTRACK_INIT = 17;
+ /** AudioTrack writing failed */
+ public static final int ERROR_AUDIOTRACK_WRITE = 18;
+ /** Other AudioTrack errors */
+ public static final int ERROR_AUDIOTRACK_OTHER = 19;
+
+ /** Exception in remote controller or player */
+ public static final int ERROR_PLAYER_REMOTE = 20;
+ /** Error when a Live playback falls behind the Live DVR window. */
+ public static final int ERROR_PLAYER_BEHIND_LIVE_WINDOW = 21;
+ /** Other player errors */
+ public static final int ERROR_PLAYER_OTHER = 22;
+
+ /** Scheme unsupported by device */
+ public static final int ERROR_DRM_UNAVAILABLE = 23;
+ /** Provisioning failed */
+ public static final int ERROR_DRM_PROVISIONING_FAILED = 24;
+ /** Failed to acquire license */
+ public static final int ERROR_DRM_LICENSE_ERROR = 25;
+ /** Operation prevented by license policy */
+ public static final int ERROR_DRM_DISALLOWED = 26;
+ /** Failure in the DRM system */
+ public static final int ERROR_DRM_SYSTEM_ERROR = 27;
+ /** Incompatible content */
+ public static final int ERROR_DRM_CONTENT_ERROR = 28;
+ /** Device has been revoked */
+ public static final int ERROR_DRM_REVOKED = 29;
+ /** Other drm errors */
+ public static final int ERROR_DRM_OTHER = 30;
+
private final @Nullable String mExceptionStack;
private final int mErrorCode;
@@ -46,11 +112,38 @@
/** @hide */
- // TODO: more error types
- @IntDef(prefix = "ERROR_CODE_", value = {
- ERROR_CODE_UNKNOWN,
- ERROR_CODE_OTHER,
- ERROR_CODE_RUNTIME
+ @IntDef(prefix = "ERROR_", value = {
+ ERROR_UNKNOWN,
+ ERROR_OTHER,
+ ERROR_RUNTIME,
+ ERROR_NETWORK_OFFLINE,
+ ERROR_NETWORK_CONNECT,
+ ERROR_NETWORK_BAD_STATUS,
+ ERROR_NETWORK_DNS,
+ ERROR_NETWORK_TIMEOUT,
+ ERROR_NETWORK_CLOSED,
+ ERROR_NETWORK_OTHER,
+ ERROR_MEDIA_MANIFEST,
+ ERROR_MEDIA_PARSER,
+ ERROR_MEDIA_OTHER,
+ ERROR_DECODER_INIT,
+ ERROR_DECODER_DECODE,
+ ERROR_DECODER_OOM,
+ ERROR_DECODER_OTHER,
+ ERROR_AUDIOTRACK_INIT,
+ ERROR_AUDIOTRACK_WRITE,
+ ERROR_AUDIOTRACK_OTHER,
+ ERROR_PLAYER_REMOTE,
+ ERROR_PLAYER_BEHIND_LIVE_WINDOW,
+ ERROR_PLAYER_OTHER,
+ ERROR_DRM_UNAVAILABLE,
+ ERROR_DRM_PROVISIONING_FAILED,
+ ERROR_DRM_LICENSE_ERROR,
+ ERROR_DRM_DISALLOWED,
+ ERROR_DRM_SYSTEM_ERROR,
+ ERROR_DRM_CONTENT_ERROR,
+ ERROR_DRM_REVOKED,
+ ERROR_DRM_OTHER,
})
@Retention(java.lang.annotation.RetentionPolicy.SOURCE)
public @interface ErrorCode {}
@@ -65,12 +158,12 @@
int errorCode,
int subErrorCode,
long timeSinceCreatedMillis,
- @Nullable Bundle extras) {
+ @NonNull Bundle extras) {
this.mExceptionStack = exceptionStack;
this.mErrorCode = errorCode;
this.mSubErrorCode = subErrorCode;
this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- this.mExtras = extras == null ? null : extras.deepCopy();
+ this.mMetricsBundle = extras.deepCopy();
}
/** @hide */
@@ -107,6 +200,16 @@
return mTimeSinceCreatedMillis;
}
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @Override
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
+ }
+
@Override
public String toString() {
return "PlaybackErrorEvent { "
@@ -138,13 +241,12 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
byte flg = 0;
if (mExceptionStack != null) flg |= 0x1;
- if (mExtras != null) flg |= 0x2;
dest.writeByte(flg);
if (mExceptionStack != null) dest.writeString(mExceptionStack);
dest.writeInt(mErrorCode);
dest.writeInt(mSubErrorCode);
dest.writeLong(mTimeSinceCreatedMillis);
- if (mExtras != null) dest.writeBundle(mExtras);
+ dest.writeBundle(mMetricsBundle);
}
@Override
@@ -159,13 +261,13 @@
int errorCode = in.readInt();
int subErrorCode = in.readInt();
long timeSinceCreatedMillis = in.readLong();
- Bundle extras = (flg & 0x2) == 0 ? null : in.readBundle();
+ Bundle extras = in.readBundle();
this.mExceptionStack = exceptionStack;
this.mErrorCode = errorCode;
this.mSubErrorCode = subErrorCode;
this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- this.mExtras = extras;
+ this.mMetricsBundle = extras;
}
@@ -190,7 +292,7 @@
private int mErrorCode;
private int mSubErrorCode;
private long mTimeSinceCreatedMillis = -1;
- private Bundle mExtras;
+ private Bundle mMetricsBundle = new Bundle();
/**
* Creates a new Builder.
@@ -235,12 +337,13 @@
}
/**
- * Set extras for compatibility.
- * <p>Should be used by support library only.
- * @hide
+ * Sets metrics-related information that is not supported by dedicated
+ * methods.
+ * <p>It is intended to be used for backwards compatibility by the
+ * metrics infrastructure.
*/
- public @NonNull Builder setExtras(@NonNull Bundle extras) {
- mExtras = extras;
+ public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+ mMetricsBundle = metricsBundle;
return this;
}
@@ -260,7 +363,7 @@
mErrorCode,
mSubErrorCode,
mTimeSinceCreatedMillis,
- mExtras);
+ mMetricsBundle);
return o;
}
}
diff --git a/media/java/android/media/metrics/PlaybackMetrics.java b/media/java/android/media/metrics/PlaybackMetrics.java
index 3ffd10f..5f606a0 100644
--- a/media/java/android/media/metrics/PlaybackMetrics.java
+++ b/media/java/android/media/metrics/PlaybackMetrics.java
@@ -60,15 +60,13 @@
public static final int STREAM_TYPE_SS = 5;
/** Unknown playback type. */
- // TODO: change the PLAYBACK_TYPE_ values
- /** @hide */
public static final int PLAYBACK_TYPE_UNKNOWN = 0;
/** VOD (Video on Demand) playback type. */
- public static final int PLAYBACK_TYPE_VOD = 0;
+ public static final int PLAYBACK_TYPE_VOD = 1;
/** Live playback type. */
- public static final int PLAYBACK_TYPE_LIVE = 1;
+ public static final int PLAYBACK_TYPE_LIVE = 2;
/** Other playback type. */
- public static final int PLAYBACK_TYPE_OTHER = 2;
+ public static final int PLAYBACK_TYPE_OTHER = 3;
/** DRM is not used. */
public static final int DRM_TYPE_NONE = 0;
@@ -86,15 +84,13 @@
public static final int DRM_TYPE_CLEARKEY = 6;
/** Unknown content type. */
- // TODO: change the CONTENT_TYPE_ values
- /** @hide */
public static final int CONTENT_TYPE_UNKNOWN = 0;
/** Main contents. */
- public static final int CONTENT_TYPE_MAIN = 0;
+ public static final int CONTENT_TYPE_MAIN = 1;
/** Advertisement contents. */
- public static final int CONTENT_TYPE_AD = 1;
+ public static final int CONTENT_TYPE_AD = 2;
/** Other contents. */
- public static final int CONTENT_TYPE_OTHER = 2;
+ public static final int CONTENT_TYPE_OTHER = 3;
/** @hide */
@@ -170,7 +166,7 @@
private final long mLocalBytesRead;
private final long mNetworkTransferDurationMillis;
private final byte[] mDrmSessionId;
- private final Bundle mExtras;
+ private final @NonNull Bundle mMetricsBundle;
/**
* Creates a new PlaybackMetrics.
@@ -194,7 +190,7 @@
long localBytesRead,
long networkTransferDurationMillis,
byte[] drmSessionId,
- @Nullable Bundle extras) {
+ @NonNull Bundle extras) {
this.mMediaDurationMillis = mediaDurationMillis;
this.mStreamSource = streamSource;
this.mStreamType = streamType;
@@ -212,7 +208,7 @@
this.mLocalBytesRead = localBytesRead;
this.mNetworkTransferDurationMillis = networkTransferDurationMillis;
this.mDrmSessionId = drmSessionId;
- this.mExtras = extras == null ? null : extras.deepCopy();
+ this.mMetricsBundle = extras.deepCopy();
}
/**
@@ -338,12 +334,23 @@
return mNetworkTransferDurationMillis;
}
- /** @hide */
+ /**
+ * Gets DRM session ID.
+ */
@NonNull
public byte[] getDrmSessionId() {
return mDrmSessionId;
}
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
+ }
+
@Override
public String toString() {
return "PlaybackMetrics { "
@@ -402,7 +409,6 @@
long flg = 0;
if (mPlayerName != null) flg |= 0x80;
if (mPlayerVersion != null) flg |= 0x100;
- if (mExtras != null) flg |= 0x200;
dest.writeLong(flg);
dest.writeLong(mMediaDurationMillis);
dest.writeInt(mStreamSource);
@@ -412,7 +418,6 @@
dest.writeInt(mContentType);
if (mPlayerName != null) dest.writeString(mPlayerName);
if (mPlayerVersion != null) dest.writeString(mPlayerVersion);
- if (mExtras != null) dest.writeBundle(mExtras);
dest.writeLongArray(mExperimentIds);
dest.writeInt(mVideoFramesPlayed);
dest.writeInt(mVideoFramesDropped);
@@ -422,6 +427,7 @@
dest.writeLong(mNetworkTransferDurationMillis);
dest.writeInt(mDrmSessionId.length);
dest.writeByteArray(mDrmSessionId);
+ dest.writeBundle(mMetricsBundle);
}
@Override
@@ -440,7 +446,6 @@
int contentType = in.readInt();
String playerName = (flg & 0x80) == 0 ? null : in.readString();
String playerVersion = (flg & 0x100) == 0 ? null : in.readString();
- Bundle extras = (flg & 0x200) == 0 ? null : in.readBundle();
long[] experimentIds = in.createLongArray();
int videoFramesPlayed = in.readInt();
int videoFramesDropped = in.readInt();
@@ -451,6 +456,7 @@
int drmSessionIdLen = in.readInt();
byte[] drmSessionId = new byte[drmSessionIdLen];
in.readByteArray(drmSessionId);
+ Bundle extras = in.readBundle();
this.mMediaDurationMillis = mediaDurationMillis;
this.mStreamSource = streamSource;
@@ -469,7 +475,7 @@
this.mLocalBytesRead = localBytesRead;
this.mNetworkTransferDurationMillis = networkTransferDurationMillis;
this.mDrmSessionId = drmSessionId;
- this.mExtras = extras;
+ this.mMetricsBundle = extras;
}
public static final @NonNull Parcelable.Creator<PlaybackMetrics> CREATOR =
@@ -506,7 +512,7 @@
private long mLocalBytesRead = -1;
private long mNetworkTransferDurationMillis = -1;
private byte[] mDrmSessionId = new byte[0];
- private Bundle mExtras;
+ private Bundle mMetricsBundle = new Bundle();
/**
* Creates a new Builder.
@@ -646,7 +652,7 @@
}
/**
- * @hide
+ * Sets DRM session ID.
*/
public @NonNull Builder setDrmSessionId(@NonNull byte[] drmSessionId) {
mDrmSessionId = drmSessionId;
@@ -654,12 +660,13 @@
}
/**
- * Set extras for compatibility.
- * <p>Should be used by support library only.
- * @hide
+ * Sets metrics-related information that is not supported by dedicated
+ * methods.
+ * <p>It is intended to be used for backwards compatibility by the
+ * metrics infrastructure.
*/
- public @NonNull Builder setExtras(@NonNull Bundle extras) {
- mExtras = extras;
+ public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+ mMetricsBundle = metricsBundle;
return this;
}
@@ -683,7 +690,7 @@
mLocalBytesRead,
mNetworkTransferDurationMillis,
mDrmSessionId,
- mExtras);
+ mMetricsBundle);
return o;
}
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index 272fd9b..aad510e 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -80,14 +80,7 @@
mManager.reportTrackChangeEvent(mId, event);
}
- public @NonNull String getId() {
- // TODO: remove this method and use getSessionId();
- return mId;
- }
-
- /** @hide */
public @NonNull LogSessionId getSessionId() {
- // TODO: remove getId() and use this method;
return mLogSessionId;
}
diff --git a/media/java/android/media/metrics/PlaybackStateEvent.java b/media/java/android/media/metrics/PlaybackStateEvent.java
index 2bab5c9..449abe9 100644
--- a/media/java/android/media/metrics/PlaybackStateEvent.java
+++ b/media/java/android/media/metrics/PlaybackStateEvent.java
@@ -138,10 +138,10 @@
public PlaybackStateEvent(
int state,
long timeSinceCreatedMillis,
- @Nullable Bundle extras) {
+ @NonNull Bundle extras) {
this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
this.mState = state;
- this.mExtras = extras == null ? null : extras.deepCopy();
+ this.mMetricsBundle = extras.deepCopy();
}
/**
@@ -161,6 +161,16 @@
return mTimeSinceCreatedMillis;
}
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @Override
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
@@ -177,12 +187,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- byte flg = 0;
- if (mExtras != null) flg |= 0x1;
- dest.writeByte(flg);
dest.writeInt(mState);
dest.writeLong(mTimeSinceCreatedMillis);
- if (mExtras != null) dest.writeBundle(mExtras);
+ dest.writeBundle(mMetricsBundle);
}
@Override
@@ -192,14 +199,13 @@
/** @hide */
/* package-private */ PlaybackStateEvent(@NonNull Parcel in) {
- byte flg = in.readByte();
int state = in.readInt();
long timeSinceCreatedMillis = in.readLong();
- Bundle extras = (flg & 0x1) == 0 ? null : in.readBundle();
+ Bundle extras = in.readBundle();
this.mState = state;
this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- this.mExtras = extras;
+ this.mMetricsBundle = extras;
}
public static final @NonNull Parcelable.Creator<PlaybackStateEvent> CREATOR =
@@ -221,7 +227,7 @@
public static final class Builder {
private int mState = STATE_NOT_STARTED;
private long mTimeSinceCreatedMillis = -1;
- private Bundle mExtras;
+ private Bundle mMetricsBundle = new Bundle();
/**
* Creates a new Builder.
@@ -248,12 +254,13 @@
}
/**
- * Set extras for compatibility.
- * <p>Should be used by support library only.
- * @hide
+ * Sets metrics-related information that is not supported by dedicated
+ * methods.
+ * <p>It is intended to be used for backwards compatibility by the
+ * metrics infrastructure.
*/
- public @NonNull Builder setExtras(@NonNull Bundle extras) {
- mExtras = extras;
+ public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+ mMetricsBundle = metricsBundle;
return this;
}
@@ -262,7 +269,7 @@
PlaybackStateEvent o = new PlaybackStateEvent(
mState,
mTimeSinceCreatedMillis,
- mExtras);
+ mMetricsBundle);
return o;
}
}
diff --git a/media/java/android/media/metrics/RecordingSession.java b/media/java/android/media/metrics/RecordingSession.java
index 541d129..d388351 100644
--- a/media/java/android/media/metrics/RecordingSession.java
+++ b/media/java/android/media/metrics/RecordingSession.java
@@ -25,7 +25,6 @@
/**
* An instances of this class represents a session of media recording.
- * @hide
*/
public final class RecordingSession implements AutoCloseable {
private final @NonNull String mId;
@@ -42,6 +41,10 @@
mLogSessionId = new LogSessionId(mId);
}
+ public @NonNull LogSessionId getSessionId() {
+ return mLogSessionId;
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
diff --git a/media/java/android/media/metrics/TrackChangeEvent.java b/media/java/android/media/metrics/TrackChangeEvent.java
index a3eb4ad..c3670269 100644
--- a/media/java/android/media/metrics/TrackChangeEvent.java
+++ b/media/java/android/media/metrics/TrackChangeEvent.java
@@ -102,39 +102,6 @@
@Retention(RetentionPolicy.SOURCE)
public @interface TrackType {}
- // TODO: remove this constructor. Use the private one below.
- public TrackChangeEvent(
- int state,
- int reason,
- @Nullable String containerMimeType,
- @Nullable String sampleMimeType,
- @Nullable String codecName,
- int bitrate,
- long timeSinceCreatedMillis,
- int type,
- @Nullable String language,
- @Nullable String languageRegion,
- int channelCount,
- int sampleRate,
- int width,
- int height) {
- this.mState = state;
- this.mReason = reason;
- this.mContainerMimeType = containerMimeType;
- this.mSampleMimeType = sampleMimeType;
- this.mCodecName = codecName;
- this.mBitrate = bitrate;
- this.mTimeSinceCreatedMillis = timeSinceCreatedMillis;
- this.mType = type;
- this.mLanguage = language;
- this.mLanguageRegion = languageRegion;
- this.mChannelCount = channelCount;
- this.mAudioSampleRate = sampleRate;
- this.mWidth = width;
- this.mHeight = height;
- this.mVideoFrameRate = -1;
- }
-
private TrackChangeEvent(
int state,
int reason,
@@ -151,7 +118,7 @@
int width,
int height,
float videoFrameRate,
- @Nullable Bundle extras) {
+ @NonNull Bundle extras) {
this.mState = state;
this.mReason = reason;
this.mContainerMimeType = containerMimeType;
@@ -167,7 +134,7 @@
this.mWidth = width;
this.mHeight = height;
this.mVideoFrameRate = videoFrameRate;
- this.mExtras = extras == null ? null : extras.deepCopy();
+ this.mMetricsBundle = extras.deepCopy();
}
/**
@@ -258,11 +225,11 @@
}
/**
- * Gets sample rate.
+ * Gets audio sample rate.
* @return the sample rate, or -1 if unknown.
*/
@IntRange(from = -1, to = Integer.MAX_VALUE)
- public int getSampleRate() {
+ public int getAudioSampleRate() {
return mAudioSampleRate;
}
@@ -287,13 +254,22 @@
/**
* Gets video frame rate.
* @return the video frame rate, or -1 if unknown.
- * @hide
*/
@FloatRange(from = -1, to = Float.MAX_VALUE)
public float getVideoFrameRate() {
return mVideoFrameRate;
}
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @Override
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
int flg = 0;
@@ -302,7 +278,6 @@
if (mCodecName != null) flg |= 0x10;
if (mLanguage != null) flg |= 0x100;
if (mLanguageRegion != null) flg |= 0x200;
- if (mExtras != null) flg |= 0x400;
dest.writeInt(flg);
dest.writeInt(mState);
dest.writeInt(mReason);
@@ -319,7 +294,7 @@
dest.writeInt(mWidth);
dest.writeInt(mHeight);
dest.writeFloat(mVideoFrameRate);
- if (mExtras != null) dest.writeBundle(mExtras);
+ dest.writeBundle(mMetricsBundle);
}
@Override
@@ -345,7 +320,7 @@
int width = in.readInt();
int height = in.readInt();
float videoFrameRate = in.readFloat();
- Bundle extras = (flg & 0x400) == 0 ? null : in.readBundle();
+ Bundle extras = in.readBundle();
this.mState = state;
this.mReason = reason;
@@ -362,7 +337,7 @@
this.mWidth = width;
this.mHeight = height;
this.mVideoFrameRate = videoFrameRate;
- this.mExtras = extras;
+ this.mMetricsBundle = extras;
}
public static final @NonNull Parcelable.Creator<TrackChangeEvent> CREATOR =
@@ -448,7 +423,7 @@
private int mWidth = -1;
private int mHeight = -1;
private float mVideoFrameRate = -1;
- private Bundle mExtras;
+ private Bundle mMetricsBundle = new Bundle();
private long mBuilderFieldsSet = 0L;
@@ -571,9 +546,8 @@
* Sets sample rate.
* @param value the sample rate. -1 indicates the value is unknown.
*/
- public @NonNull Builder setSampleRate(
+ public @NonNull Builder setAudioSampleRate(
@IntRange(from = -1, to = Integer.MAX_VALUE) int value) {
- // TODO: rename it to setAudioSampleRate
checkNotUsed();
mBuilderFieldsSet |= 0x800;
mAudioSampleRate = value;
@@ -605,7 +579,6 @@
/**
* Sets video frame rate.
* @param value the video frame rate. -1 indicates the value is unknown.
- * @hide
*/
public @NonNull Builder setVideoFrameRate(
@FloatRange(from = -1, to = Float.MAX_VALUE) float value) {
@@ -615,12 +588,13 @@
}
/**
- * Set extras for compatibility.
- * <p>Should be used by support library only.
- * @hide
+ * Sets metrics-related information that is not supported by dedicated
+ * methods.
+ * <p>It is intended to be used for backwards compatibility by the
+ * metrics infrastructure.
*/
- public @NonNull Builder setExtras(@NonNull Bundle extras) {
- mExtras = extras;
+ public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+ mMetricsBundle = metricsBundle;
return this;
}
@@ -645,7 +619,7 @@
mWidth,
mHeight,
mVideoFrameRate,
- mExtras);
+ mMetricsBundle);
return o;
}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 96bffee..3b0f577 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -39,6 +39,8 @@
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 78db750..269b70b 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -195,6 +195,44 @@
}
/**
+ * 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 @@
}
/**
- * 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 7f5dd5d..f694482 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 @@
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 @@
} gByteBufferInfo;
static struct {
+ jclass clazz;
+ jmethodID ctorId;
jmethodID sizeId;
jmethodID getId;
jmethodID addId;
@@ -153,6 +167,13 @@
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 @@
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)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 @@
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 @@
}
break;
}
+ case kWhatFirstTunnelFrameReady:
+ {
+ handleFirstTunnelFrameReadyNotification(msg);
+ break;
+ }
default:
TRESPASS();
}
@@ -1256,6 +1383,22 @@
}
}
+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 @@
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 @@
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 @@
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 @@
{ "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 @@
{ "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 f16bcf3..ee456c9 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -63,6 +63,8 @@
void release();
void releaseAsync();
+ status_t enableOnFirstTunnelFrameReadyListener(jboolean enable);
+
status_t enableOnFrameRenderedListener(jboolean enable);
status_t setCallback(jobject cb);
@@ -162,6 +164,14 @@
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 @@
kWhatCallbackNotify,
kWhatFrameRendered,
kWhatAsyncReleaseComplete,
+ kWhatFirstTunnelFrameReady,
};
jclass mClass;
@@ -191,6 +202,7 @@
std::once_flag mAsyncReleaseFlag;
sp<AMessage> mCallbackNotification;
+ sp<AMessage> mOnFirstTunnelFrameReadyNotification;
sp<AMessage> mOnFrameRenderedNotification;
status_t mInitStatus;
@@ -203,6 +215,7 @@
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 fc96fd9..3794ccd 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 a0fba73..5af77ee 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 b01878b..35249f6 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -258,6 +258,9 @@
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
@@ -285,7 +288,10 @@
ATrace_endAsyncSection; # introduced=29
ATrace_setCounter; # introduced=29
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
@@ -309,4 +315,4 @@
ASurfaceControlStats_getAcquireTime*;
ASurfaceControlStats_getFrameNumber*;
};
-} LIBANDROID;
\ No newline at end of file
+} LIBANDROID;
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index 8d4e900..a6c1b50 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -14,6 +14,10 @@
android_res_nquery; # llndk
android_res_nresult; # llndk
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 a8104fc..e2f36a7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -22,12 +22,13 @@
#include <stdlib.h>
#include <sys/limits.h>
+// This value MUST be kept in sync with the corresponding value in
+// the android.net.Network#getNetworkHandle() implementation.
+static const uint32_t kHandleMagic = 0xcafed00d;
+static const uint32_t kHandleMagicSize = 32;
static int getnetidfromhandle(net_handle_t handle, unsigned *netid) {
static const uint32_t k32BitMask = 0xffffffff;
- // This value MUST be kept in sync with the corresponding value in
- // the android.net.Network#getNetworkHandle() implementation.
- static const uint32_t kHandleMagic = 0xcafed00d;
// Check for minimum acceptable version of the API in the low bits.
if (handle != NETWORK_UNSPECIFIED &&
@@ -41,6 +42,12 @@
return 1;
}
+static net_handle_t gethandlefromnetid(unsigned netid) {
+ if (netid == NETID_UNSET) {
+ return NETWORK_UNSPECIFIED;
+ }
+ return (((net_handle_t) netid) << kHandleMagicSize) | kHandleMagic;
+}
int android_setsocknetwork(net_handle_t network, int fd) {
unsigned netid;
@@ -72,6 +79,49 @@
return rval;
}
+int android_getprocnetwork(net_handle_t *network) {
+ if (network == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ unsigned netid = getNetworkForProcess();
+ *network = gethandlefromnetid(netid);
+ 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 e8cf63f..195fd5e 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -446,6 +446,44 @@
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 f9795c6..d36836c 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/TEST_MAPPING b/packages/Connectivity/TEST_MAPPING
new file mode 100644
index 0000000..94f9232
--- /dev/null
+++ b/packages/Connectivity/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/java/android/net"
+ },
+ {
+ "path": "packages/modules/NetworkStack"
+ },
+ {
+ "path": "packages/modules/CaptivePortalLogin"
+ },
+ {
+ "path": "packages/modules/Connectivity"
+ },
+ {
+ "path": "packages/modules/Connectivity/Tethering"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 657d5a3..fdfb108 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -26,6 +26,7 @@
java_library {
name: "framework-connectivity-protos",
sdk_version: "module_current",
+ min_sdk_version: "30",
proto: {
type: "nano",
},
@@ -81,11 +82,13 @@
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 @@
"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 @@
"-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",
@@ -128,6 +157,7 @@
srcs: [
"jni/android_net_NetworkUtils.cpp",
],
+ shared_libs: ["libandroid_net"],
apex_available: [
"//apex_available:platform",
"com.android.tethering",
@@ -136,42 +166,15 @@
cc_library_shared {
name: "libframework-connectivity-jni",
+ min_sdk_version: "30",
defaults: ["libframework-connectivity-defaults"],
srcs: [
+ "jni/android_net_NetworkUtils.cpp",
"jni/onload.cpp",
],
- static_libs: ["libconnectivityframeworkutils"],
+ shared_libs: ["libandroid"],
+ 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 e415e01..0a9560a 100644
--- a/packages/Connectivity/framework/api/current.txt
+++ b/packages/Connectivity/framework/api/current.txt
@@ -68,6 +68,7 @@
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 @@
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);
@@ -396,6 +399,7 @@
public static class NetworkRequest.Builder {
ctor public NetworkRequest.Builder();
+ ctor public NetworkRequest.Builder(@NonNull android.net.NetworkRequest);
method public android.net.NetworkRequest.Builder addCapability(int);
method public android.net.NetworkRequest.Builder addTransportType(int);
method public android.net.NetworkRequest build();
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 5bd01a6..2bf807c4 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -11,19 +11,31 @@
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);
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 setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
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 setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
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 @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);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
method public void systemReady();
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";
@@ -33,14 +45,23 @@
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);
}
public final class NetworkCapabilities implements android.os.Parcelable {
+ ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long);
method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
+ method public boolean hasUnwantedCapability(int);
+ field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
+ field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
+ field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
+ field public static final long REDACT_FOR_NETWORK_SETTINGS = 4L; // 0x4L
+ field public static final long REDACT_NONE = 0L; // 0x0L
field public static final int TRANSPORT_TEST = 7; // 0x7
}
@@ -48,7 +69,14 @@
method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
+ public class NetworkRequest implements android.os.Parcelable {
+ method @NonNull public int[] getUnwantedCapabilities();
+ method public boolean hasUnwantedCapability(int);
+ }
+
public static class NetworkRequest.Builder {
+ method @NonNull public android.net.NetworkRequest.Builder addUnwantedCapability(int);
+ method @NonNull public android.net.NetworkRequest.Builder removeUnwantedCapability(int);
method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
@@ -92,6 +120,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkSpecifier> CREATOR;
}
+ public interface TransportInfo {
+ method public default long getApplicableRedactions();
+ method @NonNull public default android.net.TransportInfo makeCopy(long);
+ }
+
public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
ctor public VpnTransportInfo(int);
method public int describeContents();
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 8845225..f3f5533 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -52,7 +52,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
- method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor);
+ method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.QosCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
@@ -212,10 +212,12 @@
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 onNetworkUnwanted();
method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
method public void onQosCallbackUnregistered(int);
@@ -230,9 +232,10 @@
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>);
method public void unregister();
field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
@@ -253,7 +256,12 @@
public static final class NetworkAgentConfig.Builder {
ctor public NetworkAgentConfig.Builder();
method @NonNull public android.net.NetworkAgentConfig build();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
@@ -261,8 +269,8 @@
}
public final class NetworkCapabilities implements android.os.Parcelable {
- ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean);
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();
@@ -317,6 +325,19 @@
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();
@@ -364,6 +385,7 @@
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 {
@@ -389,6 +411,7 @@
}
public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
field public static final int SUCCESS = 0; // 0x0
}
@@ -435,11 +458,6 @@
field public final int tcpWindowScale;
}
- public interface TransportInfo {
- method public default boolean hasLocationSensitiveFields();
- method @NonNull public default android.net.TransportInfo makeCopy(boolean);
- }
-
}
package android.net.apf {
diff --git a/packages/Connectivity/framework/jarjar-rules.txt b/packages/Connectivity/framework/jarjar-rules.txt
index 0959840..7474c24 100644
--- a/packages/Connectivity/framework/jarjar-rules.txt
+++ b/packages/Connectivity/framework/jarjar-rules.txt
@@ -1,4 +1,5 @@
rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
+rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
# TODO (b/149403767): remove the annotations from net-utils-device-common instead of here
zap android.annotation.**
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index 19ffe77..e8bb42d 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -19,6 +19,7 @@
#include <vector>
#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
#include <arpa/inet.h>
#include <linux/filter.h>
#include <linux/if_arp.h>
@@ -30,12 +31,12 @@
#include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS
#include <cutils/properties.h>
+#include <nativehelper/JNIHelp.h>
#include <nativehelper/JNIPlatformHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Log.h>
#include <utils/misc.h>
-#include "NetdClient.h"
#include "jni.h"
extern "C" {
@@ -57,14 +58,6 @@
return clazz;
}
-static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
- const char* method_signature) {
- jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
- method_signature);
- return res;
-}
-
template <typename T>
static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
jobject res = env->NewGlobalRef(in);
@@ -72,27 +65,6 @@
return static_cast<T>(res);
}
-static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
- ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
- if (detailMessage.get() == NULL) {
- // Not really much we can do here. We're probably dead in the water,
- // but let's try to stumble on...
- env->ExceptionClear();
- }
- static jclass errnoExceptionClass =
- MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
-
- static jmethodID errnoExceptionCtor =
- GetMethodIDOrDie(env, errnoExceptionClass,
- "<init>", "(Ljava/lang/String;I)V");
-
- jobject exception = env->NewObject(errnoExceptionClass,
- errnoExceptionCtor,
- detailMessage.get(),
- error);
- env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
struct sock_filter filter_code[] = {
@@ -122,30 +94,32 @@
}
}
-static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
+ jlong netHandle)
{
- return (jboolean) !setNetworkForProcess(netId);
+ return (jboolean) !android_setprocnetwork(netHandle);
}
-static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz)
+static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
{
- return getNetworkForProcess();
+ net_handle_t network;
+ if (android_getprocnetwork(&network) != 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "android_getprocnetwork(): %s", strerror(errno));
+ return NETWORK_UNSPECIFIED;
+ }
+ return (jlong) network;
}
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)
@@ -157,7 +131,7 @@
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);
@@ -167,25 +141,26 @@
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) {
- throwErrnoException(env, "resNetworkQuery", -fd);
+ jniThrowErrnoException(env, "resNetworkQuery", -fd);
return nullptr;
}
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) {
- throwErrnoException(env, "resNetworkSend", -fd);
+ jniThrowErrnoException(env, "resNetworkSend", -fd);
return nullptr;
}
@@ -197,16 +172,16 @@
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) {
- throwErrnoException(env, "resNetworkResult", -res);
+ jniThrowErrnoException(env, "resNetworkResult", -res);
return nullptr;
}
jbyteArray answer = env->NewByteArray(res);
if (answer == nullptr) {
- throwErrnoException(env, "resNetworkResult", ENOMEM);
+ jniThrowErrnoException(env, "resNetworkResult", ENOMEM);
return nullptr;
} else {
env->SetByteArrayRegion(answer, 0, res,
@@ -221,23 +196,22 @@
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) {
- throwErrnoException(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) {
@@ -253,7 +227,7 @@
// Obtain the parameters of the TCP repair window.
int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size);
if (rc == -1) {
- throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
+ jniThrowErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
return NULL;
}
@@ -264,7 +238,7 @@
// should be applied to the window size.
rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size);
if (rc == -1) {
- throwErrnoException(env, "getsockopt : TCP_INFO", errno);
+ jniThrowErrnoException(env, "getsockopt : TCP_INFO", errno);
return NULL;
}
@@ -283,16 +257,15 @@
// clang-format off
static const JNINativeMethod gNetworkUtilMethods[] = {
/* name, signature, funcPtr */
- { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
- { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
+ { "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 f8a0e4e..a73d76e 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -829,6 +829,94 @@
})
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 @@
*
* @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
@@ -1125,12 +1212,13 @@
* @param ranges the UID ranges to restrict
* @param requireVpn whether the specified UID ranges must use a VPN
*
- * TODO: expose as @SystemApi.
* @hide
*/
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
- android.Manifest.permission.NETWORK_STACK})
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ @SystemApi(client = MODULE_LIBRARIES)
public void setRequireVpnForUids(boolean requireVpn,
@NonNull Collection<Range<Integer>> ranges) {
Objects.requireNonNull(ranges);
@@ -1174,13 +1262,13 @@
*
* @param enabled whether legacy lockdown VPN is enabled or disabled
*
- * TODO: @SystemApi(client = MODULE_LIBRARIES)
- *
* @hide
*/
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.NETWORK_SETTINGS})
+ @SystemApi(client = MODULE_LIBRARIES)
public void setLegacyLockdownVpnEnabled(boolean enabled) {
try {
mService.setLegacyLockdownVpnEnabled(enabled);
@@ -2127,6 +2215,7 @@
*/
@Deprecated
@UnsupportedAppUsage
+ @SystemApi(client = MODULE_LIBRARIES)
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
checkLegacyRoutingApiAccess();
try {
@@ -3233,7 +3322,60 @@
provider.setProviderId(NetworkProvider.ID_NONE);
}
+ /**
+ * Register or update a network offer with ConnectivityService.
+ *
+ * ConnectivityService keeps track of offers made by the various providers and matches
+ * them to networking requests made by apps or the system. The provider supplies a score
+ * and the capabilities of the network it might be able to bring up ; these act as filters
+ * used by ConnectivityService to only send those requests that can be fulfilled by the
+ * provider.
+ *
+ * The provider is under no obligation to be able to bring up the network it offers at any
+ * given time. Instead, this mechanism is meant to limit requests received by providers
+ * to those they actually have a chance to fulfill, as providers don't have a way to compare
+ * the quality of the network satisfying a given request to their own offer.
+ *
+ * An offer can be updated by calling this again with the same callback object. This is
+ * similar to calling unofferNetwork and offerNetwork again, but will only update the
+ * provider with the changes caused by the changes in the offer.
+ *
+ * @param provider The provider making this offer.
+ * @param score The prospective score of the network.
+ * @param caps The prospective capabilities of the network.
+ * @param callback The callback to call when this offer is needed or unneeded.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_FACTORY})
+ public void offerNetwork(@NonNull final int providerId,
+ @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
+ @NonNull final INetworkOfferCallback callback) {
+ try {
+ mService.offerNetwork(providerId,
+ Objects.requireNonNull(score, "null score"),
+ Objects.requireNonNull(caps, "null caps"),
+ Objects.requireNonNull(callback, "null callback"));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ /**
+ * Withdraw a network offer made with {@link #offerNetwork}.
+ *
+ * @param callback The callback passed at registration time. This must be the same object
+ * that was passed to {@link #offerNetwork}
+ * @hide
+ */
+ public void unofferNetwork(@NonNull final INetworkOfferCallback callback) {
+ try {
+ mService.unofferNetwork(Objects.requireNonNull(callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/** @hide exposed via the NetworkProvider class. */
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
@@ -3703,8 +3845,9 @@
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) {
@@ -3729,8 +3872,8 @@
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);
@@ -3745,6 +3888,12 @@
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.
*
@@ -4230,8 +4379,40 @@
@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);
}
@@ -4942,20 +5123,20 @@
* {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}.
*
* @param socketInfo the socket information used to match QoS events
- * @param callback receives qos events that satisfy socketInfo
* @param executor The executor on which the callback will be invoked. The provided
* {@link Executor} must run callback sequentially, otherwise the order of
- * callbacks cannot be guaranteed.
+ * callbacks cannot be guaranteed.onQosCallbackRegistered
+ * @param callback receives qos events that satisfy socketInfo
*
* @hide
*/
@SystemApi
public void registerQosCallback(@NonNull final QosSocketInfo socketInfo,
- @NonNull final QosCallback callback,
- @CallbackExecutor @NonNull final Executor executor) {
+ @CallbackExecutor @NonNull final Executor executor,
+ @NonNull final QosCallback callback) {
Objects.requireNonNull(socketInfo, "socketInfo must be non-null");
- Objects.requireNonNull(callback, "callback must be non-null");
Objects.requireNonNull(executor, "executor must be non-null");
+ Objects.requireNonNull(callback, "callback must be non-null");
try {
synchronized (mQosCallbackConnections) {
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index 3300fa8..728f375 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.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 @@
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 @@
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/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
index c5464d3..cbd6193 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.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 @@
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 0000000..67d2d405
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkRequest;
+
+/**
+ * A callback registered with connectivity by network providers together with
+ * a NetworkOffer.
+ *
+ * When the offer is needed to satisfy some application or system component,
+ * connectivity will call onOfferNeeded on this callback. When this happens,
+ * the provider should try and bring up the network.
+ *
+ * When the offer is no longer needed, for example because the application has
+ * withdrawn the request or if the request is being satisfied by a network
+ * that this offer will never be able to beat, connectivity calls
+ * onOfferUnneeded. When this happens, the provider should stop trying to
+ * bring up the network, or tear it down if it has already been brought up.
+ *
+ * When NetworkProvider#offerNetwork is called, the provider can expect to
+ * immediately receive all requests that can be fulfilled by that offer and
+ * are not already satisfied by a better network. It is possible no such
+ * request is currently outstanding, because no requests have been made that
+ * can be satisfied by this offer, or because all such requests are already
+ * satisfied by a better network.
+ * onOfferNeeded can be called at any time after registration and until the
+ * offer is withdrawn with NetworkProvider#unofferNetwork is called. This
+ * typically happens when a new network request is filed by an application,
+ * or when the network satisfying a request disconnects and this offer now
+ * stands a chance to be the best network for it.
+ *
+ * @hide
+ */
+oneway interface INetworkOfferCallback {
+ /**
+ * Informs the registrant that the offer is needed to fulfill this request.
+ * @param networkRequest the request to satisfy
+ * @param providerId the ID of the provider currently satisfying
+ * this request, or NetworkProvider.ID_NONE if none.
+ */
+ void onOfferNeeded(in NetworkRequest networkRequest, int providerId);
+
+ /**
+ * Informs the registrant that the offer is no longer needed to fulfill this request.
+ */
+ void onOfferUnneeded(in NetworkRequest networkRequest);
+}
diff --git a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
index 91c7575..c973541 100644
--- a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
+++ b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
@@ -19,6 +19,7 @@
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 @@
{
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 0741414..1f49033 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.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 @@
// 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 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 @@
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 @@
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 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 1416bb97..f7cd4f6 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.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;
@@ -362,9 +363,8 @@
public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
- // The subtype can be changed with (TODO) setLegacySubtype, but it starts
- // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description.
- final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, "");
+ final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
+ config.legacyTypeName, config.legacySubTypeName);
ni.setIsAvailable(true);
ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */,
config.getLegacyExtraInfo());
@@ -390,7 +390,6 @@
* @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,
@@ -434,7 +433,7 @@
}
mInitialConfiguration = new InitialConfiguration(context,
- new NetworkCapabilities(nc, /* parcelLocationSensitiveFields */ true),
+ new NetworkCapabilities(nc, NetworkCapabilities.REDACT_NONE),
new LinkProperties(lp), score, config, ni);
}
@@ -829,6 +828,7 @@
* @hide
*/
@Deprecated
+ @SystemApi
public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
queueOrSendNetworkInfo(mNetworkInfo);
@@ -878,8 +878,7 @@
mBandwidthUpdatePending.set(false);
mLastBwRefreshTime = System.currentTimeMillis();
final NetworkCapabilities nc =
- new NetworkCapabilities(networkCapabilities,
- /* parcelLocationSensitiveFields */ true);
+ new NetworkCapabilities(networkCapabilities, NetworkCapabilities.REDACT_NONE);
queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc));
}
@@ -963,6 +962,7 @@
* shall try to overwrite this method and produce a bandwidth update if capable.
* @hide
*/
+ @SystemApi
public void onBandwidthUpdateRequested() {
pollLceData();
}
@@ -1161,29 +1161,37 @@
/**
- * 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 5e50a64..3f058d8 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
@@ -64,6 +64,16 @@
}
/**
+ * @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.
@@ -165,6 +175,12 @@
}
/**
+ * The legacy Sub type of this network agent, or TYPE_NONE if unset.
+ * @hide
+ */
+ public int legacySubType = ConnectivityManager.TYPE_NONE;
+
+ /**
* Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
* Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
*
@@ -190,6 +206,13 @@
}
/**
+ * The name of the legacy Sub network type. It's a free-form string.
+ * @hide
+ */
+ @NonNull
+ public String legacySubTypeName = "";
+
+ /**
* The legacy extra info of the agent. The extra info should only be :
* <ul>
* <li>For cellular agents, the APN name.</li>
@@ -225,6 +248,8 @@
skip464xlat = nac.skip464xlat;
legacyType = nac.legacyType;
legacyTypeName = nac.legacyTypeName;
+ legacySubType = nac.legacySubType;
+ legacySubTypeName = nac.legacySubTypeName;
mLegacyExtraInfo = nac.mLegacyExtraInfo;
}
}
@@ -290,7 +315,6 @@
* and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableNat64Detection() {
@@ -303,7 +327,6 @@
* perform its own carrier-specific provisioning procedure.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableProvisioningNotification() {
@@ -324,6 +347,18 @@
}
/**
+ * Sets the legacy sub-type for this network.
+ *
+ * @param legacySubType the type
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubType(final int legacySubType) {
+ mConfig.legacySubType = legacySubType;
+ return this;
+ }
+
+ /**
* Sets the name of the legacy type of the agent. It's a free-form string used in logging.
* @param legacyTypeName the name
* @return this builder, to facilitate chaining.
@@ -335,10 +370,20 @@
}
/**
+ * Sets the name of the legacy Sub-type of the agent. It's a free-form string.
+ * @param legacySubTypeName the name
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubTypeName(@NonNull String legacySubTypeName) {
+ mConfig.legacySubTypeName = legacySubTypeName;
+ return this;
+ }
+
+ /**
* Sets the legacy extra info of the agent.
* @param legacyExtraInfo the legacy extra info.
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) {
@@ -347,6 +392,19 @@
}
/**
+ * 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
@@ -412,6 +470,8 @@
out.writeInt(skip464xlat ? 1 : 0);
out.writeInt(legacyType);
out.writeString(legacyTypeName);
+ out.writeInt(legacySubType);
+ out.writeString(legacySubTypeName);
out.writeString(mLegacyExtraInfo);
}
@@ -429,6 +489,8 @@
networkAgentConfig.skip464xlat = in.readInt() != 0;
networkAgentConfig.legacyType = in.readInt();
networkAgentConfig.legacyTypeName = in.readString();
+ networkAgentConfig.legacySubType = in.readInt();
+ networkAgentConfig.legacySubTypeName = in.readString();
networkAgentConfig.mLegacyExtraInfo = in.readString();
return networkAgentConfig;
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 1466629..f50b018 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -19,6 +19,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import android.annotation.IntDef;
+import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -34,7 +35,6 @@
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;
@@ -64,6 +64,68 @@
public final class NetworkCapabilities implements Parcelable {
private static final String TAG = "NetworkCapabilities";
+ /**
+ * Mechanism to support redaction of fields in NetworkCapabilities that are guarded by specific
+ * app permissions.
+ **/
+ /**
+ * Don't redact any fields since the receiving app holds all the necessary permissions.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final long REDACT_NONE = 0;
+
+ /**
+ * Redact any fields that need {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+ * permission since the receiving app does not hold this permission or the location toggle
+ * is off.
+ *
+ * @see android.Manifest.permission#ACCESS_FINE_LOCATION
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1 << 0;
+
+ /**
+ * Redact any fields that need {@link android.Manifest.permission#LOCAL_MAC_ADDRESS}
+ * permission since the receiving app does not hold this permission.
+ *
+ * @see android.Manifest.permission#LOCAL_MAC_ADDRESS
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 1 << 1;
+
+ /**
+ *
+ * Redact any fields that need {@link android.Manifest.permission#NETWORK_SETTINGS}
+ * permission since the receiving app does not hold this permission.
+ *
+ * @see android.Manifest.permission#NETWORK_SETTINGS
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final long REDACT_FOR_NETWORK_SETTINGS = 1 << 2;
+
+ /**
+ * Redact all fields in this object that require any relevant permission.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final long REDACT_ALL = -1L;
+
+ /** @hide */
+ @LongDef(flag = true, prefix = { "REDACT_" }, value = {
+ REDACT_NONE,
+ REDACT_FOR_ACCESS_FINE_LOCATION,
+ REDACT_FOR_LOCAL_MAC_ADDRESS,
+ REDACT_FOR_NETWORK_SETTINGS,
+ REDACT_ALL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RedactionType {}
+
// Set to true when private DNS is broken.
private boolean mPrivateDnsBroken;
@@ -78,32 +140,31 @@
private String mRequestorPackageName;
/**
- * Indicates whether parceling should preserve fields that are set based on permissions of
- * the process receiving the {@link NetworkCapabilities}.
+ * Indicates what fields should be redacted from this instance.
*/
- private final boolean mParcelLocationSensitiveFields;
+ private final @RedactionType long mRedactions;
public NetworkCapabilities() {
- mParcelLocationSensitiveFields = false;
+ mRedactions = REDACT_ALL;
clearAll();
mNetworkCapabilities = DEFAULT_CAPABILITIES;
}
public NetworkCapabilities(NetworkCapabilities nc) {
- this(nc, false /* parcelLocationSensitiveFields */);
+ this(nc, REDACT_ALL);
}
/**
* Make a copy of NetworkCapabilities.
*
* @param nc Original NetworkCapabilities
- * @param parcelLocationSensitiveFields Whether to parcel location sensitive data or not.
+ * @param redactions bitmask of redactions that needs to be performed on this new instance of
+ * {@link NetworkCapabilities}.
* @hide
*/
- @SystemApi
- public NetworkCapabilities(
- @Nullable NetworkCapabilities nc, boolean parcelLocationSensitiveFields) {
- mParcelLocationSensitiveFields = parcelLocationSensitiveFields;
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public NetworkCapabilities(@Nullable NetworkCapabilities nc, @RedactionType long redactions) {
+ mRedactions = redactions;
if (nc != null) {
set(nc);
}
@@ -115,11 +176,13 @@
* @hide
*/
public void clearAll() {
- // Ensures that the internal copies maintained by the connectivity stack does not set
- // this bit.
- if (mParcelLocationSensitiveFields) {
+ // Ensures that the internal copies maintained by the connectivity stack does not set it to
+ // anything other than |REDACT_ALL|.
+ if (mRedactions != REDACT_ALL) {
+ // This is needed because the current redaction mechanism relies on redaction while
+ // parceling.
throw new UnsupportedOperationException(
- "Cannot clear NetworkCapabilities when parcelLocationSensitiveFields is set");
+ "Cannot clear NetworkCapabilities when mRedactions is set");
}
mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0;
mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
@@ -149,7 +212,7 @@
mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
mNetworkSpecifier = nc.mNetworkSpecifier;
if (nc.getTransportInfo() != null) {
- setTransportInfo(nc.getTransportInfo().makeCopy(mParcelLocationSensitiveFields));
+ setTransportInfo(nc.getTransportInfo().makeCopy(mRedactions));
} else {
setTransportInfo(null);
}
@@ -474,43 +537,6 @@
| (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 =
@@ -575,19 +601,31 @@
}
/**
- * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+ * Removes (if found) the given capability from this {@code NetworkCapability}
+ * instance that were added via addCapability(int) or setCapabilities(int[], int[]).
*
* @param capability the capability to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
- // Note that this method removes capabilities that were added via addCapability(int),
- // addUnwantedCapability(int) or setCapabilities(int[], int[]).
checkValidCapability(capability);
final long mask = ~(1 << capability);
mNetworkCapabilities &= mask;
- mUnwantedNetworkCapabilities &= mask;
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given unwanted capability from this {@code NetworkCapability}
+ * instance that were added via addUnwantedCapability(int) or setCapabilities(int[], int[]).
+ *
+ * @param capability the capability to be removed.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) {
+ checkValidCapability(capability);
+ mUnwantedNetworkCapabilities &= ~(1 << capability);
return this;
}
@@ -659,6 +697,7 @@
}
/** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public boolean hasUnwantedCapability(@NetCapability int capability) {
return isValidCapability(capability)
&& ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
@@ -672,10 +711,33 @@
return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
}
- /** Note this method may result in having the same capability in wanted and unwanted lists. */
+ /**
+ * 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) {
- this.mNetworkCapabilities |= nc.mNetworkCapabilities;
- this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities;
+ final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
+ final long unwantedCaps =
+ this.mUnwantedNetworkCapabilities | nc.mUnwantedNetworkCapabilities;
+ if ((wantedCaps & unwantedCaps) != 0) {
+ throw new IllegalArgumentException(
+ "Cannot have the same capability in wanted and unwanted lists.");
+ }
+ this.mNetworkCapabilities = wantedCaps;
+ this.mUnwantedNetworkCapabilities = unwantedCaps;
}
/**
@@ -728,37 +790,12 @@
}
/**
- * 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);
}
}
@@ -2004,34 +2041,6 @@
}
}
- /** @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
*/
@@ -2350,6 +2359,23 @@
}
/**
+ * Returns a bitmask of all the applicable redactions (based on the permissions held by the
+ * receiving app) to be performed on this object.
+ *
+ * @return bitmask of redactions applicable on this instance.
+ * @hide
+ */
+ public @RedactionType long getApplicableRedactions() {
+ // Currently, there are no fields redacted in NetworkCapabilities itself, so we just
+ // passthrough the redactions required by the embedded TransportInfo. If this changes
+ // in the future, modify this method.
+ if (mTransportInfo == null) {
+ return NetworkCapabilities.REDACT_NONE;
+ }
+ return mTransportInfo.getApplicableRedactions();
+ }
+
+ /**
* Builder class for NetworkCapabilities.
*
* This class is mainly for for {@link NetworkAgent} instances to use. Many fields in
diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
index 14cb51c..d5b5c9b 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.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 @@
*/
@SystemApi
public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) {
- Handler handler = new Handler(looper) {
+ // TODO (b/174636568) : this class should be able to cache an instance of
+ // ConnectivityManager so it doesn't have to fetch it again every time.
+ final Handler handler = new Handler(looper) {
@Override
public void handleMessage(Message m) {
switch (m.what) {
@@ -159,4 +166,152 @@
public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request);
}
+
+ /** @hide */
+ // TODO : make @SystemApi when the impl is complete
+ public interface NetworkOfferCallback {
+ /** Called by the system when this offer is needed to satisfy some networking request. */
+ void onOfferNeeded(@NonNull NetworkRequest request, int providerId);
+ /** Called by the system when this offer is no longer needed. */
+ void onOfferUnneeded(@NonNull NetworkRequest request);
+ }
+
+ private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
+ @NonNull public final NetworkOfferCallback callback;
+ @NonNull private final Executor mExecutor;
+
+ NetworkOfferCallbackProxy(@NonNull final NetworkOfferCallback callback,
+ @NonNull final Executor executor) {
+ this.callback = callback;
+ this.mExecutor = executor;
+ }
+
+ @Override
+ public void onOfferNeeded(final @NonNull NetworkRequest request,
+ final int providerId) {
+ mExecutor.execute(() -> callback.onOfferNeeded(request, providerId));
+ }
+
+ @Override
+ public void onOfferUnneeded(final @NonNull NetworkRequest request) {
+ mExecutor.execute(() -> callback.onOfferUnneeded(request));
+ }
+ }
+
+ @GuardedBy("mProxies")
+ @NonNull private final ArrayList<NetworkOfferCallbackProxy> mProxies = new ArrayList<>();
+
+ // Returns the proxy associated with this callback, or null if none.
+ @Nullable
+ private NetworkOfferCallbackProxy findProxyForCallback(@NonNull final NetworkOfferCallback cb) {
+ synchronized (mProxies) {
+ for (final NetworkOfferCallbackProxy p : mProxies) {
+ if (p.callback == cb) return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Register or update an offer for network with the passed caps and score.
+ *
+ * A NetworkProvider's job is to provide networks. This function is how a provider tells the
+ * connectivity stack what kind of network it may provide. The score and caps arguments act
+ * as filters that the connectivity stack uses to tell when the offer is necessary. When an
+ * offer might be advantageous over existing networks, the provider will receive a call to
+ * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider
+ * should then try to bring up this network. When an offer is no longer needed, the stack
+ * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The
+ * provider should stop trying to bring up such a network, or disconnect it if it already has
+ * one.
+ *
+ * The stack determines what offers are needed according to what networks are currently
+ * available to the system, and what networking requests are made by applications. If an
+ * offer looks like it could be a better choice than any existing network for any particular
+ * request, that's when the stack decides the offer is needed. If the current networking
+ * requests are all satisfied by networks that this offer can't possibly be a better match
+ * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider
+ * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded}
+ * is called.
+ *
+ * Note that the offers are non-binding to the providers, in particular because providers
+ * often don't know if they will be able to bring up such a network at any given time. For
+ * example, no wireless network may be in range when the offer is needed. This is fine and
+ * expected ; the provider should simply continue to try to bring up the network and do so
+ * if/when it becomes possible. In the mean time, the stack will continue to satisfy requests
+ * with the best network currently available, or if none, keep the apps informed that no
+ * network can currently satisfy this request. When/if the provider can bring up the network,
+ * the connectivity stack will match it against requests, and inform interested apps of the
+ * availability of this network. This may, in turn, render the offer of some other provider
+ * unneeded if all requests it used to satisfy are now better served by this network.
+ *
+ * A network can become unneeded for a reason like the above : whether the provider managed
+ * to bring up the offered network after it became needed or not, some other provider may
+ * bring up a better network than this one, making this offer unneeded. A network may also
+ * become unneeded if the application making the request withdrew it (for example, after it
+ * is done transferring data, or if the user canceled an operation).
+ *
+ * The capabilities and score act as filters as to what requests the provider will see.
+ * They are not promises, but for best performance, the providers should strive to put
+ * as much known information as possible in the offer. For capabilities in particular, it
+ * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't
+ * have them at first. This applies to INTERNET, for example ; if a provider thinks the
+ * network it can bring up for this offer may offer Internet access it should include the
+ * INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET.
+ *
+ * TODO : in the future, to avoid possible infinite loops, there should be constraints on
+ * what can be put in capabilities of networks brought up for an offer. If a provider might
+ * bring up a network with or without INTERNET, then it should file two offers : this will
+ * let it know precisely what networks are needed, so it can avoid bringing up networks that
+ * won't actually satisfy requests and remove the risk for bring-up-bring-down loops.
+ *
+ * @hide
+ */
+ // TODO : make @SystemApi when the impl is complete
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public void offerNetwork(@NonNull final NetworkScore score,
+ @NonNull final NetworkCapabilities caps, @NonNull final Executor executor,
+ @NonNull final NetworkOfferCallback callback) {
+ // Can't offer a network with a provider that is not yet registered or already unregistered.
+ final int providerId = mProviderId;
+ if (providerId == ID_NONE) return;
+ NetworkOfferCallbackProxy proxy = null;
+ synchronized (mProxies) {
+ for (final NetworkOfferCallbackProxy existingProxy : mProxies) {
+ if (existingProxy.callback == callback) {
+ proxy = existingProxy;
+ break;
+ }
+ }
+ if (null == proxy) {
+ proxy = new NetworkOfferCallbackProxy(callback, executor);
+ mProxies.add(proxy);
+ }
+ }
+ mContext.getSystemService(ConnectivityManager.class)
+ .offerNetwork(providerId, score, caps, proxy);
+ }
+
+ /**
+ * Withdraw a network offer previously made to the networking stack.
+ *
+ * If a provider can no longer provide a network they offered, it should call this method.
+ * An example of usage could be if the hardware necessary to bring up the network was turned
+ * off in UI by the user. Note that because offers are never binding, the provider might
+ * alternatively decide not to withdraw this offer and simply refuse to bring up the network
+ * even when it's needed. However, withdrawing the request is slightly more resource-efficient
+ * because the networking stack won't have to compare this offer to exiting networks to see
+ * if it could beat any of them, and may be advantageous to the provider's implementation that
+ * can rely on no longer receiving callbacks for a network that they can't bring up anyways.
+ *
+ * @hide
+ */
+ // TODO : make @SystemApi when the impl is complete
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public void unofferNetwork(final @NonNull NetworkOfferCallback callback) {
+ final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback);
+ if (null == proxy) return;
+ mProxies.remove(proxy);
+ mContext.getSystemService(ConnectivityManager.class).unofferNetwork(proxy);
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index cf131f0..5313f08 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.Process;
import android.text.TextUtils;
import android.util.Range;
-import android.util.proto.ProtoOutputStream;
import java.util.Arrays;
import java.util.List;
@@ -216,6 +215,14 @@
}
/**
+ * Creates a new Builder of NetworkRequest from an existing instance.
+ */
+ public Builder(@NonNull final NetworkRequest request) {
+ Objects.requireNonNull(request);
+ mNetworkCapabilities = request.networkCapabilities;
+ }
+
+ /**
* Build {@link NetworkRequest} give the current set of capabilities.
*/
public NetworkRequest build() {
@@ -305,12 +312,31 @@
*
* @hide
*/
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addUnwantedCapability(capability);
return this;
}
/**
+ * Removes (if found) the given unwanted capability from this builder instance.
+ *
+ * @param capability The unwanted capability to remove.
+ * @return The builder to facilitate chaining.
+ *
+ * @hide
+ */
+ @NonNull
+ @SuppressLint("BuilderSetStyle")
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public Builder removeUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
+ mNetworkCapabilities.removeUnwantedCapability(capability);
+ return this;
+ }
+
+ /**
* Completely clears all the {@code NetworkCapabilities} from this builder instance,
* removing even the capabilities that are set by default when the object is constructed.
*
@@ -567,6 +593,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public boolean hasUnwantedCapability(@NetCapability int capability) {
return networkCapabilities.hasUnwantedCapability(capability);
}
@@ -647,18 +674,6 @@
}
}
- /** @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;
@@ -671,4 +686,43 @@
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 eadcb2d..6584993 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 @@
* 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 @@
/**
* @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 c0f2628..16a49bc 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.net.ConnectivityManager.NETID_UNSET;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.system.ErrnoException;
@@ -55,6 +57,8 @@
*/
public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
+ private static native boolean bindProcessToNetworkHandle(long netHandle);
+
/**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
@@ -63,13 +67,20 @@
* is by design so an application doesn't accidentally use sockets it thinks are still bound to
* a particular {@code Network}. Passing NETID_UNSET clears the binding.
*/
- public native static boolean bindProcessToNetwork(int netId);
+ public static boolean bindProcessToNetwork(int netId) {
+ return bindProcessToNetworkHandle(new Network(netId).getNetworkHandle());
+ }
+
+ private static native long getBoundNetworkHandleForProcess();
/**
* Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
* {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
*/
- public native static int getBoundNetworkForProcess();
+ public static int getBoundNetworkForProcess() {
+ final long netHandle = getBoundNetworkHandleForProcess();
+ return netHandle == 0L ? NETID_UNSET : Network.fromNetworkHandle(netHandle).getNetId();
+ }
/**
* Binds host resolutions performed by this process to the network designated by {@code netId}.
@@ -81,18 +92,28 @@
@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.
@@ -100,8 +121,13 @@
* {@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.
@@ -110,8 +136,11 @@
* {@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.
@@ -323,22 +352,7 @@
*/
@UnsupportedAppUsage
public static String trimV4AddrZeros(String addr) {
- if (addr == null) return null;
- String[] octets = addr.split("\\.");
- if (octets.length != 4) return addr;
- StringBuilder builder = new StringBuilder(16);
- String result = null;
- for (int i = 0; i < 4; i++) {
- try {
- if (octets[i].length() > 3) return addr;
- builder.append(Integer.parseInt(octets[i]));
- } catch (NumberFormatException e) {
- return addr;
- }
- if (i < 3) builder.append('.');
- }
- result = builder.toString();
- return result;
+ return Inet4AddressUtils.trimAddressZeros(addr);
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
index bdb4ad6..de0fc24 100644
--- a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
+++ b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
@@ -19,6 +19,7 @@
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 @@
}
/**
+ * 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 4f3bb77..93f2ff2 100644
--- a/packages/Connectivity/framework/src/android/net/QosSession.java
+++ b/packages/Connectivity/framework/src/android/net/QosSession.java
@@ -36,6 +36,11 @@
*/
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 @@
*/
@IntDef(value = {
TYPE_EPS_BEARER,
+ TYPE_NR_BEARER,
})
@interface QosSessionType {}
diff --git a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
index d007a95..f6cae72 100644
--- a/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
+++ b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
@@ -55,36 +55,68 @@
static final String TAG = "SocketKeepalive";
/**
- * No errors.
+ * Success. It indicates there is no error.
* @hide
*/
@SystemApi
public static final int SUCCESS = 0;
- /** @hide */
+ /**
+ * No keepalive. This should only be internally as it indicates There is no keepalive.
+ * It should not propagate to applications.
+ * @hide
+ */
public static final int NO_KEEPALIVE = -1;
- /** @hide */
+ /**
+ * Data received.
+ * @hide
+ */
public static final int DATA_RECEIVED = -2;
- /** @hide */
+ /**
+ * The binder died.
+ * @hide
+ */
public static final int BINDER_DIED = -10;
- /** The specified {@code Network} is not connected. */
+ /**
+ * The invalid network. It indicates the specified {@code Network} is not connected.
+ */
public static final int ERROR_INVALID_NETWORK = -20;
- /** The specified IP addresses are invalid. For example, the specified source IP address is
- * not configured on the specified {@code Network}. */
+
+ /**
+ * The invalid IP addresses. Indicates the specified IP addresses are invalid.
+ * For example, the specified source IP address is not configured on the
+ * specified {@code Network}.
+ */
public static final int ERROR_INVALID_IP_ADDRESS = -21;
- /** The requested port is invalid. */
+
+ /**
+ * The port is invalid.
+ */
public static final int ERROR_INVALID_PORT = -22;
- /** The packet length is invalid (e.g., too long). */
+
+ /**
+ * The length is invalid (e.g. too long).
+ */
public static final int ERROR_INVALID_LENGTH = -23;
- /** The packet transmission interval is invalid (e.g., too short). */
+
+ /**
+ * The interval is invalid (e.g. too short).
+ */
public static final int ERROR_INVALID_INTERVAL = -24;
- /** The target socket is invalid. */
+
+ /**
+ * The socket is invalid.
+ */
public static final int ERROR_INVALID_SOCKET = -25;
- /** The target socket is not idle. */
+
+ /**
+ * The socket is not idle.
+ */
public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
/**
* The stop reason is uninitialized. This should only be internally used as initial state
* of stop reason, instead of propagating to application.
@@ -92,15 +124,29 @@
*/
public static final int ERROR_STOP_REASON_UNINITIALIZED = -27;
- /** The device does not support this request. */
+ /**
+ * The request is unsupported.
+ */
public static final int ERROR_UNSUPPORTED = -30;
- /** @hide TODO: delete when telephony code has been updated. */
- public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
- /** The hardware returned an error. */
+
+ /**
+ * There was a hardware error.
+ */
public static final int ERROR_HARDWARE_ERROR = -31;
- /** The limitation of resource is reached. */
+
+ /**
+ * Resources are insufficient (e.g. all hardware slots are in use).
+ */
public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
+ /**
+ * There was no such slot. This should only be internally as it indicates
+ * a programming error in the system server. It should not propagate to
+ * applications.
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_NO_SUCH_SLOT = -33;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -111,7 +157,8 @@
ERROR_INVALID_LENGTH,
ERROR_INVALID_INTERVAL,
ERROR_INVALID_SOCKET,
- ERROR_SOCKET_NOT_IDLE
+ ERROR_SOCKET_NOT_IDLE,
+ ERROR_NO_SUCH_SLOT
})
public @interface ErrorCode {}
@@ -122,7 +169,6 @@
ERROR_INVALID_LENGTH,
ERROR_UNSUPPORTED,
ERROR_INSUFFICIENT_RESOURCES,
- ERROR_HARDWARE_UNSUPPORTED
})
public @interface KeepaliveEvent {}
diff --git a/packages/Connectivity/framework/src/android/net/TransportInfo.java b/packages/Connectivity/framework/src/android/net/TransportInfo.java
index aa4bbb0..fa889ea 100644
--- a/packages/Connectivity/framework/src/android/net/TransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/TransportInfo.java
@@ -29,35 +29,47 @@
public interface TransportInfo {
/**
- * Create a copy of a {@link TransportInfo} that will preserve location sensitive fields that
- * were set based on the permissions of the process that originally received it.
+ * Create a copy of a {@link TransportInfo} with some fields redacted based on the permissions
+ * held by the receiving app.
*
- * <p>By default {@link TransportInfo} does not preserve such fields during parceling, as
- * they should not be shared outside of the process that receives them without appropriate
- * checks.
+ * <p>
+ * Usage by connectivity stack:
+ * <ul>
+ * <li> Connectivity stack will invoke {@link #getApplicableRedactions()} to find the list
+ * of redactions that are required by this {@link TransportInfo} instance.</li>
+ * <li> Connectivity stack then loops through each bit in the bitmask returned and checks if the
+ * receiving app holds the corresponding permission.
+ * <ul>
+ * <li> If the app holds the corresponding permission, the bit is cleared from the
+ * |redactions| bitmask. </li>
+ * <li> If the app does not hold the corresponding permission, the bit is retained in the
+ * |redactions| bitmask. </li>
+ * </ul>
+ * <li> Connectivity stack then invokes {@link #makeCopy(long)} with the necessary |redactions|
+ * to create a copy to send to the corresponding app. </li>
+ * </ul>
+ * </p>
*
- * @param parcelLocationSensitiveFields Whether the location sensitive fields should be kept
- * when parceling
- * @return Copy of this instance.
+ * @param redactions bitmask of redactions that needs to be performed on this instance.
+ * @return Copy of this instance with the necessary redactions.
* @hide
*/
- @SystemApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@NonNull
- default TransportInfo makeCopy(boolean parcelLocationSensitiveFields) {
+ default TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) {
return this;
}
/**
- * Returns whether this TransportInfo type has location sensitive fields or not (helps
- * to determine whether to perform a location permission check or not before sending to
- * apps).
+ * Returns a bitmask of all the applicable redactions (based on the permissions held by the
+ * receiving app) to be performed on this TransportInfo.
*
- * @return {@code true} if this instance contains location sensitive info, {@code false}
- * otherwise.
+ * @return bitmask of redactions applicable on this instance.
+ * @see #makeCopy(long)
* @hide
*/
- @SystemApi
- default boolean hasLocationSensitiveFields() {
- return false;
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ default @NetworkCapabilities.RedactionType long getApplicableRedactions() {
+ return NetworkCapabilities.REDACT_NONE;
}
}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index f630cea..b44128b 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -25,7 +25,7 @@
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 @@
"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,39 +51,69 @@
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",
+ },
+ srcs: [
+ ":system-messages-proto-src",
+ ],
+ libs: ["libprotobuf-java-nano"],
+ apex_available: [
"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 fa4501a..d783738 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
+++ b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
@@ -22,6 +22,7 @@
android_app {
name: "ServiceConnectivityResources",
sdk_version: "module_current",
+ min_sdk_version: "30",
resource_dirs: [
"res",
],
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml
index 71674e4..9ff2a22 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml
+++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml
@@ -78,6 +78,11 @@
<item>1,3</item>
</string-array>
+ <!-- Reserved privileged keepalive slots per transport. -->
+ <integer translatable="false" name="config_reservedPrivilegedKeepaliveSlots">2</integer>
+
+ <!-- Allowed unprivileged keepalive slots per uid. -->
+ <integer translatable="false" name="config_allowedUnprivilegedKeepalivePerUid">2</integer>
<!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual
device behaviour is controlled by the metered multipath preference in
@@ -89,4 +94,33 @@
Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
<integer translatable="false" name="config_networkAvoidBadWifi">1</integer>
+ <!-- Array of ConnectivityManager.TYPE_xxxx constants for networks that may only
+ be controlled by systemOrSignature apps. -->
+ <integer-array translatable="false" name="config_protectedNetworks">
+ <item>10</item>
+ <item>11</item>
+ <item>12</item>
+ <item>14</item>
+ <item>15</item>
+ </integer-array>
+
+ <!-- Whether the internal vehicle network should remain active even when no
+ apps requested it. -->
+ <bool name="config_vehicleInternalNetworkAlwaysRequested">false</bool>
+
+
+ <!-- If the hardware supports specially marking packets that caused a wakeup of the
+ main CPU, set this value to the mark used. -->
+ <integer name="config_networkWakeupPacketMark">0</integer>
+
+ <!-- Mask to use when checking skb mark defined in config_networkWakeupPacketMark above. -->
+ <integer name="config_networkWakeupPacketMask">0</integer>
+
+ <!-- Whether/how to notify the user on network switches. See LingerMonitor.java. -->
+ <integer translatable="false" name="config_networkNotifySwitchType">0</integer>
+
+ <!-- What types of network switches to notify. See LingerMonitor.java. -->
+ <string-array translatable="false" name="config_networkNotifySwitches">
+ </string-array>
+
</resources>
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml
index 25e19ce..717d08e 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -26,6 +26,12 @@
<item type="integer" name="config_networkMeteredMultipathPreference"/>
<item type="array" name="config_networkSupportedKeepaliveCount"/>
<item type="integer" name="config_networkAvoidBadWifi"/>
+ <item type="array" name="config_protectedNetworks"/>
+ <item type="bool" name="config_vehicleInternalNetworkAlwaysRequested"/>
+ <item type="integer" name="config_networkWakeupPacketMark"/>
+ <item type="integer" name="config_networkWakeupPacketMask"/>
+ <item type="integer" name="config_networkNotifySwitchType"/>
+ <item type="array" name="config_networkNotifySwitches"/>
</policy>
</overlayable>
diff --git a/packages/Connectivity/service/jarjar-rules.txt b/packages/Connectivity/service/jarjar-rules.txt
index a7b419b..5caa11b 100644
--- a/packages/Connectivity/service/jarjar-rules.txt
+++ b/packages/Connectivity/service/jarjar-rules.txt
@@ -12,3 +12,6 @@
# the one in com.android.internal.util
rule android.util.IndentingPrintWriter* android.connectivity.util.IndentingPrintWriter@1
rule com.android.internal.util.** com.android.connectivity.util.@1
+
+rule com.android.internal.messages.** com.android.connectivity.messages.@1
+rule com.google.protobuf.** com.android.connectivity.protobuf.@1
diff --git a/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp b/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp
index 36a6fde..e7a40e5 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 @@
//------------------------------------------------------------------------------
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/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/packages/Connectivity/service/proto/connectivityproto.proto
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to packages/Connectivity/service/proto/connectivityproto.proto
index cab5ad2..a992d7c 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/packages/Connectivity/service/proto/connectivityproto.proto
@@ -14,17 +14,7 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+syntax = "proto2";
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+// Connectivity protos can be created in this directory. Note this file must be included before
+// building system-messages-proto, otherwise it will not build by itself.
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 7cc5994..c4c60ea 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -151,20 +151,14 @@
}
@Override
- public void onInitialize(boolean allowed, ProviderProperties properties, String packageName,
- String attributionTag) {
-
- }
+ public void onInitialize(boolean allowed, ProviderProperties properties,
+ String attributionTag) {}
@Override
- public void onSetAllowed(boolean allowed) {
-
- }
+ public void onSetAllowed(boolean allowed) {}
@Override
- public void onSetProperties(ProviderProperties properties) {
-
- }
+ public void onSetProperties(ProviderProperties properties) {}
@Override
public void onReportLocation(Location location) {
@@ -177,9 +171,7 @@
}
@Override
- public void onFlushComplete() {
-
- }
+ public void onFlushComplete() {}
public Location getNextLocation(long timeoutMs) throws InterruptedException {
return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index d6c66b5..5e69a4e 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -17,8 +17,8 @@
// 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 @@
resource_dirs: ["res"],
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
min_sdk_version: "21",
@@ -68,6 +71,7 @@
"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 8c208e35..0390e86 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 231babe..dd9fc2c 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -23,5 +23,6 @@
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 0000000..24d53ab
--- /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 e376930..c799b99 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 c799b99..0000000
--- 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 e1a64d4..1157a34 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 ad94cd03..957bac7 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.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 @@
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 0000000..c4c74ff
--- /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 5496a01..7a550ae 100644
--- a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
+++ b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
@@ -23,6 +23,7 @@
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
android:clipToPadding="false">
<LinearLayout
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml
new file mode 100644
index 0000000..5567790
--- /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 4a1b089..0000000
--- 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 0000000..87977bd
--- /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 0000000..3a289a7
--- /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 0000000..acf06c4
--- /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 0000000..17b6805
--- /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 0000000..a3bb1da
--- /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 9485655..009ae6b 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 a6623b0..f24e008 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 6ca8f577..17596ac2 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 0000000..36ca684
--- /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 0000000..c12f6f7
--- /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 0000000..11660a5f
--- /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
similarity index 79%
rename from packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml
rename to packages/SettingsLib/SettingsTransition/res/values/dimens.xml
index 4f40256..0630ca8 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 0000000..f99fda0
--- /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 9130662..17f257d 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 @@
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 @@
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-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index d3e4d1c..1c112c6 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -211,7 +211,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Wi-Fi ሲገናኝ የማረም ሁነታ"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"ስህተት"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"ገመድ-አልባ debugging"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ debuggingን ያብሩ"</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"የሚገኙ መሣሪያዎችን ለመመልከትና ለመጠቀም ገመድ-አልባ ማረምን ያብሩ"</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"የQR ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"የQR ኮድ መቃኛን በመጠቀም አዲስ መሣሪያዎችን ያጣምሩ"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"የማጣመሪያ ኮድን በመጠቀም መሣሪያን ያጣምሩ"</string>
@@ -303,7 +303,7 @@
<string name="adb_warning_title" msgid="7708653449506485728">"የUSB ማረሚያ ይፈቀድ?"</string>
<string name="adb_warning_message" msgid="8145270656419669221">"የUSB አድስ ለግንባታ አላማ ብቻ የታሰበ ነው። ከኮምፒዩተርህ ወደ መሳሪያህ ውሂብ ለመገልበጥ፣ መሣሪያህ ላይ ያለ ማሳወቂያ መተግበሪያዎችን መጫን፣ እና ማስታወሻ ውሂብ ማንበብ ለመጠቀም ይቻላል።"</string>
<string name="adbwifi_warning_title" msgid="727104571653031865">"ገመድ-አልባ debugging ይፈቀድ?"</string>
- <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ debugging ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string>
+ <string name="adbwifi_warning_message" msgid="8005936574322702388">"ገመድ-አልባ ማረም ለግንባታ አላማዎች ብቻ የታሰበ ነው። ውሂብን ከኮምፒዩተርዎ ወደ መሳሪያዎ ለመቅዳት፣ መሣሪያዎ ላይ ያለማሳወቂያ መተግበሪያዎችን ለመጫን እና የምዝግብ ማስታወሻ ውሂብን ለማንበብ ይጠቀሙበት።"</string>
<string name="adb_keys_warning_message" msgid="2968555274488101220">"የዩ ኤስ ቢ ማረም መዳረሻ ከዚህ ቀደም ፍቃድ ከሰጧቸው ኮምፒውተሮች ላይ ይሻሩ?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"የግንባታ ቅንብሮችን ፍቀድ?"</string>
<string name="dev_settings_warning_message" msgid="37741686486073668">"እነዚህ ቅንብሮች የታሰቡት ለግንባታ አጠቃቀም ብቻ ናቸው። መሳሪያህን እና በሱ ላይ ያሉትን መተግበሪያዎች እንዲበለሹ ወይም በትክክል እንዳይሰሩ ሊያደርጉ ይችላሉ።"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index a0bf4b1..765f31c 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (Rot-Grün-Sehschwäche)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (Blau-Gelb-Sehschwäche)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Farbkorrektur"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Hier kannst du anpassen, wie Farben auf deinem Gerät dargestellt werden sollen. Das kann in folgenden Fällen hilfreich sein:<br/><br/> <ol> <li>&nbsp;Wenn Farben genauer dargestellt werden sollen</li> <li>&nbsp;Wenn du Farben entfernen möchtest, um dich besser konzentrieren zu können</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index a18c0f1..b5da6be 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Doitu nola bistaratzen diren koloreak gailuan. Kasu hauetan izan daiteke lagungarria:<br/><br/> <ol> <li>&nbsp;Koloreak zehatzago ikusi nahi dituzunean.</li> <li>&nbsp;Hobeto fokuratzeko, koloreak kendu nahi dituzunean.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index fc788b6..a351ca0 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu-jaune)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction des couleurs"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Ajustez l\'affichage des couleurs sur votre appareil. Cette option peut vous être utile pour :<br/><br/> <ol> <li>&nbsp;Accentuer la précision des couleurs</li> <li>&nbsp;Supprimer les couleurs pour mieux vous concentrer</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 90df3b1..23baeb9 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -131,7 +131,7 @@
<string name="bluetooth_hearingaid_left_pairing_message" msgid="8561855779703533591">"מתבצעת התאמה של מכשיר שמיעה שמאלי…"</string>
<string name="bluetooth_hearingaid_right_pairing_message" msgid="2655347721696331048">"מתבצעת התאמה של מכשיר שמיעה ימני…"</string>
<string name="bluetooth_hearingaid_left_battery_level" msgid="7375621694748104876">"שמאלי - טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
- <string name="bluetooth_hearingaid_right_battery_level" msgid="1850094448499089312">"ימני - טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
+ <string name="bluetooth_hearingaid_right_battery_level" msgid="1850094448499089312">"ימני – טעינת הסוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
<string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi כבוי."</string>
<string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi מנותק."</string>
<string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"פס אחד של Wi-Fi."</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index e2b62a4..6c6a5d9 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ಪ್ರೊಟನೋಮಲಿ (ಕೆಂಪು-ಹಸಿರು)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ಟ್ರಿಟನೋಮಲಿ (ನೀಲಿ-ಹಳದಿ)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಬಣ್ಣಗಳು ಹೇಗೆ ಡಿಸ್ಪ್ಲೇ ಆಗುತ್ತವೆ ಎಂಬುದನ್ನು ಹೊಂದಿಸಿ. ನೀವು ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ನೋಡಲು ಬಯಸಿದಾಗ:<br/><br/> <ol> <li>&nbsp;ಇದು ಸಹಾಯಕವಾಗಿರುತ್ತದೆ</li> <li>&nbsp;ನಿಮಗೆ ಗಮನಹರಿಸಲು ಸಹಾಯ ಮಾಡಲು ಬಣ್ಣಗಳನ್ನು ತೆಗೆದುಹಾಕಿ</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 2b351bc..7ec137a 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"प्रोटानेमली (रातो, हरियो)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ट्रिटानोमेली (निलो-पंहेलो)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रङ्ग सुधार"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"तपाईंको यन्त्रमा रङहरू कस्ता देखिन्छन् भन्ने कुरा मिलाउनुहोस्। यो सुविधा निम्न अवस्थामा उपयोगी हुन सक्छ:<br/><br/> <ol> <li>&nbsp;तपाईं अझ सटीक रूपमा रङहरू देख्न चाहनुहुन्छ भने</li> <li>&nbsp;तपाईं कुनै कुरामा ध्यान केन्द्रित गर्न रङहरू हटाउन चाहनुहुन्छ भने</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/arrays.xml b/packages/SettingsLib/res/values-nl/arrays.xml
index b5d064e..86b34f54 100644
--- a/packages/SettingsLib/res/values-nl/arrays.xml
+++ b/packages/SettingsLib/res/values-nl/arrays.xml
@@ -248,8 +248,8 @@
</string-array>
<string-array name="debug_hw_overdraw_entries">
<item msgid="1968128556747588800">"Uit"</item>
- <item msgid="3033215374382962216">"Gedeeltes met overbelasting weergeven"</item>
- <item msgid="3474333938380896988">"Gebieden voor deuteranomalie weergeven"</item>
+ <item msgid="3033215374382962216">"Gedeelten met overbelasting tonen"</item>
+ <item msgid="3474333938380896988">"Gebieden voor deuteranomalie tonen"</item>
</string-array>
<string-array name="app_process_limit_entries">
<item msgid="794656271086646068">"Standaardlimiet"</item>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index a3a490d..e5e03e1 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -309,7 +309,7 @@
<string name="dev_settings_warning_message" msgid="37741686486073668">"Deze instellingen zijn uitsluitend bedoeld voor ontwikkelingsgebruik. Je apparaat en apps kunnen hierdoor vastlopen of anders reageren."</string>
<string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Apps verifiëren via USB"</string>
<string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Apps die zijn geïnstalleerd via ADB/ADT, controleren op schadelijk gedrag"</string>
- <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-apparaten zonder namen (alleen MAC-adressen) worden weergegeven"</string>
+ <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth-apparaten zonder naam (alleen MAC-adressen) worden weergegeven"</string>
<string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Hiermee wordt de functie voor absoluut volume van Bluetooth uitgeschakeld in geval van volumeproblemen met externe apparaten, zoals een onacceptabel hoog volume of geen volumeregeling."</string>
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Hierdoor wordt de Gabeldorsche-functiestack voor bluetooth ingeschakeld."</string>
<string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Hiermee wordt de functie voor verbeterde connectiviteit ingeschakeld."</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index f1d4a8a..9ccfb80 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ਲਾਲ-ਹਰਾ)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ਨੀਲਾ-ਪੀਲਾ)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ਰੰਗ ਸੁਧਾਈ"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"ਆਪਣੇ ਡੀਵਾਈਸ \'ਤੇ ਰੰਗਾਂ ਨੂੰ ਦਿਖਾਉਣ ਦੇ ਤਰੀਕੇ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਉਦੋਂ ਲਾਹੇਵੰਦ ਹੋ ਸਕਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਇਹ ਕਰਨਾ ਚਾਹੋਗੇ:<br/><br/> <ol> <li>&nbsp;ਰੰਗਾਂ ਨੂੰ ਹੋਰ ਸਟੀਕਤਾ ਨਾਲ ਦੇਖਣਾ</li> <li>&nbsp;ਫੋਕਸ ਕਰਨ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਲਈ ਰੰਗਾਂ ਨੂੰ ਹਟਾਉਣਾ</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 95ba023..f667c6a 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (e kuqe - e gjelbër)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (e kaltër - e verdhë)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korrigjimi i ngjyrës"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (2333641630205214702) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"Rregullo mënyrën se si shfaqen ngjyrat në pajisjen tënde. Kjo mund të jetë e dobishme kur dëshiron që:<br/><br/> <ol> <li>&nbsp;T\'i shikosh ngjyrat me më shumë saktësi</li> <li>&nbsp;T\'i heqësh ngjyrat për të të ndihmuar të fokusohesh</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 0b03d99..4c35d2f 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -424,7 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (سرخ سبز)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (نیلا پیلا)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"رنگ کی اصلاح"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"آپ کے آلے پر رنگوں کے ڈسپلے ہونے کے طریقے کو ایڈجسٹ کریں۔ یہ درج ذیل کے لیے مددگار ثابت ہوسکتا ہے :<br/>&ltlt;br/> <ol><li>جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں </li> <li>&nbsp;فوکس کرنے میں مدد کرنے کے لئے رنگوں کو ہٹائیں </li> </ol>"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="2333641630205214702">"آپ کے آلے پر رنگوں کے ڈسپلے ہونے کے طریقے کو ایڈجسٹ کریں۔ یہ درج ذیل کے لیے مددگار ثابت ہوسکتا ہے :<br/>&ltlt;br/> <ol><li>جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں </li> <li>&nbsp;فوکس کرنے میں مدد کرنے کے لئے رنگوں کو ہٹانا چاہیں </li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index d801f1b..927e9db 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/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index dfde3c7..c61f8a9 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 @@
* @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 @@
}
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 1474f18..f62ca32 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 @@
}
}
+ @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/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index cbfd4d8..41ccb16 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -19,7 +19,6 @@
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
-import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -77,25 +76,23 @@
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
private final NetworkCallback mNetworkCallback =
new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
- @Override
- public void onAvailable(
- Network network, NetworkCapabilities networkCapabilities,
- LinkProperties linkProperties, boolean blocked) {
- boolean isVcnOverWifi =
- networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
- && (Utils.tryGetWifiInfoForVcn(networkCapabilities) != null);
- boolean isWifi =
- networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
- if (isVcnOverWifi || isWifi) {
- mNetworks.add(network.getNetId());
- }
- }
-
// Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
// and onLinkPropertiesChanged.
@Override
public void onCapabilitiesChanged(
Network network, NetworkCapabilities networkCapabilities) {
+ if (!mNetworks.contains(network.getNetId())) {
+ // New network
+ boolean isVcnOverWifi =
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && (Utils.tryGetWifiInfoForVcn(networkCapabilities) != null);
+ boolean isWifi =
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+ if (isVcnOverWifi || isWifi) {
+ mNetworks.add(network.getNetId());
+ }
+ }
+
WifiInfo wifiInfo = null;
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
wifiInfo = Utils.tryGetWifiInfoForVcn(networkCapabilities);
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 5d4078d..fbb84fd 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -242,7 +242,7 @@
<bool name="def_hdmiControlAutoDeviceOff">true</bool>
<!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED -->
- <bool name="def_swipe_bottom_to_notification_enabled">true</bool>
+ <bool name="def_swipe_bottom_to_notification_enabled">false</bool>
<!-- Default for Settings.Secure.ONE_HANDED_MODE_ENABLED -->
<bool name="def_one_handed_mode_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 7288371..9c67e9c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2150,6 +2150,9 @@
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 068efac..3877b1e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -749,7 +749,8 @@
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 b86ae6d..cff8ad1 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" />
@@ -125,7 +129,6 @@
<uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
<uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
<uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
- <uses-permission android:name="android.permission.QUERY_USERS" />
<uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
<uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
<uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
@@ -238,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" />
@@ -438,6 +444,9 @@
<!-- Permission required for CTS test - ResourceObserverNativeTest -->
<uses-permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" />
+ <!-- Permission required for CTS test - android.widget.cts.ToastTest -->
+ <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3904201..4135bbe 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 beee03b..95c2d2e 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 @@
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 @@
}
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 0000000..e49fc15
--- /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 0000000..bce193a
--- /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 0000000..0fc112e
--- /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 fe76b63..0000000
--- 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 b0e0ed5..a845e73 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 4f6cb01..0dec981 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/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 19bcf95..6d44138c 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -48,10 +48,10 @@
<ImageView
android:id="@+id/preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0px"
+ android:layout_height="0px"
android:layout_marginBottom="42dp"
- android:layout_marginHorizontal="48dp"
+ android:paddingHorizontal="48dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/save"
@@ -64,8 +64,8 @@
<com.android.systemui.screenshot.CropView
android:id="@+id/crop_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0px"
+ android:layout_height="0px"
android:layout_marginBottom="42dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
diff --git a/packages/SystemUI/res/layout/people_space_small_view.xml b/packages/SystemUI/res/layout/people_tile_large_empty.xml
similarity index 63%
copy from packages/SystemUI/res/layout/people_space_small_view.xml
copy to packages/SystemUI/res/layout/people_tile_large_empty.xml
index 7b1abae..1e00307 100644
--- a/packages/SystemUI/res/layout/people_space_small_view.xml
+++ b/packages/SystemUI/res/layout/people_tile_large_empty.xml
@@ -20,40 +20,48 @@
<LinearLayout
android:id="@+id/item"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_gravity="center"
+ android:gravity="center"
android:background="@drawable/people_space_tile_view_card"
android:orientation="vertical"
- android:paddingHorizontal="4dp"
- android:paddingVertical="8dp">
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
<ImageView
android:id="@+id/person_icon"
android:layout_gravity="center"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
-
- <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" />
+ android:layout_height="wrap_content" />
<TextView
android:id="@+id/name"
android:layout_gravity="center"
- android:paddingTop="4dp"
+ android:paddingTop="14dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"
android:ellipsize="end"
- android:maxLines="1"
- android:paddingHorizontal="8dp"
+ android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp" />
+ android:textSize="16sp" />
+ <TextView
+ android:id="@+id/last_interaction"
+ android:layout_gravity="center"
+ android:text="@string/empty_status"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="14sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"/>
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_gravity="center"
+ android:layout_width="10dp"
+ android:layout_height="26dp"
+ android:paddingTop="16dp"
+ android:background="@drawable/circle_green_10dp"/>
</LinearLayout>
</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
new file mode 100644
index 0000000..9990244
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
@@ -0,0 +1,132 @@
+<?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:id="@+id/item"
+ android:background="@drawable/people_space_tile_view_card"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:padding="16dp"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start|top">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|top"
+ android:orientation="horizontal">
+
+ <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"
+ android:id="@+id/name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="12dp"
+ android:gravity="start"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="4dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/predefined_icon"
+ android:gravity="start|center_vertical"
+ android:paddingEnd="6dp"
+ android:layout_width="24dp"
+ android:layout_height="18dp" />
+
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/subtext"
+ android:gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="12sp" />
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/people_space_content_background"
+ android:gravity="center"
+ android:scaleType="centerCrop" />
+
+ <TextView
+ android:layout_gravity="center"
+ android:id="@+id/text_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:singleLine="false"
+ android:text="@string/empty_status"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
similarity index 94%
rename from packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
rename to packages/SystemUI/res/layout/people_tile_medium_empty.xml
index b1a37bf..c35787e 100644
--- a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_empty.xml
@@ -35,16 +35,17 @@
android:layout_height="match_parent">
<ImageView
android:id="@+id/person_icon"
- android:layout_width="60dp"
- android:layout_height="60dp"/>
+ android:layout_width="64dp"
+ android:layout_height="64dp"/>
<ImageView
android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/circle_green_10dp"/>
<LinearLayout
android:orientation="vertical"
- android:paddingStart="4dp"
+ android:paddingStart="6dp"
android:gravity="top"
android:layout_width="match_parent"
android:layout_height="wrap_content">
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
similarity index 66%
rename from packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
rename to packages/SystemUI/res/layout/people_tile_medium_with_content.xml
index 6a606e1..db1d46d 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml
@@ -27,28 +27,37 @@
android:layout_gravity="center"
android:padding="8dp"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="match_parent">
+
<LinearLayout
android:orientation="horizontal"
android:gravity="top"
+ android:layout_weight="1"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <ImageView
- android:gravity="start"
- android:id="@+id/person_icon"
- android:layout_width="56dp"
- android:layout_height="56dp"/>
+ android:layout_height="0dp">
+
+ <ImageView
+ android:gravity="start"
+ android:id="@+id/person_icon"
+ android:layout_marginStart="-2dp"
+ android:layout_marginTop="-2dp"
+ android:layout_width="52dp"
+ android:layout_height="52dp" />
+
<ImageView
android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
android:layout_width="10dp"
android:layout_height="10dp"
- android:background="@drawable/circle_green_10dp"/>
+ android:background="@drawable/circle_green_10dp" />
+
<LinearLayout
android:orientation="vertical"
android:gravity="top|start"
android:paddingStart="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+
<TextView
android:id="@+id/subtext"
android:text="@string/empty_user_name"
@@ -58,41 +67,39 @@
android:paddingBottom="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:ellipsize="end"/>
- <LinearLayout
- android:id="@+id/content_background"
- android:background="@drawable/people_space_content_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:singleLine="true"
+ android:ellipsize="end" />
+
<ImageView
android:id="@+id/image"
android:gravity="center"
android:background="@drawable/people_space_content_background"
android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop" />
+
+ <TextView
+ android:id="@+id/text_content"
+ android:text="@string/empty_status"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:scaleType="centerCrop"/>
- </LinearLayout>
- <TextView
- android:id="@+id/text_content"
- android:text="@string/empty_status"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="12sp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:singleLine="false"
- android:ellipsize="end"/>
+ android:maxLines="2"
+ android:singleLine="false"
+ android:ellipsize="end" />
</LinearLayout>
</LinearLayout>
+
<LinearLayout
android:gravity="bottom"
+ android:layout_gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="4dp"
- android:layout_weight="1"
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"
@@ -101,16 +108,31 @@
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"
+ android:singleLine="true"
+ 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:layout_height="wrap_content"
+ android:visibility="gone"
+ />
<ImageView
android:id="@+id/predefined_icon"
- android:gravity="end"
+ android:gravity="end|center_vertical"
android:paddingStart="6dp"
android:layout_width="24dp"
- android:layout_height="18dp"/>
+ android:layout_height="18dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/people_space_small_view.xml b/packages/SystemUI/res/layout/people_tile_small.xml
similarity index 69%
rename from packages/SystemUI/res/layout/people_space_small_view.xml
rename to packages/SystemUI/res/layout/people_tile_small.xml
index 7b1abae..f0ab187 100644
--- a/packages/SystemUI/res/layout/people_space_small_view.xml
+++ b/packages/SystemUI/res/layout/people_tile_small.xml
@@ -25,33 +25,50 @@
android:background="@drawable/people_space_tile_view_card"
android:orientation="vertical"
android:paddingHorizontal="4dp"
- android:paddingVertical="8dp">
+ android:paddingTop="6dp"
+ android:paddingBottom="8dp">
<ImageView
android:id="@+id/person_icon"
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"
android:ellipsize="end"
android:maxLines="1"
- android:paddingHorizontal="8dp"
+ android:paddingHorizontal="4dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp" />
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index fda59b5..1d5bca9 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -54,6 +54,7 @@
android:layout_width="@dimen/volume_row_slider_height"
android:layout_height="match_parent"
android:layout_gravity="center"
+ android:thumb="@android:color/transparent"
android:rotation="270" />
</FrameLayout>
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 95ff59b..8c90573 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 be49e1f..886f98e 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/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0076c51..a773b22 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -202,7 +202,7 @@
<color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
<!-- Long screenshot UI -->
- <color name="screenshot_crop_scrim">#9444</color>
+ <color name="screenshot_crop_scrim">#6444</color>
<!-- GM2 colors -->
<color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index af6df32..6e61148 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 b050945..d5c6398 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -334,7 +334,6 @@
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">6dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
- <dimen name="screenshot_action_container_padding_left">96dp</dimen>
<dimen name="screenshot_action_container_padding_right">8dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
@@ -463,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>
@@ -1354,7 +1357,20 @@
<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>
+ <dimen name="required_height_for_large">182dp</dimen>
+ <dimen name="default_width">146dp</dimen>
+ <dimen name="default_height">92dp</dimen>
+ <dimen name="avatar_size_for_medium">52dp</dimen>
+ <dimen name="max_people_avatar_size_for_large_content">64dp</dimen>
+ <dimen name="max_people_avatar_size">108dp</dimen>
+ <dimen name="name_text_size_for_small">14sp</dimen>
+ <dimen name="name_text_size_for_large">24sp</dimen>
+ <dimen name="content_text_size_for_medium">12sp</dimen>
+ <dimen name="content_text_size_for_large">14sp</dimen>
<!-- Accessibility floating menu -->
<dimen name="accessibility_floating_menu_elevation">5dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 691d111..3ca885a 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 4a661dc..ecc1a5c 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/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 3f0e3eb..ab219f3 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -131,7 +131,7 @@
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 f6b03c1..e4f6e131 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.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 @@
}
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 @@
}
}
- 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 b1e1434..997c527 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.res.Resources;
-import android.net.ConnectivityManager;
-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);
+ }
+
+ @Override
+ public void finishedWakingUp() {
+ mView.setSelected(true);
}
};
- @VisibleForTesting
- protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onRefreshCarrierInfo() {
- if (DEBUG) {
- Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
- + Boolean.toString(mTelephonyCapable));
- }
- updateCarrierText();
- }
+ @Inject
+ public CarrierTextController(CarrierText view,
+ CarrierTextManager.Builder carrierTextManagerBuilder,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ super(view);
- @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
+ mCarrierTextManager = carrierTextManagerBuilder
+ .setShowAirplaneMode(mView.getShowAirplaneMode())
+ .setShowMissingSim(mView.getShowMissingSim())
+ .build();
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}
- /**
- * 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 = ConnectivityManager.from(mContext).isNetworkSupported(
- ConnectivityManager.TYPE_MOBILE);
- if (supported && mNetworkSupported.compareAndSet(false, supported)) {
- // This will set/remove the listeners appropriately. Note that it will never double
- // add the listeners.
- handleSetListening(mCarrierTextCallback);
- }
- });
+ @Override
+ protected void onInit() {
+ super.onInit();
+ mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
}
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ @Override
+ protected void onViewAttached() {
+ mCarrierTextManager.setListening(mCarrierTextCallback);
}
- /**
- * 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);
- }
- 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;
- }
-
- return mContext.getText(carrierHelpTextId);
- }
-
- 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);
- }
- }
- /**
- * 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() {};
+ @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 0000000..bb1d972
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -0,0 +1,721 @@
+/*
+ * 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 707ee29..c4b02f6 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 @@
*/
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 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 @@
return false;
});
}
- whitelistIpcs(this::updateEmergencyCallButton);
}
@Override
@@ -165,65 +107,13 @@
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 @@
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 @@
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 0000000..4275189
--- /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 9f32c03..e41d5a3 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.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 @@
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 @@
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 @@
@Override
public void onInit() {
+ super.onInit();
mMessageAreaController.init();
}
@@ -95,10 +99,7 @@
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 276036c..76a7473 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -36,7 +36,6 @@
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 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 @@
@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 @@
// 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 72dd72e..02a8958 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -487,5 +487,9 @@
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 05f33a9..3d42da2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -42,6 +42,7 @@
private final SecurityMode mSecurityMode;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final EmergencyButton mEmergencyButton;
+ private final EmergencyButtonController mEmergencyButtonController;
private boolean mPaused;
@@ -69,11 +70,18 @@
};
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 @@
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 @@
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 @@
mResources = resources;
mLiftToActivateListener = liftToActivateListener;
mTelephonyManager = telephonyManager;
+ mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled();
}
@@ -185,33 +196,40 @@
/** 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/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index e45dd8b..933a919 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -112,11 +112,13 @@
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 e16c01a..f0d1e02 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.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 @@
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 @@
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 @@
}
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 @@
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 b156f81..8de4e7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -57,9 +57,11 @@
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 49099fa..a456d42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -34,10 +34,11 @@
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 4887767..708b2d5 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.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 @@
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 @@
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 @@
updateBiometricRetry(securityMode, faceAuthEnabled);
updateLayoutForSecurityMode(securityMode);
- mOrientationEventListener.enable();
}
void updateLayoutForSecurityMode(SecurityMode securityMode) {
@@ -385,7 +375,6 @@
mAlertDialog = null;
}
mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
- mOrientationEventListener.disable();
}
@Override
@@ -663,6 +652,15 @@
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 ccba1d5..760eaec 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -29,6 +29,7 @@
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 @@
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 @@
mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
mKeyguardSecurityCallback);
mConfigurationController = configurationController;
+ mLastOrientation = getResources().getConfiguration().orientation;
}
@Override
@@ -498,6 +502,19 @@
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 631c248..69328cd 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.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 @@
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 f1b504e..33d47fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -44,15 +44,18 @@
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 @@
if (childController == null) {
childController = new NullKeyguardInputViewController(
- securityMode, keyguardSecurityCallback);
+ securityMode, keyguardSecurityCallback,
+ mEmergencyButtonControllerFactory.create(null));
}
return childController;
@@ -140,8 +144,9 @@
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 b2bf2e67..fddbb3c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -79,10 +79,10 @@
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 620db48..50bd0c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,7 +37,6 @@
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 @@
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 @@
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 83c2d1e..96e69ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -49,10 +49,8 @@
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 @@
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 @@
* 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 @@
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 1b0a7fa..8038ce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -83,6 +83,10 @@
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 fea152a..5db4f9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -34,7 +34,6 @@
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 @@
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 @@
}
}
- void setCanShowLogout(boolean canShowLogout) {
- mCanShowLogout = canShowLogout;
- updateLogoutView();
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -159,10 +152,7 @@
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
onSliceContentChanged();
- boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
- setEnableMarquee(shouldMarquee);
updateOwnerInfo();
- updateLogoutView();
updateDark();
}
@@ -209,11 +199,11 @@
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 @@
}
}
- 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 934e768..31ec003 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 @@
@Override
public void onInit() {
mKeyguardClockSwitchController.init();
+ mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
+ mView.updateLogoutView(shouldShowLogout());
}
@Override
@@ -245,6 +248,11 @@
}
}
+ private boolean shouldShowLogout() {
+ return mKeyguardUpdateMonitor.isLogoutEnabled()
+ && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+ }
+
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
@@ -267,10 +275,10 @@
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 @@
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
refreshTime();
mView.updateOwnerInfo();
- mView.updateLogoutView();
+ mView.updateLogoutView(shouldShowLogout());
}
}
@@ -314,12 +322,12 @@
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 69e6ed0..9abc1e7 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_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.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.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 @@
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 @@
};
@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 @@
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 @@
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 @@
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/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
similarity index 62%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
index cab5ad2..c4be1ba535 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
@@ -14,17 +14,20 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package com.android.keyguard.clock;
-import android.annotation.NonNull;
+import java.util.List;
-import com.android.i18n.timezone.ZoneInfoDb;
+import dagger.Module;
+import dagger.Provides;
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
+/** Dagger Module for clock package. */
+@Module
+public abstract class ClockModule {
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
+ /** */
+ @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 5ef35be..b6413cb 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.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 @@
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 @@
}
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 @@
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 0000000..49a617e
--- /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 0000000..a672523
--- /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
similarity index 62%
rename from media/java/android/media/metrics/PlaybackComponent.java
rename to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
index 1cadf3b..ba0642f 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
/**
- * Interface for playback related components used by playback metrics.
+ * Scope annotation for singleton items within the StatusBarComponent.
*/
-public interface PlaybackComponent {
-
- /**
- * Sets the playback ID of the component.
- */
- void setPlaybackId(@NonNull String playbackId);
-
- /**
- * Gets playback ID.
- */
- @NonNull String getPlaybackId();
-}
+@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 1b6476c..d342377 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 @@
/**
* 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 cd53a34..ac99f73 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -349,6 +349,9 @@
}
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 06b486e..a686fc0 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.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 @@
@Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
@Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
@Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy;
+ @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
@Inject
public Dependency() {
@@ -545,6 +547,7 @@
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 4afa969..45a0ea1 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.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 @@
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 @@
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 @@
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 @@
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 bb4038e..fd0c4ef 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -18,10 +18,6 @@
import static android.view.View.GONE;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.FIRST_ITEM;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.LAST_ITEM;
-import static com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ItemType.REGULAR_ITEM;
-
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,9 +43,9 @@
private final List<AccessibilityTarget> mTargets;
@IntDef({
- FIRST_ITEM,
- REGULAR_ITEM,
- LAST_ITEM
+ ItemType.FIRST_ITEM,
+ ItemType.REGULAR_ITEM,
+ ItemType.LAST_ITEM
})
@Retention(RetentionPolicy.SOURCE)
@interface ItemType {
@@ -69,11 +65,11 @@
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);
}
@@ -91,14 +87,14 @@
@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/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index d3168c8..a7c1451 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -14,14 +14,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
import android.metrics.LogMaker;
import android.os.AsyncTask;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
@@ -30,12 +24,6 @@
import android.provider.Settings;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ImageView;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -43,7 +31,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.R;
import com.android.systemui.assist.ui.DefaultUiController;
import com.android.systemui.dagger.SysUISingleton;
@@ -97,8 +84,6 @@
// Note that VERBOSE logging may leak PII (e.g. transcription contents).
private static final boolean VERBOSE = false;
- private static final String ASSIST_ICON_METADATA_NAME =
- "com.android.systemui.action_assist_icon";
private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
public static final String INVOCATION_TYPE_KEY = "invocation_type";
@@ -123,69 +108,29 @@
private static final long TIMEOUT_ACTIVITY = 1000;
protected final Context mContext;
- private final WindowManager mWindowManager;
private final AssistDisclosure mAssistDisclosure;
- private final InterestingConfigChanges mInterestingConfigChanges;
private final PhoneStateMonitor mPhoneStateMonitor;
private final AssistHandleBehaviorController mHandleController;
private final UiController mUiController;
protected final Lazy<SysUiState> mSysUiState;
protected final AssistLogger mAssistLogger;
- private AssistOrbContainer mView;
private final DeviceProvisionedController mDeviceProvisionedController;
private final CommandQueue mCommandQueue;
+ private final AssistOrbController mOrbController;
protected final AssistUtils mAssistUtils;
- private final boolean mShouldEnableOrb;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@Override
public void onFailed() throws RemoteException {
- mView.post(mHideRunnable);
+ mOrbController.postHide();
}
@Override
public void onShown() throws RemoteException {
- mView.post(mHideRunnable);
- }
- };
-
- private Runnable mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mView.removeCallbacks(this);
- mView.show(false /* show */, true /* animate */);
- }
- };
-
- private ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
- return;
- }
- boolean visible = false;
- if (mView != null) {
- visible = mView.isShowing();
- if (mView.isAttachedToWindow()) {
- mWindowManager.removeView(mView);
- }
- }
-
- mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
- R.layout.assist_orb, null);
- mView.setVisibility(View.GONE);
- mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
- WindowManager.LayoutParams lp = getLayoutParams();
- mWindowManager.addView(mView, lp);
- if (visible) {
- mView.show(true /* show */, false /* animate */);
- }
+ mOrbController.postHide();
}
};
@@ -205,21 +150,15 @@
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mAssistUtils = assistUtils;
mAssistDisclosure = new AssistDisclosure(context, new Handler());
mPhoneStateMonitor = phoneStateMonitor;
mHandleController = handleController;
mAssistLogger = assistLogger;
- configurationController.addCallback(mConfigurationListener);
+ mOrbController = new AssistOrbController(configurationController, context);
registerVoiceInteractionSessionListener();
- mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
- | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
- | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
- mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
- mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
mUiController = defaultUiController;
@@ -282,7 +221,7 @@
}
protected boolean shouldShowOrb() {
- return false;
+ return !ActivityManager.isLowRamDeviceStatic();
}
public void startAssist(Bundle args) {
@@ -293,10 +232,8 @@
final boolean isService = assistComponent.equals(getVoiceInteractorComponentName());
if (!isService || (!isVoiceSessionRunning() && shouldShowOrb())) {
- showOrb(assistComponent, isService);
- mView.postDelayed(mHideRunnable, isService
- ? TIMEOUT_SERVICE
- : TIMEOUT_ACTIVITY);
+ mOrbController.showOrb(assistComponent, isService);
+ mOrbController.postHideDelayed(isService ? TIMEOUT_SERVICE : TIMEOUT_ACTIVITY);
}
if (args == null) {
@@ -340,30 +277,6 @@
mAssistUtils.hideCurrentSession();
}
- private WindowManager.LayoutParams getLayoutParams() {
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
- WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- lp.token = new Binder();
- lp.gravity = Gravity.BOTTOM | Gravity.START;
- lp.setTitle("AssistPreviewPanel");
- lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
- | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
- return lp;
- }
-
- private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
- maybeSwapSearchIcon(assistComponent, isService);
- if (mShouldEnableOrb) {
- mView.show(true /* show */, true /* animate */);
- }
- }
-
private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
boolean isService) {
if (isService) {
@@ -440,44 +353,6 @@
return mAssistUtils.isSessionRunning();
}
- private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
- replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
- isService);
- }
-
- public void replaceDrawable(ImageView v, ComponentName component, String name,
- boolean isService) {
- if (component != null) {
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = isService
- ? packageManager.getServiceInfo(
- component, PackageManager.GET_META_DATA).metaData
- : packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForApplication(
- component.getPackageName());
- v.setImageDrawable(res.getDrawable(iconResId));
- return;
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- if (VERBOSE) {
- Log.v(TAG, "Assistant component "
- + component.flattenToShortString() + " not found");
- }
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- v.setImageDrawable(null);
- }
-
protected AssistHandleBehaviorController getHandleBehaviorController() {
return mHandleController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
index f78436a..fe48c26 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java
@@ -55,14 +55,17 @@
mOrb = (AssistOrbView) findViewById(R.id.assist_orb);
}
- public void show(final boolean show, boolean animate) {
+ public void show(final boolean show, boolean animate, Runnable onDone) {
if (show) {
if (getVisibility() != View.VISIBLE) {
setVisibility(View.VISIBLE);
if (animate) {
- startEnterAnimation();
+ startEnterAnimation(onDone);
} else {
reset();
+ if (onDone != null) {
+ onDone.run();
+ }
}
}
} else {
@@ -72,10 +75,16 @@
public void run() {
mAnimatingOut = false;
setVisibility(View.GONE);
+ if (onDone != null) {
+ onDone.run();
+ }
}
});
} else {
setVisibility(View.GONE);
+ if (onDone != null) {
+ onDone.run();
+ }
}
}
}
@@ -87,7 +96,7 @@
mNavbarScrim.setAlpha(1f);
}
- private void startEnterAnimation() {
+ private void startEnterAnimation(Runnable onDone) {
if (mAnimatingOut) {
return;
}
@@ -106,7 +115,8 @@
.alpha(1f)
.setDuration(300)
.setStartDelay(0)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .withEndAction(onDone);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
new file mode 100644
index 0000000..81a13a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -0,0 +1,180 @@
+/*
+ * 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.assist;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+/**
+ * AssistOrbController controls the showing and hiding of the assistant orb.
+ */
+public class AssistOrbController {
+ private static final String ASSIST_ICON_METADATA_NAME =
+ "com.android.systemui.action_assist_icon";
+ private static final String TAG = "AssistOrbController";
+ private static final boolean VERBOSE = false;
+
+ private final InterestingConfigChanges mInterestingConfigChanges;
+ private AssistOrbContainer mView;
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+
+ private Runnable mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mView.removeCallbacks(this);
+ mView.show(false /* show */, true /* animate */, () -> {
+ mWindowManager.removeView(mView);
+ });
+ }
+ };
+
+ private ConfigurationController.ConfigurationListener mConfigurationListener =
+ new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
+ return;
+ }
+ boolean visible = false;
+ if (mView != null) {
+ visible = mView.isShowing();
+ if (mView.isAttachedToWindow()) {
+ mWindowManager.removeView(mView);
+ }
+ }
+
+ if (visible) {
+ showOrb(false);
+ }
+ }
+ };
+
+ AssistOrbController(ConfigurationController configurationController, Context context) {
+ mContext = context;
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
+
+ configurationController.addCallback(mConfigurationListener);
+ mConfigurationListener.onConfigChanged(context.getResources().getConfiguration());
+ }
+
+ public void postHide() {
+ mView.post(mHideRunnable);
+ }
+
+ public void postHideDelayed(long delayMs) {
+ mView.postDelayed(mHideRunnable, delayMs);
+ }
+
+ private void showOrb(boolean animated) {
+ if (mView == null) {
+ mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
+ R.layout.assist_orb, null);
+ mView.setVisibility(View.GONE);
+ mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ }
+ if (!mView.isAttachedToWindow()) {
+ WindowManager.LayoutParams params = getLayoutParams();
+ mWindowManager.addView(mView, params);
+ }
+ mView.show(true, animated, null);
+ }
+
+ private WindowManager.LayoutParams getLayoutParams() {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ lp.token = new Binder();
+ lp.gravity = Gravity.BOTTOM | Gravity.START;
+ lp.setTitle("AssistPreviewPanel");
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+ return lp;
+ }
+
+ public void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
+ showOrb(true);
+ maybeSwapSearchIcon(assistComponent, isService);
+ }
+
+ private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) {
+ replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME,
+ isService);
+ }
+
+ public void replaceDrawable(ImageView v, ComponentName component, String name,
+ boolean isService) {
+ if (component != null) {
+ try {
+ PackageManager packageManager = mContext.getPackageManager();
+ // Look for the search icon specified in the activity meta-data
+ Bundle metaData = isService
+ ? packageManager.getServiceInfo(
+ component, PackageManager.GET_META_DATA).metaData
+ : packageManager.getActivityInfo(
+ component, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(name);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForApplication(
+ component.getPackageName());
+ v.setImageDrawable(res.getDrawable(iconResId));
+ return;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (VERBOSE) {
+ Log.v(TAG, "Assistant component "
+ + component.flattenToShortString() + " not found");
+ }
+ } catch (Resources.NotFoundException nfe) {
+ Log.w(TAG, "Failed to swap drawable from "
+ + component.flattenToShortString(), nfe);
+ }
+ }
+ v.setImageDrawable(null);
+ }
+}
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 0000000..a1149fd
--- /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 0000000..2e32133
--- /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 98b3fe4..078ec9f 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 @@
@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 @@
@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 @@
mStatusBarStateController = statusBarStateController;
mKeyguardViewManager = statusBarKeyguardViewManager;
mDumpManager = dumpManager;
+ mAuthRippleController = authRippleController;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -343,6 +346,10 @@
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 98a703f..521c495 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.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 @@
"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 @@
// 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 378907c..e274843 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 @@
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/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 08e5d56..1f652db 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -33,6 +33,7 @@
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
private boolean mForceShow;
+ private boolean mQsExpanded;
protected UdfpsKeyguardViewController(
@NonNull UdfpsKeyguardView view,
@@ -64,7 +65,7 @@
protected void onViewDetached() {
super.onViewDetached();
mStatusBarStateController.removeCallback(mStateListener);
- mAlternateAuthInterceptor.reset();
+ mAlternateAuthInterceptor.resetForceShow();
mKeyguardViewManager.setAlternateAuthInterceptor(null);
}
@@ -100,6 +101,11 @@
if (mForceShow) {
return false;
}
+
+ if (mQsExpanded) {
+ return true;
+ }
+
return super.shouldPauseAuth();
}
@@ -130,7 +136,7 @@
}
@Override
- public boolean reset() {
+ public boolean resetForceShow() {
if (!mForceShow) {
return false;
}
@@ -140,7 +146,7 @@
}
@Override
- public boolean isShowingAlternativeAuth() {
+ public boolean isShowingAlternateAuth() {
return mForceShow;
}
@@ -150,6 +156,12 @@
}
@Override
+ public void setQsExpanded(boolean expanded) {
+ mQsExpanded = expanded;
+ updatePauseAuth();
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.print(getTag());
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 7e7cdce..6812f77 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.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 @@
}
};
- 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 @@
}
});
+ 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 @@
};
private Collection<FalsingClassifier.Result> mPriorResults;
+ private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC;
@Inject
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
@@ -154,7 +177,7 @@
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
- mDataProvider.addGestureCompleteListener(mGestureCompleteListener);
+ mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
mHistoryTracker.addBeliefListener(mBeliefListener);
}
@@ -165,50 +188,33 @@
@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);
+ logDebug("False Gesture: " + booleanResult);
- 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();
- }
- }
-
- return result;
+ return booleanResult;
}
@Override
@@ -354,7 +360,7 @@
@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 f665a74..1aaa139 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 @@
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 @@
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 @@
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 @@
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 @@
}
/** 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 ed3d5ec..8e4e308 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 @@
* 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 b0067cd..b67db03 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 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 @@
@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 52c9f16..b7f6e2b 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 @@
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 461a730..1862718 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -50,6 +50,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -72,8 +73,8 @@
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;
@@ -137,6 +138,7 @@
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;
@@ -302,7 +304,8 @@
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,
@@ -356,10 +359,11 @@
filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
- mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ mHasTelephony =
+ 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);
@@ -2047,7 +2051,8 @@
}
};
- 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 de2e7c47..a747edd 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.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 @@
/**
* 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 dd3d02a..31e4939 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 @@
mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- handleHomeTaskMovedToFront(taskInfo);
+ public void onTaskStackChanged() {
+ ActivityManager.RunningTaskInfo currentTask =
+ mActivityManagerWrapper.getRunningTask();
+ if (currentTask == null || currentTask.topActivityInfo == null) {
+ return;
+ }
+ handleTaskStackChanged(currentTask);
}
});
}
}
- private boolean isHomeTask(ActivityManager.RunningTaskInfo taskInfo) {
- return taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ private boolean hasFlagNoSound(ActivityInfo activityInfo) {
+ if ((activityInfo.privateFlags & ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND) == 0) {
+ // Only allow flag if app has permission
+ if (mPm.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS,
+ activityInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ } else {
+ Slog.w(TAG,
+ "Activity has flag playHomeTransition set to false but doesn't hold "
+ + "required permission "
+ + Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS);
+ return false;
+ }
+ }
+ return false;
}
/**
- * To enable a home sound, check if the home app moves to front.
+ * The home sound is played if all of the following conditions are met:
+ * <ul>
+ * <li>The last task which moved to front was not home. This avoids playing the sound
+ * e.g. after FallbackHome transitions to home, another activity of the home app like a
+ * notification panel moved to front, or in case the home app crashed.</li>
+ * <li>The current activity which moved to front is home</li>
+ * <li>The topActivity of the last task has {@link android.R.attr#playHomeTransitionSound} set
+ * to <code>true</code>.</li>
+ * <li>The topActivity of the last task is not of type
+ * {@link WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} if config_playHomeSoundAfterAssistant is
+ * set to <code>false</code> (default).</li>
+ * <li>The topActivity of the last task is not of type
+ * {@link WindowConfiguration#ACTIVITY_TYPE_DREAM} if config_playHomeSoundAfterDream is
+ * set to <code>false</code> (default).</li>
+ * </ul>
*/
- private void handleHomeTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- boolean isCurrentTaskHome = isHomeTask(taskInfo);
- // If the last task is home we don't want to play the home sound. This avoids playing
- // the home sound after FallbackHome transitions to Home
- if (!mIsLastTaskHome && isCurrentTaskHome) {
+ private boolean shouldPlayHomeSoundForCurrentTransition(
+ ActivityManager.RunningTaskInfo currentTask) {
+ boolean isHomeActivity =
+ currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ if (currentTask.taskId == mLastTaskId) {
+ return false;
+ }
+ if (mIsLastTaskHome || !isHomeActivity) {
+ return false;
+ }
+ if (mLastActivityHasNoHomeSound) {
+ return false;
+ }
+ if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+ && !mPlayHomeSoundAfterAssistant) {
+ return false;
+ }
+ if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ && !mPlayHomeSoundAfterDream) {
+ return false;
+ }
+ return true;
+ }
+
+ private void updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask) {
+ mLastTaskId = currentTask.taskId;
+ mLastActivityType = currentTask.topActivityType;
+ mLastActivityHasNoHomeSound = hasFlagNoSound(currentTask.topActivityInfo);
+ boolean isHomeActivity =
+ currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ boolean isHomePackage = currentTask.topActivityInfo.packageName.equals(
+ mLastHomePackageName);
+ mIsLastTaskHome = isHomeActivity || isHomePackage;
+ if (isHomeActivity && !isHomePackage) {
+ mLastHomePackageName = currentTask.topActivityInfo.packageName;
+ }
+ }
+
+ private void handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask) {
+ if (shouldPlayHomeSoundForCurrentTransition(frontTask)) {
mAudioManager.playSoundEffect(AudioManager.FX_HOME);
}
- mIsLastTaskHome = isCurrentTaskHome;
+ updateLastTaskInfo(frontTask);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index f9c2a2a..5bc1280 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -18,42 +18,23 @@
import static android.app.Notification.CATEGORY_MISSED_CALL;
import static android.app.Notification.EXTRA_MESSAGES;
-import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
-import static android.app.people.ConversationStatus.ACTIVITY_AUDIO;
-import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_GAME;
-import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
-import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
-import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_VIDEO;
-import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
-import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.Notification;
-import android.app.PendingIntent;
import android.app.people.ConversationChannel;
-import android.app.people.ConversationStatus;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
import android.database.Cursor;
import android.database.SQLException;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.icu.text.MeasureFormat;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
@@ -66,10 +47,7 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
-import android.util.IconDrawableFactory;
import android.util.Log;
-import android.util.TypedValue;
-import android.view.View;
import android.widget.RemoteViews;
import androidx.preference.PreferenceManager;
@@ -81,8 +59,6 @@
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
import com.android.systemui.people.widget.AppWidgetOptionsHelper;
-import com.android.systemui.people.widget.LaunchConversationActivity;
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.people.widget.PeopleTileKey;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -99,10 +75,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -123,18 +96,6 @@
public static final PeopleTileKey EMPTY_KEY =
new PeopleTileKey(EMPTY_STRING, INVALID_USER_ID, EMPTY_STRING);
- public static final int SMALL_LAYOUT = 0;
- public static final int MEDIUM_LAYOUT = 1;
- @VisibleForTesting
- static final int REQUIRED_WIDTH_FOR_MEDIUM = 146;
- private static final int AVATAR_SIZE_FOR_MEDIUM = 56;
- private static final int DEFAULT_WIDTH = 146;
- private static final int DEFAULT_HEIGHT = 92;
-
- 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("[!?][!?]+");
- private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
/** Represents whether {@link StatusBarNotification} was posted or removed. */
public enum NotificationAction {
@@ -313,13 +274,17 @@
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());
@@ -333,369 +298,16 @@
.setNotificationCategory(notification.category)
.setNotificationContent(content)
.setNotificationDataUri(dataUri)
+ .setMessagesCount(messagesCount)
.build();
}
- /** Creates a {@link RemoteViews} for {@code tile}. */
- public static RemoteViews createRemoteViews(Context context,
- PeopleSpaceTile tile, int appWidgetId, Bundle options) {
- int layoutSize = getLayoutSize(context, options);
- RemoteViews viewsForTile = getViewForTile(context, tile, layoutSize);
- int maxAvatarSize = getMaxAvatarSize(context, options, layoutSize);
- RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile, maxAvatarSize);
- return setLaunchIntents(context, views, tile, appWidgetId);
- }
-
/**
- * The prioritization for the {@code tile} content is missed calls, followed by notification
- * content, then birthdays, then the most recent status, and finally last interaction.
+ * Returns {@link Notification.MessagingStyle.Message}s from the Notification in chronological
+ * order from most recent to least.
*/
- private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile,
- int layoutSize) {
- if (Objects.equals(tile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
- if (DEBUG) Log.d(TAG, "Create missed call view");
- return createMissedCallRemoteViews(context, tile, layoutSize);
- }
-
- if (tile.getNotificationKey() != null) {
- if (DEBUG) Log.d(TAG, "Create notification view");
- return createNotificationRemoteViews(context, tile, layoutSize);
- }
-
- // TODO: Add sorting when we expose timestamp of statuses.
- List<ConversationStatus> statusesForEntireView =
- tile.getStatuses() == null ? Arrays.asList() : tile.getStatuses().stream().filter(
- c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
- ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
- if (birthdayStatus != null) {
- if (DEBUG) Log.d(TAG, "Create birthday view");
- return createStatusRemoteViews(context, birthdayStatus, layoutSize);
- }
-
- if (!statusesForEntireView.isEmpty()) {
- if (DEBUG) {
- Log.d(TAG,
- "Create status view for: " + statusesForEntireView.get(0).getActivity());
- }
- return createStatusRemoteViews(context, statusesForEntireView.get(0), layoutSize);
- }
-
- return createLastInteractionRemoteViews(context, tile, layoutSize);
- }
-
- /** Calculates the best layout relative to the size in {@code options}. */
- private static int getLayoutSize(Context context, Bundle options) {
- int display = context.getResources().getConfiguration().orientation;
- int width = display == Configuration.ORIENTATION_PORTRAIT
- ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
- OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
- int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
- OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
- : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
- // Small layout used below a certain minimum width with any height.
- if (width < REQUIRED_WIDTH_FOR_MEDIUM) {
- if (DEBUG) Log.d(TAG, "Small view for width: " + width + " height: " + height);
- return SMALL_LAYOUT;
- }
- if (DEBUG) Log.d(TAG, "Medium view for width: " + width + " height: " + height);
- return MEDIUM_LAYOUT;
- }
-
- /** Returns the max avatar size for {@code layoutSize} under the current {@code options}. */
- private static int getMaxAvatarSize(Context context, Bundle options, int layoutSize) {
- int avatarHeightSpace = AVATAR_SIZE_FOR_MEDIUM;
- int avatarWidthSpace = AVATAR_SIZE_FOR_MEDIUM;
-
- if (layoutSize == SMALL_LAYOUT) {
- int display = context.getResources().getConfiguration().orientation;
- int width = display == Configuration.ORIENTATION_PORTRAIT
- ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH, DEFAULT_WIDTH) : options.getInt(
- OPTION_APPWIDGET_MAX_WIDTH, DEFAULT_WIDTH);
- int height = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
- OPTION_APPWIDGET_MAX_HEIGHT, DEFAULT_HEIGHT)
- : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, DEFAULT_HEIGHT);
- avatarHeightSpace = height - (8 + 4 + 18 + 8);
- avatarWidthSpace = width - (4 + 4);
- }
- if (DEBUG) Log.d(TAG, "Height: " + avatarHeightSpace + " width: " + avatarWidthSpace);
- return Math.min(avatarHeightSpace, avatarWidthSpace);
- }
-
- @Nullable
- private static ConversationStatus getBirthdayStatus(PeopleSpaceTile tile,
- List<ConversationStatus> statuses) {
- Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
- c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
- if (birthdayStatus.isPresent()) {
- return birthdayStatus.get();
- }
- if (!TextUtils.isEmpty(tile.getBirthdayText())) {
- return new ConversationStatus.Builder(tile.getId(), ACTIVITY_BIRTHDAY).build();
- }
-
- return null;
- }
-
- /**
- * Returns whether a {@code status} should have its own entire templated view.
- *
- * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
- * not valid to compose an entire view.
- */
- private static boolean isStatusValidForEntireStatusView(ConversationStatus status) {
- switch (status.getActivity()) {
- // Birthday & Anniversary don't require text provided or icon provided.
- case ACTIVITY_BIRTHDAY:
- case ACTIVITY_ANNIVERSARY:
- return true;
- default:
- // For future birthday, location, new story, video, music, game, and other, the
- // app must provide either text or an icon.
- return !TextUtils.isEmpty(status.getDescription())
- || status.getIcon() != null;
- }
- }
-
- private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status,
- int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_small_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- CharSequence statusText = status.getDescription();
- if (TextUtils.isEmpty(statusText)) {
- statusText = getStatusTextByType(context, status.getActivity());
- }
- views.setViewVisibility(R.id.subtext, View.GONE);
- views.setViewVisibility(R.id.text_content, View.VISIBLE);
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
- int secondaryTextColor = context.getColor(typedValue.resourceId);
- views.setInt(R.id.text_content, "setTextColor", secondaryTextColor);
- views.setTextViewText(R.id.text_content, statusText);
- Icon statusIcon = status.getIcon();
- if (statusIcon != null) {
- views.setImageViewIcon(R.id.image, statusIcon);
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- } else {
- views.setViewVisibility(R.id.content_background, View.GONE);
- }
- // TODO: Set status pre-defined icons
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
- ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
- return views;
- }
-
- private static String getStatusTextByType(Context context, int activity) {
- switch (activity) {
- case ACTIVITY_BIRTHDAY:
- return context.getString(R.string.birthday_status);
- case ACTIVITY_UPCOMING_BIRTHDAY:
- return context.getString(R.string.upcoming_birthday_status);
- case ACTIVITY_ANNIVERSARY:
- return context.getString(R.string.anniversary_status);
- case ACTIVITY_LOCATION:
- return context.getString(R.string.location_status);
- case ACTIVITY_NEW_STORY:
- return context.getString(R.string.new_story_status);
- case ACTIVITY_VIDEO:
- return context.getString(R.string.video_status);
- case ACTIVITY_AUDIO:
- return context.getString(R.string.audio_status);
- case ACTIVITY_GAME:
- return context.getString(R.string.game_status);
- default:
- return EMPTY_STRING;
- }
- }
-
- private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
- PeopleSpaceTile tile, int maxAvatarSize) {
- try {
- boolean isAvailable =
- tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
- c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
- if (isAvailable) {
- views.setViewVisibility(R.id.availability, View.VISIBLE);
- } else {
- views.setViewVisibility(R.id.availability, View.GONE);
- }
- boolean hasNewStory =
- tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
- c -> c.getActivity() == ACTIVITY_NEW_STORY);
- views.setTextViewText(R.id.name, tile.getUserName().toString());
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- Icon icon = tile.getUserIcon();
- PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
- context.getPackageManager(),
- IconDrawableFactory.newInstance(context, false),
- maxAvatarSize);
- Drawable drawable = icon.loadDrawable(context);
- Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
- tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
- hasNewStory);
- Bitmap bitmap = convertDrawableToBitmap(personDrawable);
- views.setImageViewBitmap(R.id.person_icon, bitmap);
-
- return views;
- } catch (Exception e) {
- Log.e(TAG, "Failed to set common fields: " + e);
- }
- return views;
- }
-
- private static RemoteViews setLaunchIntents(Context context, RemoteViews views,
- PeopleSpaceTile tile, int appWidgetId) {
- try {
- Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
- activityIntent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_ACTIVITY_NO_HISTORY
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId());
- activityIntent.putExtra(
- PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName());
- activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
- tile.getUserHandle());
- activityIntent.putExtra(
- PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, tile.getNotificationKey());
- views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
- context,
- appWidgetId,
- activityIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
- return views;
- } catch (Exception e) {
- Log.e(TAG, "Failed to add launch intents: " + e);
- }
-
- return views;
- }
-
- private static RemoteViews createMissedCallRemoteViews(Context context,
- PeopleSpaceTile tile, int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_small_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- views.setViewVisibility(R.id.subtext, View.GONE);
- views.setViewVisibility(R.id.text_content, View.VISIBLE);
- views.setTextViewText(R.id.text_content, tile.getNotificationContent());
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
- ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- return views;
- }
-
- private static void ensurePredefinedIconVisibleOnSmallView(RemoteViews views, int layoutSize) {
- if (layoutSize == SMALL_LAYOUT) {
- views.setViewVisibility(R.id.name, View.GONE);
- views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
- }
- }
-
- private static RemoteViews createNotificationRemoteViews(Context context,
- PeopleSpaceTile tile, int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_small_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- if (layoutSize != MEDIUM_LAYOUT) {
- views.setViewVisibility(R.id.name, View.GONE);
- views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
- }
- Uri image = tile.getNotificationDataUri();
- ensurePredefinedIconVisibleOnSmallView(views, layoutSize);
- if (image != null) {
- // TODO: Use NotificationInlineImageCache
- views.setImageViewUri(R.id.image, image);
- views.setViewVisibility(R.id.content_background, View.VISIBLE);
- views.setBoolean(R.id.content_background, "setClipToOutline", true);
- views.setViewVisibility(R.id.text_content, View.GONE);
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
- } else {
- CharSequence content = tile.getNotificationContent();
- views = setPunctuationRemoteViewsFields(views, content);
- views.setTextViewText(R.id.text_content, tile.getNotificationContent());
- // TODO: Measure max lines from height.
- views.setInt(R.id.text_content, "setMaxLines", 2);
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
- int primaryTextColor = context.getColor(typedValue.resourceId);
- views.setInt(R.id.text_content, "setTextColor", primaryTextColor);
- views.setViewVisibility(R.id.text_content, View.VISIBLE);
- views.setViewVisibility(R.id.content_background, View.GONE);
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
- }
- // TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile.
- views.setViewVisibility(R.id.subtext, View.GONE);
- return views;
- }
-
- private static RemoteViews createLastInteractionRemoteViews(Context context,
- PeopleSpaceTile tile, int layoutSize) {
- int layout = layoutSize == SMALL_LAYOUT ? R.layout.people_space_small_view
- : R.layout.people_space_large_avatar_tile;
- RemoteViews views = new RemoteViews(context.getPackageName(), layout);
- if (layoutSize == SMALL_LAYOUT) {
- views.setViewVisibility(R.id.name, View.VISIBLE);
- views.setViewVisibility(R.id.predefined_icon, View.GONE);
- }
- String status = PeopleSpaceUtils.getLastInteractionString(
- context, tile.getLastInteractionTimestamp());
- views.setTextViewText(R.id.last_interaction, status);
- return views;
- }
-
- private static RemoteViews setPunctuationRemoteViewsFields(
- RemoteViews views, CharSequence content) {
- String punctuation = getBackgroundTextFromMessage(content.toString());
- int visibility = View.GONE;
- if (punctuation != null) {
- visibility = View.VISIBLE;
- }
- views.setTextViewText(R.id.punctuation1, punctuation);
- views.setTextViewText(R.id.punctuation2, punctuation);
- views.setTextViewText(R.id.punctuation3, punctuation);
- views.setTextViewText(R.id.punctuation4, punctuation);
- views.setTextViewText(R.id.punctuation5, punctuation);
- views.setTextViewText(R.id.punctuation6, punctuation);
-
- views.setViewVisibility(R.id.punctuation1, visibility);
- views.setViewVisibility(R.id.punctuation2, visibility);
- views.setViewVisibility(R.id.punctuation3, visibility);
- views.setViewVisibility(R.id.punctuation4, visibility);
- views.setViewVisibility(R.id.punctuation5, visibility);
- views.setViewVisibility(R.id.punctuation6, visibility);
-
- return views;
- }
-
- /** Gets character for tile background decoration based on notification content. */
@VisibleForTesting
- static String getBackgroundTextFromMessage(String message) {
- if (!ANY_DOUBLE_MARK_PATTERN.matcher(message).find()) {
- return null;
- }
- if (MIXED_MARK_PATTERN.matcher(message).find()) {
- return "!?";
- }
- Matcher doubleQuestionMatcher = DOUBLE_QUESTION_PATTERN.matcher(message);
- if (!doubleQuestionMatcher.find()) {
- return "!";
- }
- Matcher doubleExclamationMatcher = DOUBLE_EXCLAMATION_PATTERN.matcher(message);
- if (!doubleExclamationMatcher.find()) {
- return "?";
- }
- // If we have both "!!" and "??", return the one that comes first.
- if (doubleQuestionMatcher.start() < doubleExclamationMatcher.start()) {
- return "?";
- }
- return "!";
- }
-
- /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */
- @VisibleForTesting
- public static Notification.MessagingStyle.Message getLastMessagingStyleMessage(
+ public static List<Notification.MessagingStyle.Message> getMessagingStyleMessages(
Notification notification) {
if (notification == null) {
return null;
@@ -708,7 +320,7 @@
Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
sortedMessages.sort(Collections.reverseOrder(
Comparator.comparing(Notification.MessagingStyle.Message::getTimestamp)));
- return sortedMessages.get(0);
+ return sortedMessages;
}
}
return null;
@@ -917,7 +529,8 @@
public static void updateAppWidgetViews(AppWidgetManager appWidgetManager,
Context context, int appWidgetId, PeopleSpaceTile tile, Bundle options) {
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName());
- RemoteViews views = createRemoteViews(context, tile, appWidgetId, options);
+ RemoteViews views = new PeopleTileViewHelper(context, tile, appWidgetId,
+ options).getViews();
// Tell the AppWidgetManager to perform an update on the current app widget.
appWidgetManager.updateAppWidget(appWidgetId, views);
@@ -1005,7 +618,7 @@
if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
Bundle bundle = new Bundle();
- return PeopleSpaceUtils.createRemoteViews(context, augmentedTile, 0, bundle);
+ return new PeopleTileViewHelper(context, augmentedTile, 0, bundle).getViews();
}
/** Returns the userId associated with a {@link PeopleSpaceTile} */
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
new file mode 100644
index 0000000..8d1b712
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -0,0 +1,586 @@
+/*
+ * 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.people;
+
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_AUDIO;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_LOCATION;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_VIDEO;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+
+import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
+import static com.android.systemui.people.PeopleSpaceUtils.getUserId;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleSpaceTile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+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;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+class PeopleTileViewHelper {
+ /** Turns on debugging information about People Space. */
+ public static final boolean DEBUG = true;
+ private static final String TAG = "PeopleTileView";
+
+ public static final int LAYOUT_SMALL = 0;
+ public static final int LAYOUT_MEDIUM = 1;
+ public static final int LAYOUT_LARGE = 2;
+
+ private static final int MIN_CONTENT_MAX_LINES = 2;
+
+ private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT = 14 + 12 + 4 + 16;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT = 8 + 4 + 4 + 8;
+ 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("[!?][!?]+");
+ private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+
+ public static final String EMPTY_STRING = "";
+
+ private Context mContext;
+ private PeopleSpaceTile mTile;
+ private float mDensity;
+ private int mAppWidgetId;
+ private int mWidth;
+ private int mHeight;
+ private int mLayoutSize;
+
+ private Locale mLocale;
+ private NumberFormat mIntegerFormat;
+
+ PeopleTileViewHelper(Context context, PeopleSpaceTile tile,
+ int appWidgetId, Bundle options) {
+ mContext = context;
+ mTile = tile;
+ mAppWidgetId = appWidgetId;
+ mDensity = mContext.getResources().getDisplayMetrics().density;
+ int display = mContext.getResources().getConfiguration().orientation;
+ mWidth = display == Configuration.ORIENTATION_PORTRAIT
+ ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.default_width)) : options.getInt(
+ OPTION_APPWIDGET_MAX_WIDTH,
+ getSizeInDp(R.dimen.default_width));
+ mHeight = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
+ OPTION_APPWIDGET_MAX_HEIGHT,
+ getSizeInDp(R.dimen.default_height))
+ : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT,
+ getSizeInDp(R.dimen.default_height));
+ mLayoutSize = getLayoutSize();
+ }
+
+ public RemoteViews getViews() {
+ RemoteViews viewsForTile = getViewForTile();
+ int maxAvatarSize = getMaxAvatarSize(viewsForTile);
+ RemoteViews views = setCommonRemoteViewsFields(viewsForTile, maxAvatarSize);
+ return setLaunchIntents(views);
+ }
+
+ /**
+ * The prioritization for the {@code mTile} content is missed calls, followed by notification
+ * content, then birthdays, then the most recent status, and finally last interaction.
+ */
+ private RemoteViews getViewForTile() {
+ if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
+ if (DEBUG) Log.d(TAG, "Create missed call view");
+ return createMissedCallRemoteViews();
+ }
+
+ if (mTile.getNotificationKey() != null) {
+ if (DEBUG) Log.d(TAG, "Create notification view");
+ return createNotificationRemoteViews();
+ }
+
+ // TODO: Add sorting when we expose timestamp of statuses.
+ List<ConversationStatus> statusesForEntireView =
+ mTile.getStatuses() == null ? Arrays.asList() : mTile.getStatuses().stream().filter(
+ c -> isStatusValidForEntireStatusView(c)).collect(Collectors.toList());
+ ConversationStatus birthdayStatus = getBirthdayStatus(statusesForEntireView);
+ if (birthdayStatus != null) {
+ if (DEBUG) Log.d(TAG, "Create birthday view");
+ return createStatusRemoteViews(birthdayStatus);
+ }
+
+ if (!statusesForEntireView.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "Create status view for: " + statusesForEntireView.get(0).getActivity());
+ }
+ return createStatusRemoteViews(statusesForEntireView.get(0));
+ }
+
+ return createLastInteractionRemoteViews();
+ }
+
+ private void setMaxLines(RemoteViews views) {
+ int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp(
+ R.dimen.content_text_size_for_medium)
+ : getSizeInDp(R.dimen.content_text_size_for_medium);
+ int lineHeight = getLineHeight(textSize);
+ int notificationContentHeight = getContentHeightForLayout(lineHeight);
+ int maxAdaptiveLines = Math.floorDiv(notificationContentHeight, lineHeight);
+ int maxLines = Math.max(MIN_CONTENT_MAX_LINES, maxAdaptiveLines);
+ views.setInt(R.id.text_content, "setMaxLines", maxLines);
+ }
+
+ private int getLineHeight(int textSize) {
+ try {
+ TextView text = new TextView(mContext);
+ text.setTextSize(textSize);
+ int lineHeight = (int) (text.getLineHeight() / mDensity);
+ return lineHeight;
+ } catch (Exception e) {
+ Log.e(TAG, "Could not create text view: " + e);
+ return getSizeInDp(
+ R.dimen.content_text_size_for_medium);
+ }
+ }
+
+ private int getSizeInDp(int dimenResourceId) {
+ return (int) (mContext.getResources().getDimension(dimenResourceId) / mDensity);
+ }
+
+ private int getContentHeightForLayout(int lineHeight) {
+ switch (mLayoutSize) {
+ case LAYOUT_MEDIUM:
+ return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT);
+ case LAYOUT_LARGE:
+ return mHeight - (getSizeInDp(
+ R.dimen.max_people_avatar_size_for_large_content) + lineHeight
+ + FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT);
+ default:
+ return -1;
+ }
+ }
+
+ /** Calculates the best layout relative to the size in {@code options}. */
+ private int getLayoutSize() {
+ if (mHeight >= getSizeInDp(R.dimen.required_width_for_large)
+ && mWidth >= getSizeInDp(R.dimen.required_width_for_large)) {
+ if (DEBUG) Log.d(TAG, "Large view for mWidth: " + mWidth + " mHeight: " + mHeight);
+ return LAYOUT_LARGE;
+ }
+ // Small layout used below a certain minimum mWidth with any mHeight.
+ if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) {
+ if (DEBUG) Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight);
+ return LAYOUT_MEDIUM;
+ }
+ // Small layout can always handle our minimum mWidth and mHeight for our widget.
+ if (DEBUG) Log.d(TAG, "Small view for mWidth: " + mWidth + " mHeight: " + mHeight);
+ return LAYOUT_SMALL;
+ }
+
+ /** Returns the max avatar size for {@code views} under the current {@code options}. */
+ private int getMaxAvatarSize(RemoteViews views) {
+ int layoutId = views.getLayoutId();
+ int avatarSize = getSizeInDp(R.dimen.avatar_size_for_medium);
+ if (layoutId == R.layout.people_tile_medium_empty) {
+ return getSizeInDp(
+ R.dimen.max_people_avatar_size_for_large_content);
+ }
+ if (layoutId == R.layout.people_tile_medium_with_content) {
+ return getSizeInDp(R.dimen.avatar_size_for_medium);
+ }
+
+ // Calculate adaptive avatar size for remaining layouts.
+ if (layoutId == R.layout.people_tile_small) {
+ int avatarHeightSpace = mHeight - (FIXED_HEIGHT_DIMENS_FOR_SMALL + Math.max(18,
+ getLineHeight(getSizeInDp(
+ R.dimen.name_text_size_for_small))));
+ int avatarWidthSpace = mWidth - FIXED_WIDTH_DIMENS_FOR_SMALL;
+ avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
+ }
+
+ if (layoutId == R.layout.people_tile_large_with_content) {
+ avatarSize = mHeight - (FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT + (getLineHeight(
+ getSizeInDp(R.dimen.content_text_size_for_large))
+ * 3));
+ return Math.min(avatarSize, getSizeInDp(
+ R.dimen.max_people_avatar_size_for_large_content));
+ }
+
+ if (layoutId == R.layout.people_tile_large_empty) {
+ int avatarHeightSpace = mHeight - (14 + 14 + getLineHeight(
+ getSizeInDp(R.dimen.name_text_size_for_large))
+ + getLineHeight(
+ getSizeInDp(R.dimen.content_text_size_for_large))
+ + 16 + 10 + 14);
+ int avatarWidthSpace = mWidth - (14 + 14);
+ avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
+ }
+ return Math.min(avatarSize,
+ getSizeInDp(R.dimen.max_people_avatar_size));
+ }
+
+ private RemoteViews setCommonRemoteViewsFields(RemoteViews views,
+ int maxAvatarSize) {
+ try {
+ boolean isAvailable =
+ mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
+ c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
+ if (isAvailable) {
+ views.setViewVisibility(R.id.availability, View.VISIBLE);
+ } else {
+ views.setViewVisibility(R.id.availability, View.GONE);
+ }
+ boolean hasNewStory =
+ mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
+ c -> c.getActivity() == ACTIVITY_NEW_STORY);
+ views.setTextViewText(R.id.name, mTile.getUserName().toString());
+ views.setBoolean(R.id.image, "setClipToOutline", true);
+
+ Icon icon = mTile.getUserIcon();
+ PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(mContext,
+ mContext.getPackageManager(),
+ IconDrawableFactory.newInstance(mContext, false),
+ maxAvatarSize);
+ Drawable drawable = icon.loadDrawable(mContext);
+ Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
+ mTile.getPackageName(), getUserId(mTile), mTile.isImportantConversation(),
+ hasNewStory);
+ Bitmap bitmap = convertDrawableToBitmap(personDrawable);
+ views.setImageViewBitmap(R.id.person_icon, bitmap);
+
+ return views;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set common fields: " + e);
+ }
+ return views;
+ }
+
+ private RemoteViews setLaunchIntents(RemoteViews views) {
+ try {
+ Intent activityIntent = new Intent(mContext, LaunchConversationActivity.class);
+ activityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NO_HISTORY
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, mTile.getId());
+ activityIntent.putExtra(
+ PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, mTile.getPackageName());
+ activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
+ mTile.getUserHandle());
+ activityIntent.putExtra(
+ PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, mTile.getNotificationKey());
+ views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
+ mContext,
+ mAppWidgetId,
+ activityIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE));
+ return views;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to add launch intents: " + e);
+ }
+
+ return views;
+ }
+
+ private RemoteViews createMissedCallRemoteViews() {
+ RemoteViews views = getViewForContentLayout();
+ views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+ setMaxLines(views);
+ views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
+ return views;
+ }
+
+ private RemoteViews createNotificationRemoteViews() {
+ RemoteViews views = getViewForContentLayout();
+ Uri image = mTile.getNotificationDataUri();
+ if (image != null) {
+ // TODO: Use NotificationInlineImageCache
+ views.setImageViewUri(R.id.image, image);
+ views.setViewVisibility(R.id.image, View.VISIBLE);
+ views.setViewVisibility(R.id.text_content, View.GONE);
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
+ } else {
+ setMaxLines(views);
+ CharSequence content = mTile.getNotificationContent();
+ views = setPunctuationRemoteViewsFields(views, content);
+ TypedValue typedValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
+ int primaryTextColor = mContext.getColor(typedValue.resourceId);
+ views.setInt(R.id.text_content, "setTextColor", primaryTextColor);
+ views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
+ 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();
+ if (TextUtils.isEmpty(statusText)) {
+ statusText = getStatusTextByType(status.getActivity());
+ }
+ views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+ setMaxLines(views);
+ // Secondary text color for statuses.
+ TypedValue typedValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
+ int secondaryTextColor = mContext.getColor(typedValue.resourceId);
+ views.setInt(R.id.text_content, "setTextColor", secondaryTextColor);
+ views.setTextViewText(R.id.text_content, statusText);
+
+ Icon statusIcon = status.getIcon();
+ if (statusIcon != null) {
+ // No multi-line text with status images on medium layout.
+ views.setViewVisibility(R.id.text_content, View.GONE);
+ // Show 1-line subtext on large layout with status images.
+ if (mLayoutSize == LAYOUT_LARGE) {
+ views.setViewVisibility(R.id.subtext, View.VISIBLE);
+ views.setTextViewText(R.id.subtext, statusText);
+ }
+ views.setViewVisibility(R.id.image, View.VISIBLE);
+ views.setImageViewIcon(R.id.image, statusIcon);
+ } else {
+ views.setViewVisibility(R.id.image, View.GONE);
+ }
+ // TODO: Set status pre-defined icons
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_person);
+ return views;
+ }
+
+ @Nullable
+ private ConversationStatus getBirthdayStatus(
+ List<ConversationStatus> statuses) {
+ Optional<ConversationStatus> birthdayStatus = statuses.stream().filter(
+ c -> c.getActivity() == ACTIVITY_BIRTHDAY).findFirst();
+ if (birthdayStatus.isPresent()) {
+ return birthdayStatus.get();
+ }
+ if (!TextUtils.isEmpty(mTile.getBirthdayText())) {
+ return new ConversationStatus.Builder(mTile.getId(), ACTIVITY_BIRTHDAY).build();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether a {@code status} should have its own entire templated view.
+ *
+ * <p>A status may still be shown on the view (for example, as a new story ring) even if it's
+ * not valid to compose an entire view.
+ */
+ private boolean isStatusValidForEntireStatusView(ConversationStatus status) {
+ switch (status.getActivity()) {
+ // Birthday & Anniversary don't require text provided or icon provided.
+ case ACTIVITY_BIRTHDAY:
+ case ACTIVITY_ANNIVERSARY:
+ return true;
+ default:
+ // For future birthday, location, new story, video, music, game, and other, the
+ // app must provide either text or an icon.
+ return !TextUtils.isEmpty(status.getDescription())
+ || status.getIcon() != null;
+ }
+ }
+
+ private String getStatusTextByType(int activity) {
+ switch (activity) {
+ case ACTIVITY_BIRTHDAY:
+ return mContext.getString(R.string.birthday_status);
+ case ACTIVITY_UPCOMING_BIRTHDAY:
+ return mContext.getString(R.string.upcoming_birthday_status);
+ case ACTIVITY_ANNIVERSARY:
+ return mContext.getString(R.string.anniversary_status);
+ case ACTIVITY_LOCATION:
+ return mContext.getString(R.string.location_status);
+ case ACTIVITY_NEW_STORY:
+ return mContext.getString(R.string.new_story_status);
+ case ACTIVITY_VIDEO:
+ return mContext.getString(R.string.video_status);
+ case ACTIVITY_AUDIO:
+ return mContext.getString(R.string.audio_status);
+ case ACTIVITY_GAME:
+ return mContext.getString(R.string.game_status);
+ default:
+ return EMPTY_STRING;
+ }
+ }
+
+ private RemoteViews setPunctuationRemoteViewsFields(
+ RemoteViews views, CharSequence content) {
+ String punctuation = getBackgroundTextFromMessage(content.toString());
+ int visibility = View.GONE;
+ if (punctuation != null) {
+ visibility = View.VISIBLE;
+ }
+ views.setTextViewText(R.id.punctuation1, punctuation);
+ views.setTextViewText(R.id.punctuation2, punctuation);
+ views.setTextViewText(R.id.punctuation3, punctuation);
+ views.setTextViewText(R.id.punctuation4, punctuation);
+ views.setTextViewText(R.id.punctuation5, punctuation);
+ views.setTextViewText(R.id.punctuation6, punctuation);
+
+ views.setViewVisibility(R.id.punctuation1, visibility);
+ views.setViewVisibility(R.id.punctuation2, visibility);
+ views.setViewVisibility(R.id.punctuation3, visibility);
+ views.setViewVisibility(R.id.punctuation4, visibility);
+ views.setViewVisibility(R.id.punctuation5, visibility);
+ views.setViewVisibility(R.id.punctuation6, visibility);
+
+ return views;
+ }
+
+ /** Gets character for mTile background decoration based on notification content. */
+ @VisibleForTesting
+ String getBackgroundTextFromMessage(String message) {
+ if (!ANY_DOUBLE_MARK_PATTERN.matcher(message).find()) {
+ return null;
+ }
+ if (MIXED_MARK_PATTERN.matcher(message).find()) {
+ return "!?";
+ }
+ Matcher doubleQuestionMatcher = DOUBLE_QUESTION_PATTERN.matcher(message);
+ if (!doubleQuestionMatcher.find()) {
+ return "!";
+ }
+ Matcher doubleExclamationMatcher = DOUBLE_EXCLAMATION_PATTERN.matcher(message);
+ if (!doubleExclamationMatcher.find()) {
+ return "?";
+ }
+ // If we have both "!!" and "??", return the one that comes first.
+ if (doubleQuestionMatcher.start() < doubleExclamationMatcher.start()) {
+ return "?";
+ }
+ return "!";
+ }
+
+ private RemoteViews getViewForContentLayout() {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(),
+ getLayoutForContent());
+ if (mLayoutSize == LAYOUT_SMALL) {
+ views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+ views.setViewVisibility(R.id.name, View.GONE);
+ } else {
+ views.setViewVisibility(R.id.predefined_icon, View.GONE);
+ views.setViewVisibility(R.id.name, View.VISIBLE);
+ views.setViewVisibility(R.id.text_content, View.VISIBLE);
+ views.setViewVisibility(R.id.subtext, View.GONE);
+ }
+ return views;
+ }
+
+ private RemoteViews createLastInteractionRemoteViews() {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(), getEmptyLayout());
+ if (mLayoutSize == LAYOUT_SMALL) {
+ views.setViewVisibility(R.id.name, View.VISIBLE);
+ views.setViewVisibility(R.id.predefined_icon, View.GONE);
+ }
+ String status = PeopleSpaceUtils.getLastInteractionString(mContext,
+ mTile.getLastInteractionTimestamp());
+ views.setTextViewText(R.id.last_interaction, status);
+ return views;
+ }
+
+ private int getEmptyLayout() {
+ switch (mLayoutSize) {
+ case LAYOUT_MEDIUM:
+ return R.layout.people_tile_medium_empty;
+ case LAYOUT_LARGE:
+ return R.layout.people_tile_large_empty;
+ case LAYOUT_SMALL:
+ default:
+ return R.layout.people_tile_small;
+ }
+ }
+
+ private int getLayoutForContent() {
+ switch (mLayoutSize) {
+ case LAYOUT_MEDIUM:
+ return R.layout.people_tile_medium_with_content;
+ case LAYOUT_LARGE:
+ return R.layout.people_tile_large_with_content;
+ case LAYOUT_SMALL:
+ default:
+ return R.layout.people_tile_small;
+ }
+ }
+}
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 4ad685e..776e8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -328,6 +328,7 @@
.setNotificationKey(null)
.setNotificationContent(null)
.setNotificationDataUri(null)
+ .setMessagesCount(0)
// Reset missed calls category.
.setNotificationCategory(null)
.build();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 9967936..d54d3f2 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.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 @@
}
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 @@
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 54e8a2b..cc5a771 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 @@
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 5e13fd3..2c5dbcd 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 @@
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 3e3451e..0a9c12f 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 @@
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 @@
&& 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/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index eedcdab..b1689f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -48,7 +48,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
@@ -86,7 +85,6 @@
private final View mRingerContainer;
private final QSTileHost mQSTileHost;
private final StatusBarIconController mStatusBarIconController;
- private final CommandQueue mCommandQueue;
private final DemoModeController mDemoModeController;
private final UserTracker mUserTracker;
private final StatusIconContainer mIconContainer;
@@ -204,7 +202,7 @@
PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
ActivityStarter activityStarter, UiEventLogger uiEventLogger,
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
- CommandQueue commandQueue, DemoModeController demoModeController,
+ DemoModeController demoModeController,
UserTracker userTracker, QuickQSPanelController quickQSPanelController,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
PrivacyLogger privacyLogger,
@@ -219,7 +217,6 @@
mUiEventLogger = uiEventLogger;
mQSTileHost = qsTileHost;
mStatusBarIconController = statusBarIconController;
- mCommandQueue = commandQueue;
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
@@ -238,7 +235,7 @@
mRingerContainer = mView.findViewById(R.id.ringer_container);
mIconContainer = mView.findViewById(R.id.statusIcons);
- mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, mCommandQueue);
+ mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
mColorExtractor = colorExtractor;
mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 994da9a..8aa2d09 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 @@
/** 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 @@
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 0dc0b30..5afe1c8 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 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 @@
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 @@
}
};
- private static class Callback implements CarrierTextController.CarrierTextCallback {
+ private static class Callback implements CarrierTextManager.CarrierTextCallback {
private H mHandler;
Callback(H handler) {
@@ -157,7 +157,7 @@
}
@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 @@
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 @@
mActivityStarter = activityStarter;
mBgHandler = bgHandler;
mNetworkController = networkController;
- mCarrierTextController = carrierTextControllerBuilder
+ mCarrierTextManager = carrierTextManagerBuilder
.setShowAirplaneMode(false)
.setShowMissingSim(false)
.build();
@@ -192,7 +192,6 @@
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 @@
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 @@
}
@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 @@
}
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 @@
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 @@
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 32b41ec..d72f8e9 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 @@
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/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 1ec175c..78ee896 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,8 +16,6 @@
package com.android.systemui.screenshot;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
@@ -25,6 +23,7 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +31,7 @@
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
+import android.util.Range;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -49,29 +49,30 @@
*/
public class CropView extends View {
private static final String TAG = "CropView";
+
public enum CropBoundary {
- NONE, TOP, BOTTOM
+ NONE, TOP, BOTTOM, LEFT, RIGHT
}
private final float mCropTouchMargin;
private final Paint mShadePaint;
private final Paint mHandlePaint;
- // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the
- // view, respectively.
- private float mTopCrop = 0f;
- private float mBottomCrop = 1f;
-
- // When the user is dragging a handle, these variables store the distance between the top/bottom
- // crop values and
- private float mTopDelta = 0f;
- private float mBottomDelta = 0f;
+ // Crop rect with each element represented as [0,1] along its proper axis.
+ private RectF mCrop = new RectF(0, 0, 1, 1);
private int mExtraTopPadding;
private int mExtraBottomPadding;
+ private int mImageWidth;
private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
+ // The starting value of mCurrentDraggingBoundary's crop, used to compute touch deltas.
+ private float mMovementStartValue;
private float mStartingY; // y coordinate of ACTION_DOWN
+ private float mStartingX;
+ // The allowable values for the current boundary being dragged
+ private Range<Float> mMotionRange;
+
private CropInteractionListener mCropInteractionListener;
public CropView(Context context, @Nullable AttributeSet attrs) {
@@ -86,6 +87,7 @@
mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT));
mHandlePaint = new Paint();
mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK));
+ mHandlePaint.setStrokeCap(Paint.Cap.ROUND);
mHandlePaint.setStrokeWidth(
t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20));
t.recycle();
@@ -100,8 +102,7 @@
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
- ss.mTopBoundary = getTopBoundary();
- ss.mBottomBoundary = getBottomBoundary();
+ ss.mCrop = mCrop;
return ss;
}
@@ -110,45 +111,67 @@
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
- setBoundaryTo(CropBoundary.TOP, ss.mTopBoundary);
- setBoundaryTo(CropBoundary.BOTTOM, ss.mBottomBoundary);
+ mCrop = ss.mCrop;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
- float top = mTopCrop + mTopDelta;
- float bottom = mBottomCrop + mBottomDelta;
- drawShade(canvas, 0, top);
- drawShade(canvas, bottom, 1f);
- drawHandle(canvas, top, /* draw the handle tab down */ false);
- drawHandle(canvas, bottom, /* draw the handle tab up */ true);
+ drawShade(canvas, 0, 0, 1, mCrop.top);
+ drawShade(canvas, 0, mCrop.bottom, 1, 1);
+ drawShade(canvas, 0, mCrop.top, mCrop.left, mCrop.bottom);
+ drawShade(canvas, mCrop.right, mCrop.top, 1, mCrop.bottom);
+ drawHorizontalHandle(canvas, mCrop.top, /* draw the handle tab up */ true);
+ drawHorizontalHandle(canvas, mCrop.bottom, /* draw the handle tab down */ false);
+ drawVerticalHandle(canvas, mCrop.left, /* left */ true);
+ drawVerticalHandle(canvas, mCrop.right, /* right */ false);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- int topPx = fractionToPixels(mTopCrop);
- int bottomPx = fractionToPixels(mBottomCrop);
+ int topPx = fractionToVerticalPixels(mCrop.top);
+ int bottomPx = fractionToVerticalPixels(mCrop.bottom);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
- mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx);
+ mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx,
+ fractionToHorizontalPixels(mCrop.left),
+ fractionToHorizontalPixels(mCrop.right));
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
mStartingY = event.getY();
+ mStartingX = event.getX();
+ mMovementStartValue = getBoundaryPosition(mCurrentDraggingBoundary);
updateListener(event);
+ switch (mCurrentDraggingBoundary) {
+ case TOP:
+ mMotionRange = new Range<>(0f,
+ mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.BOTTOM));
+ break;
+ case BOTTOM:
+ mMotionRange = new Range<>(
+ mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.TOP), 1f);
+ break;
+ case LEFT:
+ mMotionRange = new Range<>(0f,
+ mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.RIGHT));
+ break;
+ case RIGHT:
+ mMotionRange = new Range<>(
+ mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.LEFT), 1f);
+ break;
+ }
}
return true;
case MotionEvent.ACTION_MOVE:
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
- float delta = event.getY() - mStartingY;
- if (mCurrentDraggingBoundary == CropBoundary.TOP) {
- mTopDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta,
- -topPx + mExtraTopPadding,
- bottomPx - 2 * mCropTouchMargin - topPx));
- } else { // Bottom
- mBottomDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta,
- topPx + 2 * mCropTouchMargin - bottomPx,
- getHeight() - bottomPx - mExtraBottomPadding));
- }
+ float deltaPx = isVertical(mCurrentDraggingBoundary) ? event.getY() - mStartingY
+ : event.getX() - mStartingX;
+ float delta = pixelDistanceToFraction((int) deltaPx, mCurrentDraggingBoundary);
+ setBoundaryPosition(mCurrentDraggingBoundary,
+ mMotionRange.clamp(mMovementStartValue + delta));
updateListener(event);
invalidate();
return true;
@@ -156,8 +179,6 @@
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mCurrentDraggingBoundary != CropBoundary.NONE) {
- // Commit the delta to the stored crop values.
- commitDeltas(mCurrentDraggingBoundary);
updateListener(event);
}
}
@@ -167,22 +188,46 @@
/**
* Set the given boundary to the given value without animation.
*/
- public void setBoundaryTo(CropBoundary boundary, float value) {
+ public void setBoundaryPosition(CropBoundary boundary, float position) {
switch (boundary) {
case TOP:
- mTopCrop = value;
+ mCrop.top = position;
break;
case BOTTOM:
- mBottomCrop = value;
+ mCrop.bottom = position;
+ break;
+ case LEFT:
+ mCrop.left = position;
+ break;
+ case RIGHT:
+ mCrop.right = position;
break;
case NONE:
- Log.w(TAG, "No boundary selected for animation");
+ Log.w(TAG, "No boundary selected");
break;
}
invalidate();
}
+ private float getBoundaryPosition(CropBoundary boundary) {
+ switch (boundary) {
+ case TOP:
+ return mCrop.top;
+ case BOTTOM:
+ return mCrop.bottom;
+ case LEFT:
+ return mCrop.left;
+ case RIGHT:
+ return mCrop.right;
+ }
+ return 0;
+ }
+
+ private static boolean isVertical(CropBoundary boundary) {
+ return boundary == CropBoundary.TOP || boundary == CropBoundary.BOTTOM;
+ }
+
/**
* Animate the given boundary to the given value.
*/
@@ -191,28 +236,13 @@
Log.w(TAG, "No boundary selected for animation");
return;
}
- float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
- : (value - mBottomCrop);
+ float start = getBoundaryPosition(boundary);
ValueAnimator animator = new ValueAnimator();
animator.addUpdateListener(animation -> {
- if (boundary == CropBoundary.TOP) {
- mTopDelta = animation.getAnimatedFraction() * totalDelta;
- } else {
- mBottomDelta = animation.getAnimatedFraction() * totalDelta;
- }
+ setBoundaryPosition(boundary,
+ MathUtils.lerp(start, value, animation.getAnimatedFraction()));
invalidate();
});
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- commitDeltas(boundary);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- commitDeltas(boundary);
- }
- });
animator.setFloatValues(0f, 1f);
animator.setDuration(750);
animator.setInterpolator(new FastOutSlowInInterpolator());
@@ -230,65 +260,79 @@
}
/**
- * @return value [0,1] representing the position of the top crop boundary. Does not reflect
- * changes from any in-progress touch input.
+ * Set the pixel width of the image on the screen (on-screen dimension, not actual bitmap
+ * dimension)
*/
- public float getTopBoundary() {
- return mTopCrop;
+ public void setImageWidth(int width) {
+ mImageWidth = width;
+ invalidate();
}
/**
- * @return value [0,1] representing the position of the bottom crop boundary. Does not reflect
- * changes from any in-progress touch input.
+ * @return RectF with values [0,1] representing the position of the boundaries along image axes.
*/
- public float getBottomBoundary() {
- return mBottomCrop;
+ public Rect getCropBoundaries(int imageWidth, int imageHeight) {
+ return new Rect((int) (mCrop.left * imageWidth), (int) (mCrop.top * imageHeight),
+ (int) (mCrop.right * imageWidth), (int) (mCrop.bottom * imageHeight));
}
public void setCropInteractionListener(CropInteractionListener listener) {
mCropInteractionListener = listener;
}
- private void commitDeltas(CropBoundary boundary) {
- if (boundary == CropBoundary.TOP) {
- mTopCrop += mTopDelta;
- mTopDelta = 0;
- } else if (boundary == CropBoundary.BOTTOM) {
- mBottomCrop += mBottomDelta;
- mBottomDelta = 0;
- }
- }
-
private void updateListener(MotionEvent event) {
- if (mCropInteractionListener != null) {
- float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
- ? mTopCrop + mTopDelta : mBottomCrop + mBottomDelta;
+ if (mCropInteractionListener != null && (isVertical(mCurrentDraggingBoundary))) {
+ float boundaryPosition = getBoundaryPosition(mCurrentDraggingBoundary);
mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary,
- boundaryPosition, fractionToPixels(boundaryPosition));
+ boundaryPosition, fractionToVerticalPixels(boundaryPosition),
+ (mCrop.left + mCrop.right) / 2);
}
}
- private void drawShade(Canvas canvas, float fracStart, float fracEnd) {
- canvas.drawRect(0, fractionToPixels(fracStart), getWidth(),
- fractionToPixels(fracEnd), mShadePaint);
+ /**
+ * Draw a shade to the given canvas with the given [0,1] fractional image bounds.
+ */
+ private void drawShade(Canvas canvas, float left, float top, float right, float bottom) {
+ canvas.drawRect(fractionToHorizontalPixels(left), fractionToVerticalPixels(top),
+ fractionToHorizontalPixels(right),
+ fractionToVerticalPixels(bottom), mShadePaint);
}
- private void drawHandle(Canvas canvas, float frac, boolean handleTabUp) {
- int y = fractionToPixels(frac);
- canvas.drawLine(0, y, getWidth(), y, mHandlePaint);
- float radius = 15 * getResources().getDisplayMetrics().density;
- float x = getWidth() * .9f;
+ private void drawHorizontalHandle(Canvas canvas, float frac, boolean handleTabUp) {
+ int y = fractionToVerticalPixels(frac);
+ canvas.drawLine(fractionToHorizontalPixels(mCrop.left), y,
+ fractionToHorizontalPixels(mCrop.right), y, mHandlePaint);
+ float radius = 8 * getResources().getDisplayMetrics().density;
+ int x = (fractionToHorizontalPixels(mCrop.left) + fractionToHorizontalPixels(mCrop.right))
+ / 2;
canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabUp ? 180 : 0, 180,
true, mHandlePaint);
}
+ private void drawVerticalHandle(Canvas canvas, float frac, boolean handleTabLeft) {
+ int x = fractionToHorizontalPixels(frac);
+ canvas.drawLine(x, fractionToVerticalPixels(mCrop.top), x,
+ fractionToVerticalPixels(mCrop.bottom), mHandlePaint);
+ float radius = 8 * getResources().getDisplayMetrics().density;
+ int y = (fractionToVerticalPixels(getBoundaryPosition(CropBoundary.TOP))
+ + fractionToVerticalPixels(
+ getBoundaryPosition(CropBoundary.BOTTOM))) / 2;
+ canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabLeft ? 90 : 270,
+ 180,
+ true, mHandlePaint);
+ }
+
/**
* Convert the given fraction position to pixel position within the View.
*/
- private int fractionToPixels(float frac) {
+ private int fractionToVerticalPixels(float frac) {
return (int) (mExtraTopPadding + frac * getImageHeight());
}
+ private int fractionToHorizontalPixels(float frac) {
+ return (int) ((getWidth() - mImageWidth) / 2 + frac * mImageWidth);
+ }
+
private int getImageHeight() {
return getHeight() - mExtraTopPadding - mExtraBottomPadding;
}
@@ -296,17 +340,30 @@
/**
* Convert the given pixel distance to fraction of the image.
*/
- private float pixelDistanceToFraction(int px) {
- return px / (float) getImageHeight();
+ private float pixelDistanceToFraction(float px, CropBoundary boundary) {
+ if (isVertical(boundary)) {
+ return px / getImageHeight();
+ } else {
+ return px / mImageWidth;
+ }
}
- private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) {
+ private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx, int leftPx,
+ int rightPx) {
if (Math.abs(event.getY() - topPx) < mCropTouchMargin) {
return CropBoundary.TOP;
}
if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) {
return CropBoundary.BOTTOM;
}
+ if (event.getY() > topPx || event.getY() < bottomPx) {
+ if (Math.abs(event.getX() - leftPx) < mCropTouchMargin) {
+ return CropBoundary.LEFT;
+ }
+ if (Math.abs(event.getX() - rightPx) < mCropTouchMargin) {
+ return CropBoundary.RIGHT;
+ }
+ }
return CropBoundary.NONE;
}
@@ -321,10 +378,10 @@
@Override
protected int getVirtualViewAt(float x, float y) {
- if (Math.abs(y - fractionToPixels(mTopCrop)) < mCropTouchMargin) {
+ if (Math.abs(y - fractionToVerticalPixels(mCrop.top)) < mCropTouchMargin) {
return TOP_HANDLE_ID;
}
- if (Math.abs(y - fractionToPixels(mBottomCrop)) < mCropTouchMargin) {
+ if (Math.abs(y - fractionToVerticalPixels(mCrop.bottom)) < mCropTouchMargin) {
return BOTTOM_HANDLE_ID;
}
return ExploreByTouchHelper.INVALID_ID;
@@ -338,7 +395,7 @@
@Override
protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
- switch(virtualViewId) {
+ switch (virtualViewId) {
case TOP_HANDLE_ID:
event.setContentDescription(
getResources().getString(R.string.screenshot_top_boundary));
@@ -353,16 +410,16 @@
@Override
protected void onPopulateNodeForVirtualView(int virtualViewId,
AccessibilityNodeInfo node) {
- switch(virtualViewId) {
+ switch (virtualViewId) {
case TOP_HANDLE_ID:
node.setContentDescription(
getResources().getString(R.string.screenshot_top_boundary));
- setNodePositions(mTopCrop, node);
+ setNodePositions(mCrop.top, node);
break;
case BOTTOM_HANDLE_ID:
node.setContentDescription(
getResources().getString(R.string.screenshot_bottom_boundary));
- setNodePositions(mBottomCrop, node);
+ setNodePositions(mCrop.bottom, node);
break;
}
@@ -380,7 +437,7 @@
}
private void setNodePositions(float fraction, AccessibilityNodeInfo node) {
- int pixels = fractionToPixels(fraction);
+ int pixels = fractionToVerticalPixels(fraction);
Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin),
getWidth(), (int) (pixels + mCropTouchMargin));
node.setBoundsInParent(rect);
@@ -400,12 +457,11 @@
* boundaries.
*/
void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition,
- int boundaryPositionPx);
+ int boundaryPositionPx, float horizontalCenter);
}
static class SavedState extends BaseSavedState {
- float mTopBoundary;
- float mBottomBoundary;
+ RectF mCrop;
/**
* Constructor called from {@link CropView#onSaveInstanceState()}
@@ -419,15 +475,13 @@
*/
private SavedState(Parcel in) {
super(in);
- mTopBoundary = in.readFloat();
- mBottomBoundary = in.readFloat();
+ mCrop = in.readParcelable(ClassLoader.getSystemClassLoader());
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
- out.writeFloat(mTopBoundary);
- out.writeFloat(mBottomBoundary);
+ out.writeParcelable(mCrop, 0);
}
public static final Parcelable.Creator<SavedState> CREATOR
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 6a004c2..04d1996 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -128,7 +128,7 @@
mPreview.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
- updateCropLocation());
+ updateImageDimensions());
Intent intent = getIntent();
mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
@@ -206,7 +206,7 @@
Log.d(TAG, "onCaptureCompleted(longScreenshot=" + longScreenshot + ")");
mLongScreenshot = longScreenshot;
mPreview.setImageDrawable(mLongScreenshot.getDrawable());
- updateCropLocation();
+ updateImageDimensions();
mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
// Original boundaries go from the image tile set's y=0 to y=pageSize, so
@@ -267,8 +267,14 @@
@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 @@
}
@Override
- protected void onStop() {
- Log.d(TAG, "onStop");
- super.onStop();
- }
-
- @Override
protected void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
@@ -363,10 +363,8 @@
return;
}
- Rect bounds = new Rect(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- int height = bounds.height();
- bounds.top = (int) (height * mCropView.getTopBoundary());
- bounds.bottom = (int) (height * mCropView.getBottomBoundary());
+ Rect bounds = mCropView.getCropBoundaries(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight());
if (bounds.isEmpty()) {
Log.w(TAG, "Crop bounds empty, skipping export.");
@@ -404,14 +402,16 @@
}
}
- private void updateCropLocation() {
+ private void updateImageDimensions() {
Drawable drawable = mPreview.getDrawable();
if (drawable == null) {
return;
}
Rect bounds = drawable.getBounds();
float imageRatio = bounds.width() / (float) bounds.height();
- float viewRatio = mPreview.getWidth() / (float) mPreview.getHeight();
+ int previewWidth = mPreview.getWidth() - mPreview.getPaddingLeft()
+ - mPreview.getPaddingRight();
+ float viewRatio = previewWidth / (float) mPreview.getHeight();
if (imageRatio > viewRatio) {
// Image is full width and height is constrained, compute extra padding to inform
@@ -419,9 +419,12 @@
float imageHeight = mPreview.getHeight() * viewRatio / imageRatio;
int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2;
mCropView.setExtraPadding(extraPadding, extraPadding);
+ mCropView.setImageWidth(previewWidth);
} else {
// Image is full height
mCropView.setExtraPadding(0, 0);
+ mCropView.setImageWidth((int) (mPreview.getHeight() * imageRatio));
}
+
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index 90f3042..08cd91c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -58,6 +58,7 @@
private float mCheckerboardBoxSize = 40;
private float mLastCropPosition;
+ private float mLastCenter = 0.5f;
private CropView.CropBoundary mCropBoundary;
private ViewPropertyAnimator mTranslationAnimator;
@@ -131,7 +132,7 @@
canvas.save();
// Translate such that the center of this view represents the center of the crop
// boundary.
- canvas.translate(-mDrawable.getBounds().width() / 2 + getWidth() / 2,
+ canvas.translate(-mDrawable.getBounds().width() * mLastCenter + getWidth() / 2,
-mDrawable.getBounds().height() * mLastCropPosition + getHeight() / 2);
mDrawable.draw(canvas);
canvas.restore();
@@ -148,8 +149,9 @@
@Override
public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary,
- float cropPosition, int cropPositionPx) {
+ float cropPosition, int cropPositionPx, float horizontalCenter) {
mCropBoundary = boundary;
+ mLastCenter = horizontalCenter;
boolean touchOnRight = event.getX() > getParentWidth() / 2;
float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
switch (event.getAction()) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index c1ae292..badffce 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_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.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.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.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.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 @@
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 @@
ScreenshotNotificationsController screenshotNotificationsController,
ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
- DeviceConfigProxy configProxy,
ImageExporter imageExporter,
@Main Executor mainExecutor) {
mScreenshotSmartActions = screenshotSmartActions;
@@ -254,7 +248,6 @@
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 @@
// 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/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 5c650ad..d15f1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -142,6 +142,8 @@
private ScreenshotViewCallback mCallbacks;
private Animator mDismissAnimation;
private boolean mPendingSharedTransition;
+ private GestureDetector mSwipeDetector;
+ private SwipeDismissHandler mSwipeDismissHandler;
private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
@@ -181,6 +183,23 @@
mContext.getDisplay().getRealMetrics(mDisplayMetrics);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+
+ mSwipeDetector = new GestureDetector(mContext,
+ new GestureDetector.SimpleOnGestureListener() {
+ final Rect mActionsRect = new Rect();
+
+ @Override
+ public boolean onScroll(
+ MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
+ mActionsContainer.getBoundsOnScreen(mActionsRect);
+ // return true if we aren't in the actions bar, or if we are but it isn't
+ // scrollable in the direction of movement
+ return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
+ || !mActionsContainer.canScrollHorizontally((int) distanceX);
+ }
+ });
+ mSwipeDetector.setIsLongpressEnabled(false);
+ mSwipeDismissHandler = new SwipeDismissHandler();
}
/**
@@ -230,6 +249,15 @@
inoutInfo.touchableRegion.set(touchRegion);
}
+ @Override // ViewGroup
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // always pass through the down event so the swipe handler knows the initial state
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mSwipeDismissHandler.onTouch(this, ev);
+ }
+ return mSwipeDetector.onTouchEvent(ev);
+ }
+
@Override // View
protected void onFinishInflate() {
mScreenshotStatic = requireNonNull(findViewById(R.id.global_screenshot_static));
@@ -455,11 +483,7 @@
createScreenshotActionsShadeAnimation().start();
- SwipeDismissHandler swipeDismissHandler = new SwipeDismissHandler();
- mScreenshotPreview.setOnTouchListener(swipeDismissHandler);
- mActionsContainer.setOnTouchListener(swipeDismissHandler);
- mActionsContainerBackground.setOnTouchListener(swipeDismissHandler);
- mBackgroundProtection.setOnTouchListener(swipeDismissHandler);
+ setOnTouchListener(mSwipeDismissHandler);
}
});
@@ -776,30 +800,16 @@
}
class SwipeDismissHandler implements OnTouchListener {
-
- // if distance moved on ACTION_UP is less than this, register a click
- // otherwise, run return animator
- private static final float CLICK_MOVEMENT_THRESHOLD_DP = 1;
// distance needed to register a dismissal
private static final float DISMISS_DISTANCE_THRESHOLD_DP = 30;
private final GestureDetector mGestureDetector;
- private final float mDismissStartX;
- private final Rect mActionsRect = new Rect();
private float mStartX;
- private float mStartY;
- private float mTranslationX = 0;
-
- // tracks whether mStartX has been set for this interaction
- private boolean mInteractionStarted = false;
- // tracks whether we're dragging the UI (as opposed to scrolling the actions bar)
- private boolean mIsDragging = false;
SwipeDismissHandler() {
GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
mGestureDetector = new GestureDetector(mContext, gestureListener);
- mDismissStartX = mDismissButton.getX();
}
@Override
@@ -807,13 +817,9 @@
boolean gestureResult = mGestureDetector.onTouchEvent(event);
mCallbacks.onUserInteraction();
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mInteractionStarted = true;
mStartX = event.getRawX();
- mStartY = event.getRawY();
return true;
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- mInteractionStarted = false;
- mIsDragging = false;
if (isPastDismissThreshold()
&& (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
if (DEBUG_INPUT) {
@@ -821,87 +827,47 @@
}
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED);
animateDismissal(createSwipeDismissAnimation());
- return true;
- } else if (MathUtils.dist(mStartX, mStartY, event.getRawX(), event.getRawY())
- > dpToPx(CLICK_MOVEMENT_THRESHOLD_DP)) {
- // if we've moved a non-negligible distance (but not past the threshold),
+ } else {
+ // if we've moved, but not past the threshold, start the return animation
if (DEBUG_DISMISS) {
Log.d(TAG, "swipe gesture abandoned");
}
- // start the return animation
if ((mDismissAnimation == null || !mDismissAnimation.isRunning())) {
createSwipeReturnAnimation().start();
}
- return true;
- } else {
- if (view == mScreenshotPreview) {
- mScreenshotPreview.performClick();
- }
}
+ return true;
}
return gestureResult;
}
class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
-
- /**
- * This is somewhat complicated to handle because we want to allow scrolling the actions
- * bar (if it extends off the screen) as well as dismissing the UI horizontally by
- * dragging the actions bar. In addition, we don't get the initial ACTION_DOWN because
- * it is consumed by the action bar view.
- *
- * So, we use a gated system: first, keep track of the pointer location as we move;
- * next, check whether the actions bar can scroll in the direction we're moving in. If
- * it can, let the actions bar handle the event; otherwise, we've gone as far as we can
- * and can start dragging the UI instead.
- */
@Override
public boolean onScroll(
MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
- mActionsContainer.getBoundsOnScreen(mActionsRect);
- if (!mInteractionStarted) {
- if (mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())) {
- mStartX = ev2.getRawX();
- mInteractionStarted = true;
- }
- } else {
- float distance = ev2.getRawX() - mStartX;
- if ((mIsDragging && distance * mTranslationX > 0)
- || !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
- || !mActionsContainer.canScrollHorizontally(-1 * (int) distance)) {
- mIsDragging = true;
- mTranslationX = distance;
- mScreenshotStatic.setTranslationX(mTranslationX);
- return true;
- } else {
- mStartX = ev2.getRawX();
- }
- }
- return false;
+ mScreenshotStatic.setTranslationX(ev2.getRawX() - mStartX);
+ return true;
}
}
private boolean isPastDismissThreshold() {
- if (mDirectionLTR) {
- return mTranslationX <= -1 * dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
- } else {
- return mTranslationX >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
- }
+ float distance = Math.abs(mScreenshotStatic.getTranslationX());
+ return distance >= dpToPx(DISMISS_DISTANCE_THRESHOLD_DP);
}
private ValueAnimator createSwipeDismissAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mTranslationX;
- float finalX = mDirectionLTR
- ? -1 * (mActionsContainerBackground.getX()
- + mActionsContainerBackground.getWidth())
+ float startX = mScreenshotStatic.getTranslationX();
+ // make sure the UI gets all the way off the screen in the direction of movement
+ // (the actions container background is guaranteed to be both the leftmost and
+ // rightmost UI element in LTR and RTL)
+ float finalX = startX < 0
+ ? -1 * mActionsContainerBackground.getRight()
: mDisplayMetrics.widthPixels;
anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(startX, finalX,
- animation.getAnimatedFraction());
+ float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
mScreenshotStatic.setTranslationX(translation);
-
setAlpha(1 - animation.getAnimatedFraction());
});
anim.setDuration(400);
@@ -910,9 +876,8 @@
private ValueAnimator createSwipeReturnAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mTranslationX;
+ float startX = mScreenshotStatic.getTranslationX();
float finalX = 0;
- mTranslationX = 0;
anim.addUpdateListener(animation -> {
float translation = MathUtils.lerp(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index ca3923f..c565a271 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -38,8 +38,6 @@
import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.Properties;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
@@ -48,7 +46,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
@@ -147,23 +144,6 @@
private ImageView mBackdropFront;
private ImageView mBackdropBack;
- private boolean mShowCompactMediaSeekbar;
- private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(Properties properties) {
- for (String name : properties.getKeyset()) {
- if (SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED.equals(name)) {
- String value = properties.getString(name, null);
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: compact media seekbar flag updated: " + value);
- }
- mShowCompactMediaSeekbar = "true".equals(value);
- }
- }
- }
- };
-
private final MediaController.Callback mMediaListener = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -231,14 +211,6 @@
setupNotifPipeline();
mUsingNotifPipeline = true;
}
-
- mShowCompactMediaSeekbar = "true".equals(
- DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
-
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mContext.getMainExecutor(),
- mPropertiesChangedListener);
}
private void setupNotifPipeline() {
@@ -405,10 +377,6 @@
return mMediaMetadata;
}
- public boolean getShowCompactMediaSeekbar() {
- return mShowCompactMediaSeekbar;
- }
-
public Icon getMediaIcon() {
if (mMediaNotificationKey == null) {
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index ead6d32..e27c1a2 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 @@
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/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 2aba103..cc7a4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -16,7 +16,6 @@
import android.content.Context;
import android.graphics.Rect;
-import android.net.ConnectivityManager;
import android.os.Bundle;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -123,8 +122,7 @@
.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
setVisibility(showOperatorName ? VISIBLE : GONE);
- boolean hasMobile = ConnectivityManager.from(mContext)
- .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
if (!hasMobile || airplaneMode) {
setText(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
deleted file mode 100644
index f5a76f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageGradientColorizer.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2017 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.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-
-/**
- * A utility class to colorize bitmaps with a color gradient and a special blending mode
- */
-public class ImageGradientColorizer {
- public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
- int width = drawable.getIntrinsicWidth();
- int height = drawable.getIntrinsicHeight();
- int size = Math.min(width, height);
- int widthInset = (width - size) / 2;
- int heightInset = (height - size) / 2;
- drawable = drawable.mutate();
- drawable.setBounds(- widthInset, - heightInset, width - widthInset, height - heightInset);
- Bitmap newBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(newBitmap);
-
- // Values to calculate the luminance of a color
- float lr = 0.2126f;
- float lg = 0.7152f;
- float lb = 0.0722f;
-
- // Extract the red, green, blue components of the color extraction color in
- // float and int form
- int tri = Color.red(backgroundColor);
- int tgi = Color.green(backgroundColor);
- int tbi = Color.blue(backgroundColor);
-
- float tr = tri / 255f;
- float tg = tgi / 255f;
- float tb = tbi / 255f;
-
- // Calculate the luminance of the color extraction color
- float cLum = (tr * lr + tg * lg + tb * lb) * 255;
-
- ColorMatrix m = new ColorMatrix(new float[] {
- lr, lg, lb, 0, tri - cLum,
- lr, lg, lb, 0, tgi - cLum,
- lr, lg, lb, 0, tbi - cLum,
- 0, 0, 0, 1, 0,
- });
-
- Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- LinearGradient linearGradient = new LinearGradient(0, 0, size, 0,
- new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
- new float[] {0.0f, 0.4f, 1.0f}, Shader.TileMode.CLAMP);
- paint.setShader(linearGradient);
- Bitmap fadeIn = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas fadeInCanvas = new Canvas(fadeIn);
- drawable.clearColorFilter();
- drawable.draw(fadeInCanvas);
-
- if (isRtl) {
- // Let's flip the gradient
- fadeInCanvas.translate(size, 0);
- fadeInCanvas.scale(-1, 1);
- }
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
- fadeInCanvas.drawPaint(paint);
-
- Paint coloredPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- coloredPaint.setColorFilter(new ColorMatrixColorFilter(m));
- coloredPaint.setAlpha((int) (0.5f * 255));
- canvas.drawBitmap(fadeIn, 0, 0, coloredPaint);
-
- linearGradient = new LinearGradient(0, 0, size, 0,
- new int[] {0, Color.argb(0.5f, 1, 1, 1), Color.BLACK},
- new float[] {0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP);
- paint.setShader(linearGradient);
- fadeInCanvas.drawPaint(paint);
- canvas.drawBitmap(fadeIn, 0, 0, null);
-
- return newBitmap;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
index 2586e94..732c115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
@@ -16,237 +16,30 @@
package com.android.systemui.statusbar.notification;
-import android.app.Notification;
-import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.util.LayoutDirection;
-import androidx.annotation.VisibleForTesting;
import androidx.palette.graphics.Palette;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.settingslib.Utils;
-
import java.util.List;
/**
- * A class the processes media notifications and extracts the right text and background colors.
+ * A gutted class that now contains only a color extraction utility used by the
+ * MediaArtworkProcessor, which has otherwise supplanted this.
+ *
+ * TODO(b/182926117): move this into MediaArtworkProcessor.kt
*/
public class MediaNotificationProcessor {
/**
- * The fraction below which we select the vibrant instead of the light/dark vibrant color
- */
- private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f;
-
- /**
- * Minimum saturation that a muted color must have if there exists if deciding between two
- * colors
- */
- private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f;
-
- /**
- * Minimum fraction that any color must have to be picked up as a text color
- */
- private static final double MINIMUM_IMAGE_FRACTION = 0.002;
-
- /**
- * The population fraction to select the dominant color as the text color over a the colored
- * ones.
- */
- private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f;
-
- /**
* The population fraction to select a white or black color as the background over a color.
*/
private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.90f;
private static final int RESIZE_BITMAP_AREA = 150 * 150;
- private final ImageGradientColorizer mColorizer;
- private final Context mContext;
- private final Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl);
- /**
- * The context of the notification. This is the app context of the package posting the
- * notification.
- */
- private final Context mPackageContext;
-
- public MediaNotificationProcessor(Context context, Context packageContext) {
- this(context, packageContext, new ImageGradientColorizer());
- }
-
- @VisibleForTesting
- MediaNotificationProcessor(Context context, Context packageContext,
- ImageGradientColorizer colorizer) {
- mContext = context;
- mPackageContext = packageContext;
- mColorizer = colorizer;
- }
-
- /**
- * Processes a builder of a media notification and calculates the appropriate colors that should
- * be used.
- *
- * @param notification the notification that is being processed
- * @param builder the recovered builder for the notification. this will be modified
- */
- public void processNotification(Notification notification, Notification.Builder builder) {
- Icon largeIcon = notification.getLargeIcon();
- Bitmap bitmap = null;
- Drawable drawable = null;
- if (largeIcon != null) {
- // We're transforming the builder, let's make sure all baked in RemoteViews are
- // rebuilt!
- builder.setRebuildStyledRemoteViews(true);
- drawable = largeIcon.loadDrawable(mPackageContext);
- int backgroundColor = 0;
- if (notification.isColorizedMedia()) {
- int width = drawable.getIntrinsicWidth();
- int height = drawable.getIntrinsicHeight();
- int area = width * height;
- if (area > RESIZE_BITMAP_AREA) {
- double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area);
- width = (int) (factor * width);
- height = (int) (factor * height);
- }
- bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, width, height);
- drawable.draw(canvas);
-
- Palette.Builder paletteBuilder = generateArtworkPaletteBuilder(bitmap);
- Palette palette = paletteBuilder.generate();
- Palette.Swatch backgroundSwatch = findBackgroundSwatch(palette);
- backgroundColor = backgroundSwatch.getRgb();
- // we want most of the full region again, slightly shifted to the right
- float textColorStartWidthFraction = 0.4f;
- paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0,
- bitmap.getWidth(),
- bitmap.getHeight());
- // We're not filtering on white or black
- if (!isWhiteOrBlack(backgroundSwatch.getHsl())) {
- final float backgroundHue = backgroundSwatch.getHsl()[0];
- paletteBuilder.addFilter((rgb, hsl) -> {
- // at least 10 degrees hue difference
- float diff = Math.abs(hsl[0] - backgroundHue);
- return diff > 10 && diff < 350;
- });
- }
- paletteBuilder.addFilter(mBlackWhiteFilter);
- palette = paletteBuilder.generate();
- int foregroundColor = selectForegroundColor(backgroundColor, palette);
- builder.setColorPalette(backgroundColor, foregroundColor);
- } else {
- backgroundColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground)
- .getDefaultColor();
- }
- Bitmap colorized = mColorizer.colorize(drawable, backgroundColor,
- mContext.getResources().getConfiguration().getLayoutDirection() ==
- LayoutDirection.RTL);
- builder.setLargeIcon(Icon.createWithBitmap(colorized));
- }
- }
-
- /**
- * Select a foreground color depending on whether the background color is dark or light
- * @param backgroundColor Background color to coordinate with
- * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
- * @return foreground color
- */
- public static int selectForegroundColor(int backgroundColor, Palette palette) {
- if (ContrastColorUtil.isColorLight(backgroundColor)) {
- return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(),
- palette.getVibrantSwatch(),
- palette.getDarkMutedSwatch(),
- palette.getMutedSwatch(),
- palette.getDominantSwatch(),
- Color.BLACK);
- } else {
- return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(),
- palette.getVibrantSwatch(),
- palette.getLightMutedSwatch(),
- palette.getMutedSwatch(),
- palette.getDominantSwatch(),
- Color.WHITE);
- }
- }
-
- private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
- Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch,
- Palette.Swatch dominantSwatch, int fallbackColor) {
- Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
- if (coloredCandidate == null) {
- coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch);
- }
- if (coloredCandidate != null) {
- if (dominantSwatch == coloredCandidate) {
- return coloredCandidate.getRgb();
- } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation()
- < POPULATION_FRACTION_FOR_DOMINANT
- && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) {
- return dominantSwatch.getRgb();
- } else {
- return coloredCandidate.getRgb();
- }
- } else if (hasEnoughPopulation(dominantSwatch)) {
- return dominantSwatch.getRgb();
- } else {
- return fallbackColor;
- }
- }
-
- private static Palette.Swatch selectMutedCandidate(Palette.Swatch first,
- Palette.Swatch second) {
- boolean firstValid = hasEnoughPopulation(first);
- boolean secondValid = hasEnoughPopulation(second);
- if (firstValid && secondValid) {
- float firstSaturation = first.getHsl()[1];
- float secondSaturation = second.getHsl()[1];
- float populationFraction = first.getPopulation() / (float) second.getPopulation();
- if (firstSaturation * populationFraction > secondSaturation) {
- return first;
- } else {
- return second;
- }
- } else if (firstValid) {
- return first;
- } else if (secondValid) {
- return second;
- }
- return null;
- }
-
- private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first,
- Palette.Swatch second) {
- boolean firstValid = hasEnoughPopulation(first);
- boolean secondValid = hasEnoughPopulation(second);
- if (firstValid && secondValid) {
- int firstPopulation = first.getPopulation();
- int secondPopulation = second.getPopulation();
- if (firstPopulation / (float) secondPopulation
- < POPULATION_FRACTION_FOR_MORE_VIBRANT) {
- return second;
- } else {
- return first;
- }
- } else if (firstValid) {
- return first;
- } else if (secondValid) {
- return second;
- }
- return null;
- }
-
- private static boolean hasEnoughPopulation(Palette.Swatch swatch) {
- // We want a fraction that is at least 1% of the image
- return swatch != null
- && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
+ private MediaNotificationProcessor() {
}
/**
@@ -279,7 +72,7 @@
List<Palette.Swatch> swatches = palette.getSwatches();
float highestNonWhitePopulation = -1;
Palette.Swatch second = null;
- for (Palette.Swatch swatch: swatches) {
+ for (Palette.Swatch swatch : swatches) {
if (swatch != dominantSwatch
&& swatch.getPopulation() > highestNonWhitePopulation
&& !isWhiteOrBlack(swatch.getHsl())) {
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 6cf5c30..a3a4014 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 @@
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();
@@ -668,7 +663,6 @@
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
boolean isMessagingLayout = contractedView instanceof MessagingLayout;
boolean isCallLayout = contractedView instanceof CallLayout;
- boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
if (customView && beforeS && !mIsSummaryWithChildren) {
if (beforeN) {
@@ -678,12 +672,6 @@
} else {
smallHeight = mMaxSmallHeightBeforeS;
}
- } else if (isMediaLayout) {
- // TODO(b/172652345): MediaStyle notifications currently look broken when we enforce
- // the standard notification height, so we have to afford them more vertical space to
- // make sure we don't crop them terribly. We actually need to revisit this and give
- // them a headerless design, then remove this hack.
- smallHeight = showCompactMediaSeekbar ? mMaxSmallHeightMedia : mMaxSmallHeightBeforeS;
} else if (isMessagingLayout) {
// TODO(b/173204301): MessagingStyle notifications currently look broken when we enforce
// the standard notification height, so we have to afford them more vertical space to
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 3728388..609ca97c 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.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 @@
}
};
+ /**
+ * 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 @@
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 73e0804..1086d67 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 @@
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 @@
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 @@
@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 @@
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/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 58b87cd..73c4b05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -40,13 +40,11 @@
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaDataManagerKt;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -799,13 +797,6 @@
// For all of our templates, we want it to be RTL
packageContext = new RtlEnabledContext(packageContext);
}
- Notification notification = sbn.getNotification();
- if (notification.isMediaNotification() && !(mIsMediaInQS
- && MediaDataManagerKt.isMediaNotification(sbn))) {
- MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
- packageContext);
- processor.processNotification(notification, recoveredBuilder);
- }
if (mEntry.getRanking().isConversation()) {
mConversationProcessor.processNotification(mEntry, recoveredBuilder);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 2535e5d..c75cd78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -16,341 +16,26 @@
package com.android.systemui.statusbar.notification.row.wrapper;
-import static com.android.systemui.Dependency.MAIN_HANDLER;
-
-import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewStub;
-import android.widget.SeekBar;
-import android.widget.TextView;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.widget.MediaNotificationView;
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import java.util.Timer;
-import java.util.TimerTask;
-
/**
* Wraps a notification containing a media template
*/
public class NotificationMediaTemplateViewWrapper extends NotificationTemplateViewWrapper {
- private static final long PROGRESS_UPDATE_INTERVAL = 1000; // 1s
- private static final String COMPACT_MEDIA_TAG = "media";
- private final Handler mHandler = Dependency.get(MAIN_HANDLER);
- private Timer mSeekBarTimer;
private View mActions;
- private SeekBar mSeekBar;
- private TextView mSeekBarElapsedTime;
- private TextView mSeekBarTotalTime;
- private long mDuration = 0;
- private MediaController mMediaController;
- private MediaMetadata mMediaMetadata;
- private NotificationMediaManager mMediaManager;
- private View mSeekBarView;
- private Context mContext;
- private MetricsLogger mMetricsLogger;
- private boolean mIsViewVisible;
-
- @VisibleForTesting
- protected SeekBar.OnSeekBarChangeListener mSeekListener =
- new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (mMediaController != null) {
- mMediaController.getTransportControls().seekTo(mSeekBar.getProgress());
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_UPDATE));
- }
- }
- };
-
- private MediaNotificationView.VisibilityChangeListener mVisibilityListener =
- new MediaNotificationView.VisibilityChangeListener() {
- @Override
- public void onAggregatedVisibilityChanged(boolean isVisible) {
- mIsViewVisible = isVisible;
- if (isVisible && mMediaController != null) {
- // Restart timer if we're currently playing and didn't already have one going
- PlaybackState state = mMediaController.getPlaybackState();
- if (state != null && state.getState() == PlaybackState.STATE_PLAYING
- && mSeekBarTimer == null && mSeekBarView != null
- && mSeekBarView.getVisibility() != View.GONE) {
- startTimer();
- }
- } else {
- clearTimer();
- }
- }
- };
-
- private View.OnAttachStateChangeListener mAttachStateListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mIsViewVisible = false;
- }
- };
-
- private MediaController.Callback mMediaCallback = new MediaController.Callback() {
- @Override
- public void onSessionDestroyed() {
- clearTimer();
- mMediaController.unregisterCallback(this);
- if (mView instanceof MediaNotificationView) {
- ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
- mView.removeOnAttachStateChangeListener(mAttachStateListener);
- }
- }
-
- @Override
- public void onPlaybackStateChanged(@Nullable PlaybackState state) {
- if (state == null) {
- return;
- }
-
- if (state.getState() != PlaybackState.STATE_PLAYING) {
- // Update the UI once, in case playback info changed while we were paused
- updatePlaybackUi(state);
- clearTimer();
- } else if (mSeekBarTimer == null && mSeekBarView != null
- && mSeekBarView.getVisibility() != View.GONE) {
- startTimer();
- }
- }
-
- @Override
- public void onMetadataChanged(@Nullable MediaMetadata metadata) {
- if (mMediaMetadata == null || !mMediaMetadata.equals(metadata)) {
- mMediaMetadata = metadata;
- updateDuration();
- }
- }
- };
protected NotificationMediaTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
- mContext = ctx;
- mMediaManager = Dependency.get(NotificationMediaManager.class);
- mMetricsLogger = Dependency.get(MetricsLogger.class);
}
private void resolveViews() {
mActions = mView.findViewById(com.android.internal.R.id.media_actions);
- mIsViewVisible = mView.isShown();
-
- final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
- .getParcelable(Notification.EXTRA_MEDIA_SESSION);
-
- boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
- if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
- if (mSeekBarView != null) {
- mSeekBarView.setVisibility(View.GONE);
- }
- return;
- }
-
- // Check for existing media controller and clean up / create as necessary
- boolean shouldUpdateListeners = false;
- if (mMediaController == null || !mMediaController.getSessionToken().equals(token)) {
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaCallback);
- }
- mMediaController = new MediaController(mContext, token);
- shouldUpdateListeners = true;
- }
-
- mMediaMetadata = mMediaController.getMetadata();
- if (mMediaMetadata != null) {
- long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (duration <= 0) {
- // Don't include the seekbar if this is a livestream
- if (mSeekBarView != null && mSeekBarView.getVisibility() != View.GONE) {
- mSeekBarView.setVisibility(View.GONE);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
- clearTimer();
- } else if (mSeekBarView == null && shouldUpdateListeners) {
- // Only log if the controller changed, otherwise we would log multiple times for
- // the same notification when user pauses/resumes
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
- }
- return;
- } else if (mSeekBarView != null && mSeekBarView.getVisibility() == View.GONE) {
- // Otherwise, make sure the seekbar is visible
- mSeekBarView.setVisibility(View.VISIBLE);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
- updateDuration();
- startTimer();
- }
- }
-
- // Inflate the seekbar template
- ViewStub stub = mView.findViewById(R.id.notification_media_seekbar_container);
- if (stub instanceof ViewStub) {
- LayoutInflater layoutInflater = LayoutInflater.from(stub.getContext());
- stub.setLayoutInflater(layoutInflater);
- stub.setLayoutResource(R.layout.notification_material_media_seekbar);
- mSeekBarView = stub.inflate();
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
-
- mSeekBar = mSeekBarView.findViewById(R.id.notification_media_progress_bar);
- mSeekBar.setOnSeekBarChangeListener(mSeekListener);
-
- mSeekBarElapsedTime = mSeekBarView.findViewById(R.id.notification_media_elapsed_time);
- mSeekBarTotalTime = mSeekBarView.findViewById(R.id.notification_media_total_time);
-
- shouldUpdateListeners = true;
- }
-
- if (shouldUpdateListeners) {
- if (mView instanceof MediaNotificationView) {
- MediaNotificationView mediaView = (MediaNotificationView) mView;
- mediaView.addVisibilityListener(mVisibilityListener);
- mView.addOnAttachStateChangeListener(mAttachStateListener);
- }
-
- if (mSeekBarTimer == null) {
- if (mMediaController != null && canSeekMedia(mMediaController.getPlaybackState())) {
- // Log initial state, since it will not be updated
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, 1));
- } else {
- setScrubberVisible(false);
- }
- updateDuration();
- startTimer();
- mMediaController.registerCallback(mMediaCallback);
- }
- }
- updateSeekBarTint(mSeekBarView);
- }
-
- private void startTimer() {
- clearTimer();
- if (mIsViewVisible) {
- mSeekBarTimer = new Timer(true /* isDaemon */);
- mSeekBarTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- mHandler.post(mOnUpdateTimerTick);
- }
- }, 0, PROGRESS_UPDATE_INTERVAL);
- }
- }
-
- private void clearTimer() {
- if (mSeekBarTimer != null) {
- mSeekBarTimer.cancel();
- mSeekBarTimer.purge();
- mSeekBarTimer = null;
- }
- }
-
- @Override
- public void setRemoved() {
- clearTimer();
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mMediaCallback);
- }
- if (mView instanceof MediaNotificationView) {
- ((MediaNotificationView) mView).removeVisibilityListener(mVisibilityListener);
- mView.removeOnAttachStateChangeListener(mAttachStateListener);
- }
- }
-
- private boolean canSeekMedia(@Nullable PlaybackState state) {
- if (state == null) {
- return false;
- }
-
- long actions = state.getActions();
- return ((actions & PlaybackState.ACTION_SEEK_TO) != 0);
- }
-
- private void setScrubberVisible(boolean isVisible) {
- if (mSeekBar == null || mSeekBar.isEnabled() == isVisible) {
- return;
- }
-
- mSeekBar.getThumb().setAlpha(isVisible ? 255 : 0);
- mSeekBar.setEnabled(isVisible);
- mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, isVisible ? 1 : 0));
- }
-
- private void updateDuration() {
- if (mMediaMetadata != null && mSeekBar != null) {
- long duration = mMediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- if (mDuration != duration) {
- mDuration = duration;
- mSeekBar.setMax((int) mDuration);
- mSeekBarTotalTime.setText(millisecondsToTimeString(duration));
- }
- }
- }
-
- protected final Runnable mOnUpdateTimerTick = new Runnable() {
- @Override
- public void run() {
- if (mMediaController != null && mSeekBar != null) {
- PlaybackState playbackState = mMediaController.getPlaybackState();
- if (playbackState != null) {
- updatePlaybackUi(playbackState);
- } else {
- clearTimer();
- }
- } else {
- clearTimer();
- }
- }
- };
-
- private void updatePlaybackUi(PlaybackState state) {
- if (mSeekBar == null || mSeekBarElapsedTime == null) {
- return;
- }
-
- long position = state.getPosition();
- mSeekBar.setProgress((int) position);
-
- mSeekBarElapsedTime.setText(millisecondsToTimeString(position));
-
- // Update scrubber in case available actions have changed
- setScrubberVisible(canSeekMedia(state));
- }
-
- private String millisecondsToTimeString(long milliseconds) {
- long seconds = milliseconds / 1000;
- String text = DateUtils.formatElapsedTime(seconds);
- return text;
}
@Override
@@ -361,28 +46,6 @@
super.onContentUpdated(row);
}
- private void updateSeekBarTint(View seekBarContainer) {
- if (seekBarContainer == null) {
- return;
- }
-
- if (this.getNotificationHeader() == null) {
- return;
- }
-
- int tintColor = getOriginalIconColor();
- mSeekBarElapsedTime.setTextColor(tintColor);
- mSeekBarTotalTime.setTextColor(tintColor);
- mSeekBarTotalTime.setShadowLayer(1.5f, 1.5f, 1.5f, mBackgroundColor);
-
- ColorStateList tintList = ColorStateList.valueOf(tintColor);
- mSeekBar.setThumbTintList(tintList);
- tintList = tintList.withAlpha(192); // 75%
- mSeekBar.setProgressTintList(tintList);
- tintList = tintList.withAlpha(128); // 50%
- mSeekBar.setProgressBackgroundTintList(tintList);
- }
-
@Override
protected void updateTransformedTypes() {
// This also clears the existing types
@@ -394,36 +57,7 @@
}
@Override
- public boolean isDimmable() {
- return getCustomBackgroundColor() == 0;
- }
-
- @Override
public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
return true;
}
-
- /**
- * Returns an initialized LogMaker for logging changes to the seekbar
- * @return new LogMaker
- */
- private LogMaker newLog(int event) {
- String packageName = mRow.getEntry().getSbn().getPackageName();
-
- return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
- .setType(event)
- .setPackageName(packageName);
- }
-
- /**
- * Returns an initialized LogMaker for logging changes with subtypes
- * @return new LogMaker
- */
- private LogMaker newLog(int event, int subtype) {
- String packageName = mRow.getEntry().getSbn().getPackageName();
- return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
- .setType(event)
- .setSubtype(subtype)
- .setPackageName(packageName);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 39f5847c..562d0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -42,6 +42,9 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -70,6 +73,8 @@
private View mOperatorNameFrame;
private CommandQueue mCommandQueue;
+ private List<String> mBlockedIcons = new ArrayList<>();
+
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setIsAirplaneMode(NetworkController.IconState icon) {
@@ -101,9 +106,12 @@
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
- mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons),
- Dependency.get(CommandQueue.class));
+ mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
mDarkIconManager.setShouldLog(true);
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_volume));
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_alarm_clock));
+ mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_call_strength));
+ mDarkIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mClockView = mStatusBar.findViewById(R.id.clock);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 33798d6..2d760e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -47,7 +47,6 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -59,6 +58,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* The header group on Keyguard.
@@ -89,6 +90,7 @@
private int mSystemIconsBaseMargin;
private View mSystemIconsContainer;
private TintedIconManager mIconManager;
+ private List<String> mBlockedIcons = new ArrayList<>();
private View mCutoutSpace;
private ViewGroup mStatusIconArea;
@@ -121,6 +123,7 @@
mStatusIconContainer = findViewById(R.id.statusIcons);
loadDimens();
+ loadBlockList();
mBatteryController = Dependency.get(BatteryController.class);
}
@@ -181,6 +184,14 @@
R.dimen.rounded_corner_content_padding);
}
+ // Set hidden status bar items
+ private void loadBlockList() {
+ Resources r = getResources();
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_volume));
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_alarm_clock));
+ mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_call_strength));
+ }
+
private void updateVisibilities() {
if (mMultiUserAvatar.getParent() != mStatusIconArea
&& !mKeyguardUserSwitcherEnabled) {
@@ -336,8 +347,8 @@
userInfoController.addCallback(this);
userInfoController.reloadUserInfo();
Dependency.get(ConfigurationController.class).addCallback(this);
- mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
- Dependency.get(CommandQueue.class));
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
+ mIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
onThemeChanged();
}
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 0000000..377fb92
--- /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 4ef6668..d3da0bce 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.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 @@
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 @@
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 @@
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+ KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
QSDetailDisplayer qsDetailDisplayer,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
@@ -589,6 +593,7 @@
mGroupManager = groupManager;
mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
+ mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
mFeatureFlags = featureFlags;
mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
@@ -693,7 +698,9 @@
}
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 @@
}
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 @@
showKeyguardUserSwitcher /* enabled */);
mBigClockContainer.removeAllViews();
- updateViewControllers(keyguardStatusView, userAvatarView, keyguardUserSwitcherView);
+ updateViewControllers(
+ keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
// Update keyguard bottom area
index = mView.indexOfChild(mKeyguardBottomArea);
@@ -1325,6 +1341,9 @@
traceQsJank(true /* startTracing */, false /* wasCancelled */);
flingSettings(0 /* velocity */, FLING_EXPAND);
mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0);
+ if (mAccessibilityManager.isEnabled()) {
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
}
public void expandWithoutQs() {
@@ -1824,6 +1843,7 @@
mNotificationContainerParent.setQsExpanded(expanded);
mPulseExpansionHandler.setQsExpanded(expanded);
mKeyguardBypassController.setQSExpanded(expanded);
+ mStatusBarKeyguardViewManager.setQsExpanded(expanded);
}
}
@@ -3389,7 +3409,7 @@
return new TouchHandler() {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
return true;
}
if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
@@ -3420,8 +3440,8 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
final boolean showingOrAnimatingAltAuth =
- mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating();
- if (showingOrAnimatingAltAuth) {
+ mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating();
+ if (showingOrAnimatingAltAuth && event.getAction() == MotionEvent.ACTION_DOWN) {
mStatusBarKeyguardViewManager.resetAlternateAuth();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index ed4f324..77abe79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -454,7 +454,7 @@
mView.postOnAnimation(mPostCollapseRunnable);
}
} else if (!mStatusBar.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
boolean expands = onEmptySpaceClick(mInitialTouchX);
onTrackingStopped(expands);
}
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 0e1fe22..c83b60d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4224,7 +4224,7 @@
mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
- if (mStatusBarKeyguardViewManager.isShowingAlternativeAuth()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
} else if (mBouncerShowing) {
// Bouncer needs the front scrim when it's on top of an activity,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 8fe9a48..93b83d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
@@ -37,7 +38,6 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import java.util.ArrayList;
import java.util.List;
public interface StatusBarIconController {
@@ -54,15 +55,22 @@
* When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
* in that slot and not added as a sub slot.
*/
- public static final int TAG_PRIMARY = 0;
+ int TAG_PRIMARY = 0;
- public void addIconGroup(IconManager iconManager);
- public void removeIconGroup(IconManager iconManager);
- public void setExternalIcon(String slot);
- public void setIcon(String slot, int resourceId, CharSequence contentDescription);
- public void setIcon(String slot, StatusBarIcon icon);
- public void setSignalIcon(String slot, WifiIconState state);
- public void setMobileIcons(String slot, List<MobileIconState> states);
+ /** */
+ void addIconGroup(IconManager iconManager);
+ /** */
+ void removeIconGroup(IconManager iconManager);
+ /** */
+ void setExternalIcon(String slot);
+ /** */
+ void setIcon(String slot, int resourceId, CharSequence contentDescription);
+ /** */
+ void setIcon(String slot, StatusBarIcon icon);
+ /** */
+ void setSignalIcon(String slot, WifiIconState state);
+ /** */
+ void setMobileIcons(String slot, List<MobileIconState> states);
/**
* Display the no calling & SMS icons.
*/
@@ -85,8 +93,9 @@
* If you don't know what to pass for `tag`, either remove all icons for slot, or use
* TAG_PRIMARY to refer to the first icon at a given slot.
*/
- public void removeIcon(String slot, int tag);
- public void removeAllIconsForSlot(String slot);
+ void removeIcon(String slot, int tag);
+ /** */
+ void removeAllIconsForSlot(String slot);
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
@@ -108,12 +117,12 @@
/**
* Version of ViewGroup that observes state from the DarkIconDispatcher.
*/
- public static class DarkIconManager extends IconManager {
+ class DarkIconManager extends IconManager {
private final DarkIconDispatcher mDarkIconDispatcher;
private int mIconHPadding;
- public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) {
- super(linearLayout, commandQueue);
+ public DarkIconManager(LinearLayout linearLayout) {
+ super(linearLayout);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -169,11 +178,12 @@
}
}
- public static class TintedIconManager extends IconManager {
+ /** */
+ class TintedIconManager extends IconManager {
private int mColor;
- public TintedIconManager(ViewGroup group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ public TintedIconManager(ViewGroup group) {
+ super(group);
}
@Override
@@ -219,7 +229,9 @@
private boolean mIsInDemoMode;
protected DemoStatusIcons mDemoStatusIcons;
- public IconManager(ViewGroup group, CommandQueue commandQueue) {
+ protected ArrayList<String> mBlockList = new ArrayList<>();
+
+ public IconManager(ViewGroup group) {
mGroup = group;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -234,6 +246,15 @@
mDemoable = demoable;
}
+ public void setBlockList(@Nullable List<String> blockList) {
+ mBlockList.clear();
+ if (blockList == null || blockList.isEmpty()) {
+ return;
+ }
+
+ mBlockList.addAll(blockList);
+ }
+
public void setShouldLog(boolean should) {
mShouldLog = should;
}
@@ -249,6 +270,11 @@
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
+ // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
+ // that is in our blocked list, then we'll never see it
+ if (mBlockList.contains(slot)) {
+ blocked = true;
+ }
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 6404aea..75900a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -66,6 +66,7 @@
private Context mContext;
+ /** */
@Inject
public StatusBarIconControllerImpl(
Context context,
@@ -84,6 +85,7 @@
demoModeController.addCallback(this);
}
+ /** */
@Override
public void addIconGroup(IconManager group) {
mIconGroups.add(group);
@@ -101,12 +103,14 @@
}
}
+ /** */
@Override
public void removeIconGroup(IconManager group) {
group.destroy();
mIconGroups.remove(group);
}
+ /** */
@Override
public void onTuningChanged(String key, String newValue) {
if (!ICON_HIDE_LIST.equals(key)) {
@@ -149,6 +153,7 @@
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
}
+ /** */
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
int index = getSlotIndex(slot);
@@ -290,8 +295,9 @@
* For backwards compatibility, in the event that someone gives us a slot and a status bar icon
*/
private void setIcon(int index, StatusBarIcon icon) {
+ String slot = getSlotName(index);
if (icon == null) {
- removeAllIconsForSlot(getSlotName(index));
+ removeAllIconsForSlot(slot);
return;
}
@@ -299,6 +305,7 @@
setIcon(index, holder);
}
+ /** */
@Override
public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
boolean isNew = getIcon(index, holder.getTag()) == null;
@@ -328,6 +335,7 @@
handleSet(index, holder);
}
+ /** */
@Override
public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) {
Slot slot = getSlot(slotName);
@@ -344,15 +352,18 @@
}
}
+ /** */
public void removeIcon(String slot) {
removeAllIconsForSlot(slot);
}
+ /** */
@Override
public void removeIcon(String slot, int tag) {
removeIcon(getSlotIndex(slot), tag);
}
+ /** */
@Override
public void removeAllIconsForSlot(String slotName) {
Slot slot = getSlot(slotName);
@@ -369,6 +380,7 @@
}
}
+ /** */
@Override
public void removeIcon(int index, int tag) {
if (getIcon(index, tag) == null) {
@@ -384,6 +396,7 @@
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
+ /** */
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(TAG + " state:");
@@ -402,6 +415,7 @@
super.dump(pw);
}
+ /** */
@Override
public void onDemoModeStarted() {
for (IconManager manager : mIconGroups) {
@@ -411,6 +425,7 @@
}
}
+ /** */
@Override
public void onDemoModeFinished() {
for (IconManager manager : mIconGroups) {
@@ -420,6 +435,7 @@
}
}
+ /** */
@Override
public void dispatchDemoCommand(String command, Bundle args) {
for (IconManager manager : mIconGroups) {
@@ -429,6 +445,7 @@
}
}
+ /** */
@Override
public List<String> demoCommands() {
List<String> s = new ArrayList<>();
@@ -436,6 +453,7 @@
return s;
}
+ /** */
@Override
public void onDensityOrFontScaleChanged() {
loadDimens();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 19db02a..af342dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -39,7 +39,10 @@
private MobileIconState mMobileState;
private int mType = TYPE_ICON;
private int mTag = 0;
- private boolean mVisible = true;
+
+ private StatusBarIconHolder() {
+
+ }
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
StatusBarIconHolder wrapper = new StatusBarIconHolder();
@@ -48,7 +51,10 @@
return wrapper;
}
- public static StatusBarIconHolder fromResId(Context context, int resId,
+ /** */
+ public static StatusBarIconHolder fromResId(
+ Context context,
+ int resId,
CharSequence contentDescription) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
@@ -56,6 +62,7 @@
return holder;
}
+ /** */
public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mWifiState = state;
@@ -63,6 +70,7 @@
return holder;
}
+ /** */
public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
holder.mMobileState = state;
@@ -75,7 +83,8 @@
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
public static StatusBarIconHolder fromCallIndicatorState(
- Context context, CallIndicatorIconState state) {
+ Context context,
+ CallIndicatorIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
String contentDescription = state.isNoCalling
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c1f300b..7ee7aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -476,11 +476,12 @@
return;
}
- if (mAlternateAuthInterceptor != null
- && mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
- mStatusBar.updateScrimController();
+ if (mAlternateAuthInterceptor != null) {
mAfterKeyguardGoneAction = r;
mKeyguardGoneCancelAction = cancelAction;
+ if (mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
+ mStatusBar.updateScrimController();
+ }
return;
}
@@ -528,7 +529,7 @@
* Stop showing any alternate auth methods
*/
public void resetAlternateAuth() {
- if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.reset()) {
+ if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.resetForceShow()) {
mStatusBar.updateScrimController();
}
}
@@ -1125,18 +1126,27 @@
setDozing(isDozing);
}
+ /**
+ * Set whether qs is currently expanded
+ */
+ public void setQsExpanded(boolean expanded) {
+ if (mAlternateAuthInterceptor != null) {
+ mAlternateAuthInterceptor.setQsExpanded(expanded);
+ }
+ }
+
public KeyguardBouncer getBouncer() {
return mBouncer;
}
- public boolean isShowingAlternativeAuth() {
+ public boolean isShowingAlternateAuth() {
return mAlternateAuthInterceptor != null
- && mAlternateAuthInterceptor.isShowingAlternativeAuth();
+ && mAlternateAuthInterceptor.isShowingAlternateAuth();
}
- public boolean isShowingAlternativeAuthOrAnimating() {
+ public boolean isShowingAlternateAuthOrAnimating() {
return mAlternateAuthInterceptor != null
- && (mAlternateAuthInterceptor.isShowingAlternativeAuth()
+ && (mAlternateAuthInterceptor.isShowingAlternateAuth()
|| mAlternateAuthInterceptor.isAnimating());
}
@@ -1169,12 +1179,12 @@
* reset the state to the default (only keyguard showing, no auth methods showing)
* @return whether alternative auth method was newly hidden
*/
- boolean reset();
+ boolean resetForceShow();
/**
* @return true if alternative auth method is showing
*/
- boolean isShowingAlternativeAuth();
+ boolean isShowingAlternateAuth();
/**
* print information for the alternate auth interceptor registered
@@ -1185,5 +1195,10 @@
* @return true if the new auth method is currently animating in or out.
*/
boolean isAnimating();
+
+ /**
+ * Set whether qs is currently expanded
+ */
+ void setQsExpanded(boolean expanded);
}
}
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 525f220..94edd1e 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.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 @@
boolean removedByUser,
int reason) {
StatusBarNotificationPresenter.this.onNotificationRemoved(
- entry.getKey(), entry.getSbn());
+ entry.getKey(), entry.getSbn(), reason);
if (removedByUser) {
maybeEndAmbientPulse();
}
@@ -301,13 +302,14 @@
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/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 5dc9104..f2ee858 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
@@ -95,8 +94,7 @@
}
public void update() {
- boolean hasMobile = ConnectivityManager.from(mContext)
- .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
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 4615877..fd37d89 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 @@
}
@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 db039b4..cfaeb0e 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_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.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,11 +73,13 @@
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;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
@@ -107,6 +108,7 @@
private final Context mContext;
private final TelephonyManager mPhone;
+ private final TelephonyListenerManager mTelephonyListenerManager;
private final WifiManager mWifiManager;
private final ConnectivityManager mConnectivityManager;
private final SubscriptionManager mSubscriptionManager;
@@ -120,7 +122,7 @@
private final boolean mProviderModel;
private Config mConfig;
- private PhoneStateListener mPhoneStateListener;
+ private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
// Subcontrollers.
@@ -200,12 +202,14 @@
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,
@@ -221,7 +225,9 @@
@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,
@@ -232,6 +238,7 @@
BroadcastDispatcher broadcastDispatcher,
DemoModeController demoModeController) {
mContext = context;
+ mTelephonyListenerManager = telephonyListenerManager;
mConfig = config;
mReceiverHandler = new Handler(bgLooper);
mCallbackHandler = callbackHandler;
@@ -241,8 +248,7 @@
mSubscriptionManager = subManager;
mSubDefaults = defaultsHandler;
mConnectivityManager = connectivityManager;
- mHasMobileDataFeature =
- mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ mHasMobileDataFeature = telephonyManager.isDataCapable();
mDemoModeController = demoModeController;
// telephony
@@ -337,10 +343,19 @@
// This callback is invoked a lot (i.e. when RSSI changes), so avoid updating
// icons when connectivity state has remained the same.
- if (network.equals(mLastNetwork) &&
- networkCapabilities.equalsTransportTypes(mLastNetworkCapabilities) &&
- validated == lastValidated) {
- return;
+ if (network.equals(mLastNetwork) && validated == lastValidated) {
+ // Should not rely on getTransportTypes() returning the same order of transport
+ // types. So sort the array before comparing.
+ int[] newTypes = networkCapabilities.getTransportTypes();
+ Arrays.sort(newTypes);
+
+ int[] lastTypes = (mLastNetworkCapabilities != null)
+ ? mLastNetworkCapabilities.getTransportTypes() : null;
+ if (lastTypes != null) Arrays.sort(lastTypes);
+
+ if (Arrays.equals(newTypes, lastTypes)) {
+ return;
+ }
}
mLastNetwork = network;
mLastNetworkCapabilities = networkCapabilities;
@@ -363,23 +378,20 @@
// 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);
@@ -419,7 +431,7 @@
mSubscriptionListener = new SubListener();
}
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
- mPhone.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
// broadcasts
IntentFilter filter = new IntentFilter();
@@ -430,14 +442,13 @@
filter.addAction(Intent.ACTION_SERVICE_STATE);
filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
// Initial setup of connectivity. Handled as if we had received a sticky broadcast of
- // ConnectivityManager.CONNECTIVITY_ACTION or ConnectivityManager.INET_CONDITION_ACTION.
+ // ConnectivityManager.CONNECTIVITY_ACTION.
mReceiverHandler.post(this::updateConnectivity);
// Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast
@@ -682,7 +693,6 @@
final String action = intent.getAction();
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
- case ConnectivityManager.INET_CONDITION_ACTION:
updateConnectivity();
break;
case Intent.ACTION_AIRPLANE_MODE_CHANGED:
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 d4029e64..83558cb 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.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.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 @@
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 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 @@
// 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 @@
}
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 @@
}
@Override
+ public int getSettingsText() {
+ return R.string.quick_settings_more_user_settings;
+ }
+
+ @Override
public Boolean getToggleState() {
return null;
}
@@ -891,6 +897,7 @@
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 0000000..3bc2632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.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.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 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 0000000..4e1acca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyListenerManager.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.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/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index bbb2f1a..278663b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -65,8 +65,6 @@
"android.theme.customization.accent_color";
static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
"android.theme.customization.system_palette";
- static final String OVERLAY_CATEGORY_NEUTRAL_PALETTE =
- "android.theme.customization.neutral_palette";
@VisibleForTesting
static final String OVERLAY_CATEGORY_FONT = "android.theme.customization.font";
@VisibleForTesting
@@ -94,7 +92,6 @@
*/
static final List<String> THEME_CATEGORIES = Lists.newArrayList(
OVERLAY_CATEGORY_SYSTEM_PALETTE,
- OVERLAY_CATEGORY_NEUTRAL_PALETTE,
OVERLAY_CATEGORY_ICON_LAUNCHER,
OVERLAY_CATEGORY_SHAPE,
OVERLAY_CATEGORY_FONT,
@@ -108,7 +105,6 @@
@VisibleForTesting
static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
OVERLAY_CATEGORY_SYSTEM_PALETTE,
- OVERLAY_CATEGORY_NEUTRAL_PALETTE,
OVERLAY_CATEGORY_ACCENT_COLOR,
OVERLAY_CATEGORY_FONT,
OVERLAY_CATEGORY_SHAPE,
@@ -131,8 +127,8 @@
mLauncherPackage = launcherPackage;
mThemePickerPackage = themePickerPackage;
mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
- OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_NEUTRAL_PALETTE,
- OVERLAY_CATEGORY_ACCENT_COLOR, OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
+ OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR,
+ OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
OVERLAY_CATEGORY_ICON_ANDROID));
mTargetPackageToCategories.put(SYSUI_PACKAGE,
Sets.newHashSet(OVERLAY_CATEGORY_ICON_SYSUI));
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f192287..d317712 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -16,7 +16,6 @@
package com.android.systemui.theme;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import android.annotation.Nullable;
@@ -82,9 +81,8 @@
protected static final String TAG = "ThemeOverlayController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected static final int PRIMARY = 0;
- protected static final int SECONDARY = 1;
- protected static final int NEUTRAL = 2;
+ protected static final int NEUTRAL = 0;
+ protected static final int ACCENT = 1;
private final ThemeOverlayApplier mThemeManager;
private final UserManager mUserManager;
@@ -103,8 +101,6 @@
protected int mMainWallpaperColor = Color.TRANSPARENT;
// Accent color extracted from wallpaper, NOT the color used on the overlay
protected int mWallpaperAccentColor = Color.TRANSPARENT;
- // System colors overlay
- private FabricatedOverlay mPrimaryOverlay;
// Accent colors overlay
private FabricatedOverlay mSecondaryOverlay;
// Neutral system colors overlay
@@ -205,7 +201,7 @@
mainColor = Color.TRANSPARENT;
accentCandidate = Color.TRANSPARENT;
} else {
- mainColor = getDominantColor(currentColors);
+ mainColor = getNeutralColor(currentColors);
accentCandidate = getAccentColor(currentColors);
}
@@ -218,13 +214,12 @@
mWallpaperAccentColor = accentCandidate;
if (mIsMonetEnabled) {
- mPrimaryOverlay = getOverlay(mMainWallpaperColor, PRIMARY);
- mSecondaryOverlay = getOverlay(mWallpaperAccentColor, SECONDARY);
+ mSecondaryOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL);
mNeedsOverlayCreation = true;
if (DEBUG) {
- Log.d(TAG, "fetched overlays. primary: " + mPrimaryOverlay + " secondary: "
- + mSecondaryOverlay + " neutral: " + mNeutralOverlay);
+ Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay
+ + " neutral: " + mNeutralOverlay);
}
}
@@ -234,7 +229,7 @@
/**
* Return the main theme color from a given {@link WallpaperColors} instance.
*/
- protected int getDominantColor(@NonNull WallpaperColors wallpaperColors) {
+ protected int getNeutralColor(@NonNull WallpaperColors wallpaperColors) {
return wallpaperColors.getPrimaryColor().toArgb();
}
@@ -283,8 +278,6 @@
if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
try {
int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
- mPrimaryOverlay = getOverlay(color, PRIMARY);
- // Neutral palette is always derived from primary color.
mNeutralOverlay = getOverlay(color, NEUTRAL);
mNeedsOverlayCreation = true;
categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
@@ -308,7 +301,7 @@
if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
try {
int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
- mSecondaryOverlay = getOverlay(color, SECONDARY);
+ mSecondaryOverlay = getOverlay(color, ACCENT);
mNeedsOverlayCreation = true;
categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
} catch (NumberFormatException e) {
@@ -326,9 +319,8 @@
// Compatibility with legacy themes, where full packages were defined, instead of just
// colors.
if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
- && mPrimaryOverlay != null) {
- categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mPrimaryOverlay.getIdentifier());
- categoryToPackage.put(OVERLAY_CATEGORY_NEUTRAL_PALETTE,
+ && mNeutralOverlay != null) {
+ categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
mNeutralOverlay.getIdentifier());
}
if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
@@ -350,7 +342,7 @@
if (mNeedsOverlayCreation) {
mNeedsOverlayCreation = false;
mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
- mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay
+ mSecondaryOverlay, mNeutralOverlay
}, currentUser, managedProfiles);
} else {
mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
@@ -363,7 +355,6 @@
pw.println("mSystemColors=" + mSystemColors);
pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
- pw.println("mPrimaryOverlay=" + mPrimaryOverlay);
pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
pw.println("mNeutralOverlay=" + mNeutralOverlay);
pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 5dc7006..044d828 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 @@
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 @@
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 @@
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 @@
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 ddfa63a..709ccd4 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.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 @@
* 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 0000000..61f50b5
--- /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
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index aa4122f..c2c7dde 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_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;
@@ -37,13 +38,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.net.ConnectivityManager;
+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.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 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
[email protected]
-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,36 +92,39 @@
@Mock
private WifiManager mWifiManager;
@Mock
- private CarrierTextController.CarrierTextCallback mCarrierTextCallback;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock
+ private CarrierTextManager.CarrierTextCallback mCarrierTextCallback;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- private ConnectivityManager mConnectivityManager;
+ private PackageManager mPackageManager;
@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(ConnectivityManager.class, mConnectivityManager);
- when(mConnectivityManager.isNetworkSupported(anyInt())).thenReturn(true);
+ mContext.addMockSystemService(PackageManager.class, mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
mContext.getOrCreateTestableResources().addOverride(
@@ -132,9 +132,6 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0cf343c..90f7fda 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 @@
@Mock
private LatencyTracker mLatencyTracker;
private final FalsingCollector mFalsingCollector = new FalsingCollectorFake();
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
@@ -87,7 +89,8 @@
.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 826be2b..4beec57 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 @@
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 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 fc93ded..bb71bed8 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 @@
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 @@
.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 33a0dcd0..9597cab 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 @@
private LatencyTracker mLatencyTracker;
@Mock
private LiftToActivateListener mLiftToactivateListener;
+ @Mock
+ private EmergencyButtonController mEmergencyButtonController;
private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
private SingleTapClassifier mSingleTapClassifier;
@@ -97,7 +99,7 @@
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 096ce0f..99b3f6f 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.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 @@
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 @@
@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 @@
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 @@
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 3b7f4b8..9296d3d 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 @@
@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 @@
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 52e2016..160dae5 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.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 @@
@Mock
private AuthController mAuthController;
@Mock
+ private TelephonyListenerManager mTelephonyListenerManager;
+ @Mock
private FeatureFlags mFeatureFlags;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
@@ -883,7 +886,7 @@
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 0000000..02ba304
--- /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 3f1a927..d3694dd 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 @@
@Mock
private DumpManager mDumpManager;
@Mock
+ private AuthRippleController mAuthRippleController;
+ @Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
private FakeExecutor mFgExecutor;
@@ -148,7 +150,8 @@
mFgExecutor,
mStatusBar,
mStatusBarKeyguardViewManager,
- mDumpManager);
+ mDumpManager,
+ mAuthRippleController);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 65f0f7b..4410992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -170,11 +170,11 @@
mAltAuthInterceptor.showAlternativeAuthMethod(); // force show
assertFalse(mController.shouldPauseAuth());
- assertTrue(mAltAuthInterceptor.isShowingAlternativeAuth());
+ assertTrue(mAltAuthInterceptor.isShowingAlternateAuth());
- mAltAuthInterceptor.reset(); // stop force show
+ mAltAuthInterceptor.resetForceShow(); // stop force show
assertTrue(mController.shouldPauseAuth());
- assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+ assertFalse(mAltAuthInterceptor.isShowingAlternateAuth());
}
@Test
@@ -190,7 +190,7 @@
mController.onViewDetached();
// THEN alt auth state reports not showing
- assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+ assertFalse(mAltAuthInterceptor.isShowingAlternateAuth());
}
private void sendStatusBarStateChanged(int statusBarState) {
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 67c1d07..1f165bb 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 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 @@
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 @@
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 @@
@Test
public void testHistory() {
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
}
@@ -220,7 +220,7 @@
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 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 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 eedf099..8add930 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.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.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 @@
@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 @@
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 3a77f7ee..33a30e0 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.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.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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 ee98a59..1f4dffa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -16,20 +16,11 @@
package com.android.systemui.people;
-import static android.app.Notification.CATEGORY_MISSED_CALL;
-import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
-import static android.app.people.ConversationStatus.ACTIVITY_GAME;
-import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
-import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
-import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
-
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
-import static com.android.systemui.people.PeopleSpaceUtils.REQUIRED_WIDTH_FOR_MEDIUM;
import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -48,7 +39,6 @@
import android.app.NotificationManager;
import android.app.Person;
import android.app.people.ConversationChannel;
-import android.app.people.ConversationStatus;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
@@ -70,13 +60,12 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.TextView;
+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;
@@ -117,8 +106,6 @@
private static final int TEST_COLUMN_INDEX = 1;
private static final Uri URI = Uri.parse("fake_uri");
private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
- private static final String GAME_DESCRIPTION = "Playing a game!";
- private static final CharSequence MISSED_CALL = "Custom missed call message";
private static final String NAME = "username";
private static final Person PERSON = new Person.Builder()
.setName("name")
@@ -126,11 +113,6 @@
.setUri(URI.toString())
.setBot(false)
.build();
- private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
- new PeopleSpaceTile
- .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
- .setLastInteractionTimestamp(0L)
- .build();
private static final PeopleSpaceTile PERSON_TILE =
new PeopleSpaceTile
.Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
@@ -138,16 +120,7 @@
.setNotificationKey(NOTIFICATION_KEY)
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
- .build();
- private static final ConversationStatus GAME_STATUS =
- new ConversationStatus
- .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
- .setDescription(GAME_DESCRIPTION)
- .build();
- private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
- new ConversationStatus
- .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
- .setAvailability(AVAILABILITY_AVAILABLE)
+ .setMessagesCount(1)
.build();
private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
@@ -244,6 +217,12 @@
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
.thenReturn(new Bundle());
+ Configuration configuration = mock(Configuration.class);
+ DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
+ Resources resources = mock(Resources.class);
+ when(mMockContext.getResources()).thenReturn(resources);
+ when(resources.getConfiguration()).thenReturn(configuration);
+ when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
when(mMockContentResolver.query(any(Uri.class), any(), anyString(), any(),
isNull())).thenReturn(mMockCursor);
@@ -254,10 +233,6 @@
when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
mContext.getString(R.string.over_timestamp));
- Configuration configuration = mock(Configuration.class);
- Resources resources = mock(Resources.class);
- when(mMockContext.getResources()).thenReturn(resources);
- when(resources.getConfiguration()).thenReturn(configuration);
when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
when(mNotificationEntryManager.getVisibleNotifications())
.thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
@@ -345,7 +320,7 @@
}
@Test
- public void testGetLastMessagingStyleMessageNoMessage() {
+ public void testGetMessagingStyleMessagesNoMessage() {
Notification notification = new Notification.Builder(mContext, "test")
.setContentTitle("TEST_TITLE")
.setContentText("TEST_TEXT")
@@ -355,111 +330,23 @@
.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 testGetBackgroundTextFromMessageNoPunctuation() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageSingleExclamation() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test!");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageSingleQuestion() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("?test");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageSeparatedMarks() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test! right!");
-
- assertThat(backgroundText).isNull();
- }
-
- @Test
- public void testGetBackgroundTextFromMessageDoubleExclamation() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("!!test");
-
- assertThat(backgroundText).isEqualTo("!");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageDoubleQuestion() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test??");
-
- assertThat(backgroundText).isEqualTo("?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMixed() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage("test?!");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMixedInTheMiddle() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!? in the middle");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMixedDifferentOrder() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!? in the middle");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageMultiple() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!?!!? in the middle");
-
- assertThat(backgroundText).isEqualTo("!?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageQuestionFirst() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test?? in the middle!!");
-
- assertThat(backgroundText).isEqualTo("?");
- }
-
- @Test
- public void testGetBackgroundTextFromMessageExclamationFirst() {
- String backgroundText = PeopleSpaceUtils.getBackgroundTextFromMessage(
- "test!! in the middle??");
-
- assertThat(backgroundText).isEqualTo("!");
- }
-
- @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
@@ -687,225 +574,6 @@
any());
}
- @Test
- public void testCreateRemoteViewsWithLastInteractionTime() {
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
- PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has last interaction.
- TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
- assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
- // No availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.GONE, availability.getVisibility());
- // Shows person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // No status.
- assertThat((View) result.findViewById(R.id.text_content)).isNull();
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show name over predefined icon.
- assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Shows person icon.
- assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
- PeopleSpaceTile tileWithAvailabilityAndNewStory =
- PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
- Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
- new ConversationStatus.Builder(
- PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
- ACTIVITY_GAME).build())).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
- tileWithAvailabilityAndNewStory, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has last interaction over status.
- TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
- assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // No status.
- assertThat((View) result.findViewById(R.id.text_content)).isNull();
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithAvailabilityAndNewStory, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show name rather than game type.
- assertEquals(View.VISIBLE, 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());
- }
-
- @Test
- public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
- PeopleSpaceTile tileWithStatusTemplate =
- PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
- Arrays.asList(
- NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
- PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
- ACTIVITY_BIRTHDAY).build())).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has status text from backup text.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithStatusTemplate() {
- PeopleSpaceTile tileWithStatusTemplate =
- PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
- Arrays.asList(GAME_STATUS,
- NEW_STORY_WITH_AVAILABILITY)).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has status.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), GAME_DESCRIPTION);
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithMissedCallNotification() {
- PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
- .setNotificationDataUri(null)
- .setNotificationCategory(CATEGORY_MISSED_CALL)
- .setNotificationContent(MISSED_CALL)
- .build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithMissedCallNotification, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.GONE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has missed call notification content.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), MISSED_CALL);
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithMissedCallNotification, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
- @Test
- public void testCreateRemoteViewsWithNotificationTemplate() {
- PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
- .setNotificationDataUri(null)
- .setStatuses(Arrays.asList(GAME_STATUS,
- NEW_STORY_WITH_AVAILABILITY)).build();
- RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusAndNotification, 0, mOptions);
- View result = views.apply(mContext, null);
-
- TextView name = (TextView) result.findViewById(R.id.name);
- assertEquals(name.getText(), NAME);
- TextView subtext = (TextView) result.findViewById(R.id.subtext);
- assertEquals(View.GONE, subtext.getVisibility());
- // Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
- // Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
- // Has notification content.
- TextView statusContent = (TextView) result.findViewById(R.id.text_content);
- assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
-
- mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, REQUIRED_WIDTH_FOR_MEDIUM - 1);
- RemoteViews smallView = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusAndNotification, 0, mOptions);
- View smallResult = smallView.apply(mContext, null);
-
- // Show icon instead of name.
- assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.predefined_icon).getVisibility());
- // Has person icon.
- assertEquals(View.VISIBLE,
- smallResult.findViewById(R.id.person_icon).getVisibility());
- }
-
private ConversationChannelWrapper getConversationChannelWrapper(String shortcutId,
boolean importantConversation, long lastInteractionTimestamp) throws Exception {
ConversationChannelWrapper convo = new ConversationChannelWrapper();
@@ -931,13 +599,4 @@
eq(shortcutId))).thenReturn(lastInteractionTimestamp);
return convo;
}
-
- private ConversationChannel getConversationChannelWithoutTimestamp(String shortcutId)
- throws Exception {
- ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
- "name").build();
- ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
- 0L, false);
- return convo;
- }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
new file mode 100644
index 0000000..8db0f33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -0,0 +1,670 @@
+/*
+ * 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.people;
+
+import static android.app.Notification.CATEGORY_MISSED_CALL;
+import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+
+import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleSpaceTile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PeopleTileViewHelperTest extends SysuiTestCase {
+
+ private static final String SHORTCUT_ID_1 = "101";
+ private static final String NOTIFICATION_KEY = "notification_key";
+ private static final String NOTIFICATION_CONTENT = "notification_content";
+ private static final Uri URI = Uri.parse("fake_uri");
+ private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
+ private static final String GAME_DESCRIPTION = "Playing a game!";
+ private static final CharSequence MISSED_CALL = "Custom missed call message";
+ private static final String NAME = "username";
+ private static final PeopleSpaceTile PERSON_TILE_WITHOUT_NOTIFICATION =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+ .setLastInteractionTimestamp(0L)
+ .build();
+ private static final PeopleSpaceTile PERSON_TILE =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, NAME, ICON, new Intent())
+ .setLastInteractionTimestamp(123L)
+ .setNotificationKey(NOTIFICATION_KEY)
+ .setNotificationContent(NOTIFICATION_CONTENT)
+ .setNotificationDataUri(URI)
+ .build();
+ private static final ConversationStatus GAME_STATUS =
+ new ConversationStatus
+ .Builder(PERSON_TILE.getId(), ACTIVITY_GAME)
+ .setDescription(GAME_DESCRIPTION)
+ .build();
+ private static final ConversationStatus NEW_STORY_WITH_AVAILABILITY =
+ new ConversationStatus
+ .Builder(PERSON_TILE.getId(), ACTIVITY_NEW_STORY)
+ .setAvailability(AVAILABILITY_AVAILABLE)
+ .build();
+
+ @Mock
+ private Context mMockContext;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private Bundle mOptions;
+ private PeopleTileViewHelper mPeopleTileViewHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mOptions = new Bundle();
+ mOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
+
+ when(mMockContext.getString(R.string.birthday_status)).thenReturn(
+ mContext.getString(R.string.birthday_status));
+ when(mMockContext.getString(R.string.basic_status)).thenReturn(
+ mContext.getString(R.string.basic_status));
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mMockContext.getString(R.string.over_timestamp)).thenReturn(
+ mContext.getString(R.string.over_timestamp));
+ Configuration configuration = mock(Configuration.class);
+ DisplayMetrics displayMetrics = mock(DisplayMetrics.class);
+ Resources resources = mock(Resources.class);
+ when(mMockContext.getResources()).thenReturn(resources);
+ when(resources.getConfiguration()).thenReturn(configuration);
+ when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
+ TextView textView = mock(TextView.class);
+ // when(new TextView(mMockContext)).thenReturn(textView);
+ when(textView.getLineHeight()).thenReturn(16);
+ when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
+ mPeopleTileViewHelper = new PeopleTileViewHelper(mContext,
+ PERSON_TILE, 0, mOptions);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithLastInteractionTime() {
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction.
+ TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // No availability.
+ assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show name over predefined icon.
+ assertEquals(View.VISIBLE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.GONE, smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).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,
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction.
+ lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // No availability.
+ assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithGameTypeOnlyIsIgnored() {
+ PeopleSpaceTile tileWithAvailabilityAndNewStory =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(NEW_STORY_WITH_AVAILABILITY,
+ new ConversationStatus.Builder(
+ PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+ ACTIVITY_GAME).build())).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ TextView name = (TextView) result.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction over status.
+ TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // Has availability.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show name rather than game type.
+ assertEquals(View.VISIBLE, 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());
+
+ 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,
+ tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ // Has last interaction.
+ lastInteraction = (TextView) result.findViewById(R.id.last_interaction);
+ assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status));
+ // Has availability.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+ // Shows person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // No status.
+ assertThat((View) result.findViewById(R.id.text_content)).isNull();
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithBirthdayTypeOnlyIsNotIgnored() {
+ PeopleSpaceTile tileWithStatusTemplate =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(
+ NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
+ PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
+ ACTIVITY_BIRTHDAY).build())).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 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.VISIBLE, 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 status text from backup text.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), mContext.getString(R.string.birthday_status));
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 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.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).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,
+ tileWithStatusTemplate, 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.VISIBLE, 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(), mContext.getString(R.string.birthday_status));
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithStatusTemplate() {
+ PeopleSpaceTile tileWithStatusTemplate =
+ PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
+ Arrays.asList(GAME_STATUS,
+ NEW_STORY_WITH_AVAILABILITY)).build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 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.VISIBLE, 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 status.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(statusContent.getText(), GAME_DESCRIPTION);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithStatusTemplate, 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.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).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,
+ tileWithStatusTemplate, 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.VISIBLE, 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(), GAME_DESCRIPTION);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithMissedCallNotification() {
+ PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
+ .setNotificationDataUri(null)
+ .setNotificationCategory(CATEGORY_MISSED_CALL)
+ .setNotificationContent(MISSED_CALL)
+ .build();
+ RemoteViews views = new PeopleTileViewHelper(mContext,
+ tileWithMissedCallNotification, 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.VISIBLE, result.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
+ // Has missed call notification content.
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), MISSED_CALL);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithMissedCallNotification, 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.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE, smallResult.findViewById(R.id.person_icon).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,
+ tileWithMissedCallNotification, 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.VISIBLE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.GONE, 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(), MISSED_CALL);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithNotificationTemplate() {
+ PeopleSpaceTile tileWithStatusAndNotification = PERSON_TILE.toBuilder()
+ .setNotificationDataUri(null)
+ .setStatuses(Arrays.asList(GAME_STATUS,
+ NEW_STORY_WITH_AVAILABILITY)).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 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,
+ 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.VISIBLE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ 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,
+ 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 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");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageSingleExclamation() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test!");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageSingleQuestion() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("?test");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageSeparatedMarks() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test! right!");
+
+ assertThat(backgroundText).isNull();
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageDoubleExclamation() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("!!test");
+
+ assertThat(backgroundText).isEqualTo("!");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageDoubleQuestion() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test??");
+
+ assertThat(backgroundText).isEqualTo("?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMixed() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage("test?!");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMixedInTheMiddle() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!? in the middle");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMixedDifferentOrder() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!? in the middle");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageMultiple() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!?!!? in the middle");
+
+ assertThat(backgroundText).isEqualTo("!?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageQuestionFirst() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test?? in the middle!!");
+
+ assertThat(backgroundText).isEqualTo("?");
+ }
+
+ @Test
+ public void testGetBackgroundTextFromMessageExclamationFirst() {
+ String backgroundText = mPeopleTileViewHelper.getBackgroundTextFromMessage(
+ "test!! in the middle??");
+
+ assertThat(backgroundText).isEqualTo("!");
+ }
+
+ private int getSizeInDp(int dimenResourceId) {
+ return (int) (mContext.getResources().getDimension(dimenResourceId)
+ / mContext.getResources().getDisplayMetrics().density);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 4948c2b1..3595095 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.Clock
@@ -78,8 +77,6 @@
@Mock
private lateinit var statusBarIconController: StatusBarIconController
@Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
private lateinit var demoModeController: DemoModeController
@Mock
private lateinit var userTracker: UserTracker
@@ -130,7 +127,6 @@
uiEventLogger,
qsTileHost,
statusBarIconController,
- commandQueue,
demoModeController,
userTracker,
quickQSPanelController,
@@ -233,4 +229,4 @@
`when`(privacyItemController.micCameraAvailable).thenReturn(micCamera)
`when`(privacyItemController.locationAvailable).thenReturn(location)
}
-}
\ No newline at end of file
+}
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 418fa61..6af8402 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 @@
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 @@
secureSettings,
Handler(testableLooper.looper),
TEST_SETTING,
- USER
+ USER,
+ DEFAULT_VALUE
) {
override fun handleValueChanged(value: Int, observedChange: Boolean) {
callback(value, observedChange)
@@ -150,4 +152,14 @@
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 5a1bd5f..59a5f03 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 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 @@
private QSCarrierGroupController mQSCarrierGroupController;
private NetworkController.SignalCallback mSignalCallback;
- private CarrierTextController.CarrierTextCallback mCallback;
+ private CarrierTextManager.CarrierTextCallback mCallback;
@Mock
private QSCarrierGroup mQSCarrierGroup;
@Mock
@@ -63,9 +63,9 @@
@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 @@
.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 @@
(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 @@
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 @@
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 @@
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 @@
(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 @@
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 @@
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 @@
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 @@
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 @@
@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 410d9de..7fe178c 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 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 0bf1ac3..e65db5e 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 @@
@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 @@
@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/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index e6287e7..aeb5b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -18,31 +18,18 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
import android.annotation.Nullable;
-import android.app.Notification;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.drawable.Drawable;
import android.test.suitebuilder.annotation.SmallTest;
-import android.widget.RemoteViews;
import androidx.palette.graphics.Palette;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.tests.R;
import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,17 +45,8 @@
*/
private static final int COLOR_TOLERANCE = 8;
- private MediaNotificationProcessor mProcessor;
- private Bitmap mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- private ImageGradientColorizer mColorizer;
@Nullable private Bitmap mArtwork;
- @Before
- public void setUp() {
- mColorizer = spy(new TestableColorizer(mBitmap));
- mProcessor = new MediaNotificationProcessor(getContext(), getContext(), mColorizer);
- }
-
@After
public void tearDown() {
if (mArtwork != null) {
@@ -78,53 +56,6 @@
}
@Test
- public void testColorizedWithLargeIcon() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setLargeIcon(mBitmap)
- .setContentText("Text");
- Notification notification = builder.build();
- mProcessor.processNotification(notification, builder);
- verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
- }
-
- @Test
- public void testNotColorizedWithoutLargeIcon() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
- Notification notification = builder.build();
- mProcessor.processNotification(notification, builder);
- verifyZeroInteractions(mColorizer);
- }
-
- @Test
- public void testRemoteViewsReset() {
- Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
- R.drawable.ic_person)
- .setContentTitle("Title")
- .setStyle(new Notification.MediaStyle())
- .setLargeIcon(mBitmap)
- .setContentText("Text");
- Notification notification = builder.build();
- RemoteViews remoteViews = new RemoteViews(getContext().getPackageName(),
- R.layout.custom_view_dark);
- notification.contentView = remoteViews;
- notification.bigContentView = remoteViews;
- notification.headsUpContentView = remoteViews;
- mProcessor.processNotification(notification, builder);
- verify(mColorizer).colorize(any(), anyInt(), anyBoolean());
- RemoteViews contentView = builder.createContentView();
- assertNotSame(contentView, remoteViews);
- contentView = builder.createBigContentView();
- assertNotSame(contentView, remoteViews);
- contentView = builder.createHeadsUpContentView();
- assertNotSame(contentView, remoteViews);
- }
-
- @Test
public void findBackgroundSwatch_white() {
// Given artwork that is completely white.
mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
@@ -153,17 +84,4 @@
assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
}
-
- public static class TestableColorizer extends ImageGradientColorizer {
- private final Bitmap mBitmap;
-
- private TestableColorizer(Bitmap bitmap) {
- mBitmap = bitmap;
- }
-
- @Override
- public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) {
- return mBitmap;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
deleted file mode 100644
index fbe4d73..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row.wrapper;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.Notification;
-import android.media.MediaMetadata;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.SeekBar;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase {
-
- private ExpandableNotificationRow mRow;
- private Notification mNotif;
- private View mView;
- private NotificationMediaTemplateViewWrapper mWrapper;
-
- @Mock
- private MetricsLogger mMetricsLogger;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- allowTestableLooperAsMainThread();
-
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-
- // These tests are for regular media style notifications, not controls in quick settings
- Settings.System.putInt(mContext.getContentResolver(), "qs_media_player", 0);
- }
-
- private void makeTestNotification(long duration, boolean allowSeeking) throws Exception {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
-
- MediaMetadata metadata = new MediaMetadata.Builder()
- .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
- .build();
- MediaSession session = new MediaSession(mContext, "TEST_CHANNEL");
- session.setMetadata(metadata);
-
- PlaybackState playbackState = new PlaybackState.Builder()
- .setActions(allowSeeking ? PlaybackState.ACTION_SEEK_TO : 0)
- .build();
-
- session.setPlaybackState(playbackState);
-
- builder.setStyle(new Notification.MediaStyle()
- .setMediaSession(session.getSessionToken())
- );
-
- mNotif = builder.build();
- assertTrue(mNotif.hasMediaSession());
-
- NotificationTestHelper helper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = helper.createRow(mNotif);
-
- RemoteViews views = new RemoteViews(mContext.getPackageName(),
- com.android.internal.R.layout.notification_template_material_big_media);
- mView = views.apply(mContext, null);
- mWrapper = new NotificationMediaTemplateViewWrapper(mContext,
- mView, mRow);
- mWrapper.onContentUpdated(mRow);
- }
-
- @Test
- public void testLogging_NoSeekbar() throws Exception {
- // Media sessions with duration <= 0 should not include a seekbar
- makeTestNotification(0, false);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_CLOSE
- ));
-
- verify(mMetricsLogger, times(0)).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
- }
-
- @Test
- public void testLogging_HasSeekbarNoScrubber() throws Exception {
- // Media sessions that do not support seeking should have a seekbar, but no scrubber
- makeTestNotification(1000, false);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
-
- // Ensure the callback runs at least once
- mWrapper.mOnUpdateTimerTick.run();
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_DETAIL
- && logMaker.getSubtype() == 0
- ));
- }
-
- @Test
- public void testLogging_HasSeekbarAndScrubber() throws Exception {
- makeTestNotification(1000, true);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_OPEN
- ));
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_DETAIL
- && logMaker.getSubtype() == 1
- ));
- }
-
- @Test
- public void testLogging_UpdateSeekbar() throws Exception {
- makeTestNotification(1000, true);
-
- SeekBar seekbar = mView.findViewById(
- com.android.internal.R.id.notification_media_progress_bar);
- assertTrue(seekbar != null);
-
- mWrapper.mSeekListener.onStopTrackingTouch(seekbar);
-
- verify(mMetricsLogger).write(argThat(logMaker ->
- logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
- && logMaker.getType() == MetricsEvent.TYPE_UPDATE));
- }
-}
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 91f3611..6a5e6e8 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.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 @@
@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 @@
.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 @@
mKeyguardStatusViewComponentFactory,
mKeyguardQsUserSwitchComponentFactory,
mKeyguardUserSwitcherComponentFactory,
+ mKeyguardStatusBarViewComponentFactory,
mQSDetailDisplayer,
mGroupManager,
mNotificationAreaController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 7b7e2d3..f147f1ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -31,7 +31,6 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -60,14 +59,14 @@
@Test
public void testSetCalledOnAdd_IconManager() {
LinearLayout layout = new LinearLayout(mContext);
- TestIconManager manager = new TestIconManager(layout, new CommandQueue(mContext));
+ TestIconManager manager = new TestIconManager(layout);
testCallOnAdd_forManager(manager);
}
@Test
public void testSetCalledOnAdd_DarkIconManager() {
LinearLayout layout = new LinearLayout(mContext);
- TestDarkIconManager manager = new TestDarkIconManager(layout, new CommandQueue(mContext));
+ TestDarkIconManager manager = new TestDarkIconManager(layout);
testCallOnAdd_forManager(manager);
}
@@ -104,8 +103,8 @@
private static class TestDarkIconManager extends DarkIconManager
implements TestableIconManager {
- TestDarkIconManager(LinearLayout group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ TestDarkIconManager(LinearLayout group) {
+ super(group);
}
@Override
@@ -139,8 +138,8 @@
}
private static class TestIconManager extends IconManager implements TestableIconManager {
- TestIconManager(ViewGroup group, CommandQueue commandQueue) {
- super(group, commandQueue);
+ TestIconManager(ViewGroup group) {
+ super(group);
}
@Override
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 999d282..ef33172 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
@@ -35,7 +35,6 @@
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
-import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
@@ -54,7 +53,6 @@
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.testing.TestableLooper;
@@ -78,6 +76,7 @@
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;
@@ -115,6 +114,7 @@
protected NetworkScoreManager mMockNsm;
protected SubscriptionManager mMockSm;
protected TelephonyManager mMockTm;
+ protected TelephonyListenerManager mTelephonyListenerManager;
protected BroadcastDispatcher mMockBd;
protected Config mConfig;
protected CallbackHandler mCallbackHandler;
@@ -166,13 +166,14 @@
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);
mMockNsm = mock(NetworkScoreManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mNetCapabilities = new NetworkCapabilities();
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
+ when(mMockTm.isDataCapable()).thenReturn(true);
when(mMockTm.createForSubscriptionId(anyInt())).thenReturn(mMockTm);
doAnswer(invocation -> {
int rssi = invocation.getArgument(0);
@@ -215,6 +216,7 @@
mNetworkController = new NetworkControllerImpl(mContext,
mMockCm,
mMockTm,
+ mTelephonyListenerManager,
mMockWm,
mMockNsm,
mMockSm,
@@ -285,9 +287,10 @@
}
protected NetworkControllerImpl setUpNoMobileData() {
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ 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,
@@ -308,27 +311,14 @@
TelephonyManager.NETWORK_TYPE_UMTS);
setConnectivityViaCallbackInNetworkController(
NetworkCapabilities.TRANSPORT_CELLULAR, true, true, null);
- setConnectivityViaBroadcast(
- NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
}
- public void setConnectivityViaBroadcastForVcn(
+ public void setConnectivityViaCallbackInNetworkControllerForVcn(
int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
mNetCapabilities.setTransportInfo(info);
setConnectivityCommon(networkType, validated, isConnected);
mDefaultCallbackInNetworkController.onCapabilitiesChanged(
mock(Network.class), new NetworkCapabilities(mNetCapabilities));
- Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
- mNetworkController.onReceive(mContext, i);
- }
-
- public void setConnectivityViaBroadcast(
- int networkType, boolean validated, boolean isConnected) {
- setConnectivityCommon(networkType, validated, isConnected);
- mDefaultCallbackInNetworkController.onCapabilitiesChanged(
- mock(Network.class), new NetworkCapabilities(mNetCapabilities));
- Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
- mNetworkController.onReceive(mContext, i);
}
public void setConnectivityViaCallbackInNetworkController(
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 37b6a5d..f4ad819 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 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,
@@ -126,7 +127,8 @@
when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -140,7 +142,8 @@
when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -155,7 +158,8 @@
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -170,7 +174,8 @@
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
// Verify that a SignalDrawable with a cut out is used to display data disabled.
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, 0,
@@ -184,7 +189,8 @@
when(mMockTm.isDataConnectionAllowed()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(false);
mUserCallback.onUserSetupChanged();
TestableLooper.get(this).processAllMessages();
@@ -206,7 +212,8 @@
mConfig.alwaysShowDataRatIcon = true;
mNetworkController.handleConfigurationChanged();
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
verifyLastMobileDataIndicators(false, DEFAULT_SIGNAL_STRENGTH, TelephonyIcons.ICON_G,
true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
index 93cf3e8..6aab9c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
@@ -37,7 +37,8 @@
}
protected void setEthernetState(boolean connected, boolean validated) {
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_ETHERNET, validated, connected);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_ETHERNET, validated, connected, null);
}
protected void verifyLastEthernetIcon(boolean visible, int icon) {
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 c0d9c3d..3c5cbb6 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
@@ -23,8 +23,8 @@
import static org.mockito.Mockito.when;
import android.content.Intent;
-import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
+import android.net.wifi.WifiInfo;
import android.os.Handler;
import android.os.Looper;
import android.telephony.CellSignalStrength;
@@ -59,10 +59,11 @@
@Test
public void testNoIconWithoutMobile() {
// Turn off mobile network support.
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ 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 @@
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);
@@ -145,10 +147,11 @@
@Test
public void testNoSimlessIconWithoutMobile() {
// Turn off mobile network support.
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ 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);
@@ -172,7 +175,8 @@
testStrength, DEFAULT_ICON);
// Verify low inet number indexing.
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, true, null);
verifyLastMobileDataIndicators(true,
testStrength, DEFAULT_ICON, false, false);
}
@@ -259,8 +263,10 @@
@Test
public void testNoBangWithWifi() {
setupDefaultSignal();
- setConnectivityViaBroadcast(mMobileSignalController.mTransportType, false, false);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ mMobileSignalController.mTransportType, false, false, null);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mock(WifiInfo.class));
verifyLastMobileDataIndicators(false, DEFAULT_LEVEL, 0);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 847030e..ab7cbf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -3,7 +3,7 @@
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -41,7 +41,7 @@
@Before
public void setUp() throws Exception {
super.setUp();
- when(mWifiInfo.makeCopy(anyBoolean())).thenReturn(mWifiInfo);
+ when(mWifiInfo.makeCopy(anyLong())).thenReturn(mWifiInfo);
}
@Test
@@ -59,9 +59,11 @@
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
// Icon does not show if not validated
verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
}
@@ -80,12 +82,14 @@
setWifiState(true, testSsid);
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
setConnectivityViaDefaultCallbackInWifiTracker(
NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel],
testSsid);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
testSsid);
}
@@ -99,7 +103,8 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
setConnectivityViaDefaultCallbackInWifiTracker(
NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastQsWifiIcon(true, true,
@@ -126,14 +131,17 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setupDefaultSignal();
setGsmRoaming(true);
// Still be on wifi though.
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
verifyLastMobileDataIndicators(true, DEFAULT_LEVEL, 0, true);
}
@@ -145,7 +153,8 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setConnectivityViaCallbackInNetworkController(
@@ -161,7 +170,8 @@
setWifiEnabled(true);
setWifiState(true, testSsid);
setWifiLevel(testLevel);
- setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_WIFI, true, true, mWifiInfo);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setWifiState(false, testSsid);
@@ -234,11 +244,11 @@
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevelForVcn(testLevel);
- setConnectivityViaBroadcastForVcn(
+ setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
- setConnectivityViaBroadcastForVcn(
+ setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
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 0000000..463b336
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/TelephonyCallbackTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.Before;
+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 0000000..0d1ac7b
--- /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/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 6067b42..eb6fc2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -23,7 +23,6 @@
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SETTINGS;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_SYSUI;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_THEME_PICKER;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SHAPE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.SETTINGS_PACKAGE;
@@ -116,8 +115,6 @@
ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, false),
- createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE,
- ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_FONT,
ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, false),
createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
@@ -128,8 +125,6 @@
ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
ANDROID_PACKAGE, OVERLAY_CATEGORY_SYSTEM_PALETTE, true),
- createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_NEUTRAL_PALETTE,
- ANDROID_PACKAGE, OVERLAY_CATEGORY_NEUTRAL_PALETTE, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_FONT,
ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT, true),
createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SHAPE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 8a0ac11..4e7e0a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.theme;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
-import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_NEUTRAL_PALETTE;
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import static com.google.common.truth.Truth.assertThat;
@@ -147,8 +146,6 @@
// Assert that we received the colors that we were expecting
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
.isEqualTo(new OverlayIdentifier("ffff0000"));
- assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_NEUTRAL_PALETTE))
- .isEqualTo(new OverlayIdentifier("ffff0000"));
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
.isEqualTo(new OverlayIdentifier("ff0000ff"));
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 7c7ad53..d3d30f2 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 @@
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 boolean runNextReady() {
if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) {
+ mExecuting = true;
mQueuedRunnables.poll().mRunnable.run();
+ mExecuting = false;
return true;
}
@@ -162,6 +165,10 @@
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 abc283f..87206c5 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 @@
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 443a9bc..f971a09 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/proto/src/camera.proto b/proto/src/camera.proto
new file mode 100644
index 0000000..d07a525
--- /dev/null
+++ b/proto/src/camera.proto
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.stats.camera;
+
+option java_package = "android.stats.camera";
+option java_outer_classname = "CameraProtos";
+
+/**
+ * CameraStreamProto from atoms.proto, duplicated here so that it's accessible from the
+ * logging code. Must be kept in sync with the definition in atoms.proto.
+ */
+message CameraStreamProto {
+ // The stream width (in pixels)
+ optional int32 width = 1;
+ // The stream height (in pixels)
+ optional int32 height = 2;
+ // The format of the stream
+ optional int32 format = 3;
+ // The dataspace of the stream
+ optional int32 data_space = 4;
+ // The usage flag of the stream
+ optional int64 usage = 5;
+
+ // The number of requests for this stream
+ optional int64 request_count = 6;
+ // The number of buffer error for this stream
+ optional int64 error_count = 7;
+ // The capture latency of first request for this stream
+ optional int32 first_capture_latency_millis = 8;
+
+ // The maximum number of hal buffers
+ optional int32 max_hal_buffers = 9;
+ // The maximum number of app buffers
+ optional int32 max_app_buffers = 10;
+
+ // Type of stream histogram
+ // 1: Capture latency: bin size in milliseconds
+ enum HistogramType {
+ UNKNOWN = 0;
+ CAPTURE_LATENCY = 1;
+ }
+ optional HistogramType histogram_type = 11;
+ // The boundary values between histogram bins
+ // Expected number of fields: 9
+ repeated float histogram_bins = 12;
+ // The frame counts for each histogram bins
+ // Expected number of fields: 10
+ repeated int64 histogram_counts = 13;
+}
diff --git a/services/Android.bp b/services/Android.bp
index f6bb157..311e8735 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -153,7 +153,7 @@
" --hide DeprecationMismatch" +
" --hide HiddenTypedefConstant",
visibility: ["//visibility:private"],
- filter_packages: ["com.android."]
+ filter_packages: ["com.android."],
}
droidstubs {
@@ -168,7 +168,7 @@
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 @@
},
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 @@
},
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 d2c1bc1..1fdd908 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 @@
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 @@
.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 @@
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/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index e9a099a..2c50389 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -35,6 +35,7 @@
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.Slog;
@@ -179,7 +180,8 @@
Context ctx = getContext();
if (!(ctx.checkCallingPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED
|| mServiceNameResolver.isTemporary(userId)
- || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())
+ || Binder.getCallingUid() == Process.SYSTEM_UID)) {
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid()
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 735f420..cd332a6 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -40,6 +40,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.infra.AbstractRemoteService;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -55,6 +56,8 @@
private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX =
"predict_using_people_service_";
+ private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
+
@Nullable
@GuardedBy("mLock")
@@ -112,8 +115,16 @@
@GuardedBy("mLock")
public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
@NonNull AppPredictionSessionId sessionId, @NonNull IBinder token) {
- final boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ boolean usesPeopleService = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false);
+ if (context.getExtras() != null
+ && context.getExtras().getBoolean(REMOTE_APP_PREDICTOR_KEY, false)
+ && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED, false)
+ ) {
+ // connect with remote AppPredictionService instead for dark launch
+ usesPeopleService = false;
+ }
final boolean serviceExists = resolveService(sessionId, false,
usesPeopleService, s -> s.onCreatePredictionSession(context, sessionId));
if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 9ac93d9..483f67a 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 @@
}));
}
+ @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();
@@ -1205,8 +1215,6 @@
}
public void schedule() {
- Slog.d(LOG_TAG,
- "TriggerDeviceDisappearedRunnable.schedule(address = " + mAddress + ")");
mMainHandler.removeCallbacks(this);
mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
}
@@ -1244,8 +1252,6 @@
}
private void onDeviceNearby(String address) {
- Slog.i(LOG_TAG, "onDeviceNearby(address = " + address + ")");
-
Date timestamp = new Date();
Date oldTimestamp = mDevicesLastNearby.put(address, timestamp);
@@ -1259,13 +1265,11 @@
boolean justAppeared = oldTimestamp == null
|| timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS;
if (justAppeared) {
+ Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")");
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Device " + address
- + " managed by " + association.getPackageName()
- + " is nearby on " + timestamp);
- }
+ Slog.i(LOG_TAG,
+ "Sending onDeviceAppeared to " + association.getPackageName() + ")");
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
}
@@ -1279,12 +1283,8 @@
boolean hasDeviceListeners = false;
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "Device " + address
- + " managed by " + association.getPackageName()
- + " disappeared; last seen on " + mDevicesLastNearby.get(address));
- }
-
+ Slog.i(LOG_TAG,
+ "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceDisappeared(address));
hasDeviceListeners = true;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index ed2e625..641b38d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -222,10 +222,12 @@
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",
"java/com/android/server/connectivity/DnsManager.java",
+ "java/com/android/server/connectivity/FullScore.java",
"java/com/android/server/connectivity/KeepaliveTracker.java",
"java/com/android/server/connectivity/LingerMonitor.java",
"java/com/android/server/connectivity/MockableSystemProperties.java",
@@ -233,8 +235,11 @@
"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 c295778..a9eb2c1 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 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 0515eca..8f56842 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.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;
@@ -69,10 +69,12 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+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.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;
@@ -84,7 +86,6 @@
import static java.util.Map.Entry;
import android.Manifest;
-import android.annotation.BoolRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -120,6 +121,7 @@
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;
@@ -143,7 +145,6 @@
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;
@@ -170,13 +171,14 @@
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;
@@ -211,6 +213,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
+import com.android.connectivity.resources.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
@@ -226,6 +229,7 @@
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;
@@ -233,6 +237,7 @@
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;
@@ -590,6 +595,18 @@
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.
*/
@@ -817,8 +834,7 @@
private ArrayMap<Integer, Integer> loadRestoreTimers() {
final String[] configs = mService.mResources.get().getStringArray(
- com.android.connectivity.resources.R.array
- .config_legacy_networktype_restore_timers);
+ R.array.config_legacy_networktype_restore_timers);
final ArrayMap<Integer, Integer> ret = new ArrayMap<>(configs.length);
for (final String config : configs) {
final String[] splits = TextUtils.split(config, ",");
@@ -1119,10 +1135,10 @@
}
/**
- * 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);
}
/**
@@ -1143,8 +1159,8 @@
/**
* @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);
}
/**
@@ -1181,7 +1197,8 @@
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
@@ -1200,7 +1217,7 @@
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);
@@ -1256,8 +1273,7 @@
mLegacyTypeTracker.loadSupportedTypes(mContext, mTelephonyManager);
mProtectedNetworks = new ArrayList<>();
- int[] protectedNetworks = context.getResources().getIntArray(
- com.android.internal.R.array.config_protectedNetworks);
+ int[] protectedNetworks = mResources.get().getIntArray(R.array.config_protectedNetworks);
for (int p : protectedNetworks) {
if (mLegacyTypeTracker.isTypeSupported(p) && !mProtectedNetworks.contains(p)) {
mProtectedNetworks.add(p);
@@ -1387,7 +1403,7 @@
mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
- private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, @BoolRes int id) {
+ private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) {
final boolean enable = mContext.getResources().getBoolean(id);
handleAlwaysOnNetworkRequest(networkRequest, enable);
}
@@ -1407,8 +1423,7 @@
if (enable) {
handleRegisterNetworkRequest(new NetworkRequestInfo(
- networkRequest, null,
- new Binder(),
+ Process.myUid(), networkRequest, null, new Binder(),
NetworkCallback.FLAG_INCLUDE_LOCATION_INFO,
null /* attributionTags */));
} else {
@@ -1422,8 +1437,14 @@
ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON, true /* defaultValue */);
handleAlwaysOnNetworkRequest(mDefaultWifiRequest,
ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED, false /* defaultValue */);
+ final boolean vehicleAlwaysRequested = mResources.get().getBoolean(
+ R.bool.config_vehicleInternalNetworkAlwaysRequested);
+ // TODO (b/183076074): remove legacy fallback after migrating overlays
+ final boolean legacyAlwaysRequested = mContext.getResources().getBoolean(
+ mContext.getResources().getIdentifier(
+ "config_vehicleInternalNetworkAlwaysRequested", "bool", "android"));
handleAlwaysOnNetworkRequest(mDefaultVehicleRequest,
- com.android.internal.R.bool.config_vehicleInternalNetworkAlwaysRequested);
+ vehicleAlwaysRequested || legacyAlwaysRequested);
}
private void registerSettingsCallbacks() {
@@ -1555,7 +1576,7 @@
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()));
}
/**
@@ -1771,7 +1792,8 @@
nai.network,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
nc, false /* includeLocationSensitiveInfo */,
- mDeps.getCallingUid(), callingPackageName, callingAttributionTag));
+ getCallingPid(), mDeps.getCallingUid(), callingPackageName,
+ callingAttributionTag));
}
}
@@ -1786,7 +1808,7 @@
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
nc,
false /* includeLocationSensitiveInfo */,
- mDeps.getCallingUid(), callingPackageName,
+ getCallingPid(), mDeps.getCallingUid(), callingPackageName,
callingAttributionTag));
}
}
@@ -1869,7 +1891,7 @@
return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
getNetworkCapabilitiesInternal(network),
false /* includeLocationSensitiveInfo */,
- mDeps.getCallingUid(), callingPackageName, callingAttributionTag);
+ getCallingPid(), mDeps.getCallingUid(), callingPackageName, callingAttributionTag);
}
@VisibleForTesting
@@ -1888,40 +1910,137 @@
return newNc;
}
- private boolean hasLocationPermission(int callerUid, @NonNull String callerPkgName,
- @Nullable String callingAttributionTag) {
- final long token = Binder.clearCallingIdentity();
- try {
- return mLocationPermissionChecker.checkLocationPermission(
- callerPkgName, callingAttributionTag, callerUid, null /* message */);
- } finally {
- Binder.restoreCallingIdentity(token);
+ /**
+ * Wrapper used to cache the permission check results performed for the corresponding
+ * app. This avoid performing multiple permission checks for different fields in
+ * NetworkCapabilities.
+ * Note: This wrapper does not support any sort of invalidation and thus must not be
+ * persistent or long-lived. It may only be used for the time necessary to
+ * compute the redactions required by one particular NetworkCallback or
+ * synchronous call.
+ */
+ private class RedactionPermissionChecker {
+ private final int mCallingPid;
+ private final int mCallingUid;
+ @NonNull private final String mCallingPackageName;
+ @Nullable private final String mCallingAttributionTag;
+
+ private Boolean mHasLocationPermission = null;
+ private Boolean mHasLocalMacAddressPermission = null;
+ private Boolean mHasSettingsPermission = null;
+
+ RedactionPermissionChecker(int callingPid, int callingUid,
+ @NonNull String callingPackageName, @Nullable String callingAttributionTag) {
+ mCallingPid = callingPid;
+ mCallingUid = callingUid;
+ mCallingPackageName = callingPackageName;
+ mCallingAttributionTag = callingAttributionTag;
}
+
+ private boolean hasLocationPermissionInternal() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mLocationPermissionChecker.checkLocationPermission(
+ mCallingPackageName, mCallingAttributionTag, mCallingUid,
+ null /* message */);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Returns whether the app holds location permission or not (might return cached result
+ * if the permission was already checked before).
+ */
+ public boolean hasLocationPermission() {
+ if (mHasLocationPermission == null) {
+ // If there is no cached result, perform the check now.
+ mHasLocationPermission = hasLocationPermissionInternal();
+ }
+ return mHasLocationPermission;
+ }
+
+ /**
+ * Returns whether the app holds local mac address permission or not (might return cached
+ * result if the permission was already checked before).
+ */
+ public boolean hasLocalMacAddressPermission() {
+ if (mHasLocalMacAddressPermission == null) {
+ // If there is no cached result, perform the check now.
+ mHasLocalMacAddressPermission =
+ checkLocalMacAddressPermission(mCallingPid, mCallingUid);
+ }
+ return mHasLocalMacAddressPermission;
+ }
+
+ /**
+ * Returns whether the app holds settings permission or not (might return cached
+ * result if the permission was already checked before).
+ */
+ public boolean hasSettingsPermission() {
+ if (mHasSettingsPermission == null) {
+ // If there is no cached result, perform the check now.
+ mHasSettingsPermission = checkSettingsPermission(mCallingPid, mCallingUid);
+ }
+ return mHasSettingsPermission;
+ }
+ }
+
+ private static boolean shouldRedact(@NetworkCapabilities.RedactionType long redactions,
+ @NetworkCapabilities.NetCapability long redaction) {
+ return (redactions & redaction) != 0;
+ }
+
+ /**
+ * Use the provided |applicableRedactions| to check the receiving app's
+ * permissions and clear/set the corresponding bit in the returned bitmask. The bitmask
+ * returned will be used to ensure the necessary redactions are performed by NetworkCapabilities
+ * before being sent to the corresponding app.
+ */
+ private @NetworkCapabilities.RedactionType long retrieveRequiredRedactions(
+ @NetworkCapabilities.RedactionType long applicableRedactions,
+ @NonNull RedactionPermissionChecker redactionPermissionChecker,
+ boolean includeLocationSensitiveInfo) {
+ long redactions = applicableRedactions;
+ if (shouldRedact(redactions, REDACT_FOR_ACCESS_FINE_LOCATION)) {
+ if (includeLocationSensitiveInfo
+ && redactionPermissionChecker.hasLocationPermission()) {
+ redactions &= ~REDACT_FOR_ACCESS_FINE_LOCATION;
+ }
+ }
+ if (shouldRedact(redactions, REDACT_FOR_LOCAL_MAC_ADDRESS)) {
+ if (redactionPermissionChecker.hasLocalMacAddressPermission()) {
+ redactions &= ~REDACT_FOR_LOCAL_MAC_ADDRESS;
+ }
+ }
+ if (shouldRedact(redactions, REDACT_FOR_NETWORK_SETTINGS)) {
+ if (redactionPermissionChecker.hasSettingsPermission()) {
+ redactions &= ~REDACT_FOR_NETWORK_SETTINGS;
+ }
+ }
+ return redactions;
}
@VisibleForTesting
@Nullable
NetworkCapabilities createWithLocationInfoSanitizedIfNecessaryWhenParceled(
@Nullable NetworkCapabilities nc, boolean includeLocationSensitiveInfo,
- int callerUid, @NonNull String callerPkgName, @Nullable String callingAttributionTag) {
+ int callingPid, int callingUid, @NonNull String callingPkgName,
+ @Nullable String callingAttributionTag) {
if (nc == null) {
return null;
}
- Boolean hasLocationPermission = null;
- final NetworkCapabilities newNc;
// Avoid doing location permission check if the transport info has no location sensitive
// data.
- if (includeLocationSensitiveInfo
- && nc.getTransportInfo() != null
- && nc.getTransportInfo().hasLocationSensitiveFields()) {
- hasLocationPermission =
- hasLocationPermission(callerUid, callerPkgName, callingAttributionTag);
- newNc = new NetworkCapabilities(nc, hasLocationPermission);
- } else {
- newNc = new NetworkCapabilities(nc, false /* parcelLocationSensitiveFields */);
- }
+ final RedactionPermissionChecker redactionPermissionChecker =
+ new RedactionPermissionChecker(callingPid, callingUid, callingPkgName,
+ callingAttributionTag);
+ final long redactions = retrieveRequiredRedactions(
+ nc.getApplicableRedactions(), redactionPermissionChecker,
+ includeLocationSensitiveInfo);
+ final NetworkCapabilities newNc = new NetworkCapabilities(nc, redactions);
// Reset owner uid if not destined for the owner app.
- if (callerUid != nc.getOwnerUid()) {
+ if (callingUid != nc.getOwnerUid()) {
newNc.setOwnerUid(INVALID_UID);
return newNc;
}
@@ -1930,23 +2049,17 @@
// Owner UIDs already checked above. No need to re-check.
return newNc;
}
- // If the caller does not want location sensitive data & target SDK >= S, then mask info.
- // Else include the owner UID iff the caller has location permission to provide backwards
+ // If the calling does not want location sensitive data & target SDK >= S, then mask info.
+ // Else include the owner UID iff the calling has location permission to provide backwards
// compatibility for older apps.
if (!includeLocationSensitiveInfo
&& isTargetSdkAtleast(
- Build.VERSION_CODES.S, callerUid, callerPkgName)) {
+ Build.VERSION_CODES.S, callingUid, callingPkgName)) {
newNc.setOwnerUid(INVALID_UID);
return newNc;
}
-
- if (hasLocationPermission == null) {
- // Location permission not checked yet, check now for masking owner UID.
- hasLocationPermission =
- hasLocationPermission(callerUid, callerPkgName, callingAttributionTag);
- }
// Reset owner uid if the app has no location permission.
- if (!hasLocationPermission) {
+ if (!redactionPermissionChecker.hasLocationPermission()) {
newNc.setOwnerUid(INVALID_UID);
}
return newNc;
@@ -1978,6 +2091,8 @@
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);
@@ -2437,6 +2552,11 @@
mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
}
+ private boolean checkLocalMacAddressPermission(int pid, int uid) {
+ return PERMISSION_GRANTED == mContext.checkPermission(
+ Manifest.permission.LOCAL_MAC_ADDRESS, pid, uid);
+ }
+
private void sendConnectedBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
@@ -2800,10 +2920,6 @@
}
pw.println();
- pw.println("NetworkStackClient logs:");
- pw.increaseIndent();
- NetworkStackClient.getInstance().dump(pw);
- pw.decreaseIndent();
pw.println();
pw.println("Permission Monitor:");
@@ -4463,6 +4579,18 @@
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;
@@ -4632,7 +4760,7 @@
mWakelockLogs.log("ACQUIRE for " + forWhom);
Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
final int lockTimeout = mResources.get().getInteger(
- com.android.connectivity.resources.R.integer.config_networkTransitionTimeout);
+ R.integer.config_networkTransitionTimeout);
mHandler.sendMessageDelayed(msg, lockTimeout);
}
@@ -4738,6 +4866,42 @@
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
@@ -4758,7 +4922,7 @@
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);
@@ -4985,7 +5149,7 @@
@Override
public void setRequireVpnForUids(boolean requireVpn, UidRange[] ranges) {
- PermissionUtils.enforceNetworkStackPermission(mContext);
+ enforceNetworkStackOrSettingsPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_REQUIRE_VPN_FOR_UIDS,
encodeBool(requireVpn), 0 /* arg2 */, ranges));
}
@@ -5023,7 +5187,7 @@
@Override
public void setLegacyLockdownVpnEnabled(boolean enabled) {
- enforceSettingsPermission();
+ enforceNetworkStackOrSettingsPermission();
mHandler.post(() -> mLockdownEnabled = enabled);
}
@@ -5263,6 +5427,8 @@
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;
@@ -5270,6 +5436,13 @@
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
@@ -5297,12 +5470,12 @@
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);
@@ -5313,6 +5486,7 @@
mBinder = null;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
+ mAsUid = asUid;
mNetworkRequestCounter.incrementCountOrThrow(mUid);
/**
* Location sensitive data not included in pending intent. Only included in
@@ -5322,14 +5496,15 @@
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,
@@ -5342,6 +5517,7 @@
mBinder = binder;
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
+ mAsUid = asUid;
mPendingIntent = null;
mNetworkRequestCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
@@ -5384,18 +5560,19 @@
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
@@ -5431,9 +5608,10 @@
@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)
@@ -5534,7 +5712,7 @@
}
@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) {
@@ -5546,6 +5724,12 @@
}
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];
@@ -5555,10 +5739,10 @@
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:
@@ -5610,7 +5794,8 @@
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
@@ -5637,25 +5822,27 @@
* 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,
@@ -5736,8 +5923,8 @@
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));
@@ -5804,7 +5991,7 @@
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);
@@ -5829,8 +6016,8 @@
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));
@@ -5885,12 +6072,37 @@
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);
}
@@ -5929,6 +6141,10 @@
// (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<>();
@@ -5980,33 +6196,37 @@
/**
* 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;
@@ -6014,12 +6234,17 @@
@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 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.setUids(new ArraySet<>());
+ netCap.setSingleUid(asUid);
restrictRequestUidsForCallerAndSetRequestorInfo(
netCap, requestorUid, requestorPackageName);
return netCap;
@@ -6229,6 +6454,71 @@
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
@@ -6362,10 +6652,16 @@
return;
}
- int mark = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_networkWakeupPacketMark);
- int mask = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_networkWakeupPacketMask);
+ int mark = mResources.get().getInteger(R.integer.config_networkWakeupPacketMark);
+ int mask = mResources.get().getInteger(R.integer.config_networkWakeupPacketMask);
+
+ // TODO (b/183076074): remove legacy fallback after migrating overlays
+ final int legacyMark = mContext.getResources().getInteger(mContext.getResources()
+ .getIdentifier("config_networkWakeupPacketMark", "integer", "android"));
+ final int legacyMask = mContext.getResources().getInteger(mContext.getResources()
+ .getIdentifier("config_networkWakeupPacketMask", "integer", "android"));
+ mark = mark == 0 ? legacyMark : mark;
+ mask = mask == 0 ? legacyMask : mask;
// Mask/mark of zero will not detect anything interesting.
// Don't install rules unless both values are nonzero.
@@ -6558,8 +6854,7 @@
private void updateWakeOnLan(@NonNull LinkProperties lp) {
if (mWolSupportedInterfaces == null) {
mWolSupportedInterfaces = new ArraySet<>(mResources.get().getStringArray(
- com.android.connectivity.resources.R.array
- .config_wakeonlan_supported_interfaces));
+ R.array.config_wakeonlan_supported_interfaces));
}
lp.setWakeOnLanSupported(mWolSupportedInterfaces.contains(lp.getInterfaceName()));
}
@@ -7143,7 +7438,7 @@
putParcelable(
bundle,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
- nc, includeLocationSensitiveInfo, nri.mUid,
+ nc, includeLocationSensitiveInfo, nri.mPid, nri.mUid,
nrForCallback.getRequestorPackageName(),
nri.mCallingAttributionTag));
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
@@ -7164,7 +7459,7 @@
putParcelable(
bundle,
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
- netCap, includeLocationSensitiveInfo, nri.mUid,
+ netCap, includeLocationSensitiveInfo, nri.mPid, nri.mUid,
nrForCallback.getRequestorPackageName(),
nri.mCallingAttributionTag));
break;
@@ -7920,9 +8215,9 @@
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);
}
@@ -7950,12 +8245,12 @@
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(
@@ -7990,7 +8285,7 @@
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);
}
}
@@ -8123,7 +8418,7 @@
public String getCaptivePortalServerUrl() {
enforceNetworkStackOrSettingsPermission();
String settingUrl = mResources.get().getString(
- com.android.connectivity.resources.R.string.config_networkCaptivePortalServerUrl);
+ R.string.config_networkCaptivePortalServerUrl);
if (!TextUtils.isEmpty(settingUrl)) {
return settingUrl;
@@ -8300,7 +8595,7 @@
}
}
- private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+ private int getVpnType(@Nullable NetworkAgentInfo vpn) {
if (vpn == null) return VpnManager.TYPE_VPN_NONE;
final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
@@ -8755,7 +9050,7 @@
// 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);
@@ -9239,7 +9534,7 @@
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;
@@ -9410,7 +9705,7 @@
}
// 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);
}
@@ -9432,7 +9727,7 @@
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) {
@@ -9442,12 +9737,12 @@
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;
}
@@ -9551,7 +9846,7 @@
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/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f04af8b..0c3d884 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -448,7 +448,7 @@
final GZIPOutputStream gzipOutputStream =
new GZIPOutputStream(new FileOutputStream(fd));
FileUtils.copy(in, gzipOutputStream);
- gzipOutputStream.finish();
+ gzipOutputStream.close();
} else {
FileUtils.copy(in, new FileOutputStream(fd));
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7f96aff..09f4c22 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.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 @@
}
@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 @@
}
}
+ @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 @@
// 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 @@
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 @@
}
}
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 @@
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/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index a09dbc7..ceb12c8 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -21,6 +21,21 @@
}
],
"file_patterns": ["NotificationManagerService\\.java"]
+ },
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "include-filter": "android.content.cts.ClipboardManagerTest"
+ },
+ {
+ "include-filter": "android.content.cts.ClipDataTest"
+ },
+ {
+ "include-filter": "android.content.cts.ClipDescriptionTest"
+ }
+ ],
+ "file_patterns": ["ClipboardService\\.java"]
}
],
"presubmit-large": [
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7276c78..3ea4458 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -154,6 +154,7 @@
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 @@
|| 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 @@
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 @@
}
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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
&& 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 @@
}
}
+ 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 @@
}
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 @@
}
}
- 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 f566277..09873f4 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.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 @@
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 051cd99..cd2d6d4 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -29,7 +29,10 @@
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 @@
@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 @@
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 @@
}
}
- 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 @@
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 @@
mContext.getSystemService(AppOpsManager.class)
.checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
- enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+ enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
@@ -553,11 +584,14 @@
* <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 @@
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 9636641..211999f 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.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.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 @@
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 1b352c7..c6405e0 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 @@
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.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 @@
}
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 @@
+ 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 @@
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 @@
}
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 @@
active.mNumActive++;
}
r.isForeground = true;
+ r.mStartForegroundCount++;
if (!stopProcStatsOp) {
ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1901,6 +1928,7 @@
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 @@
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 @@
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 @@
// 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 @@
r.foregroundId = 0;
r.foregroundNoti = null;
r.mAllowWhileInUsePermissionInFgs = false;
+ r.mAllowStartForeground = REASON_DENIED;
// Clear start entries.
r.clearDeliveredStartsLocked();
@@ -5428,8 +5458,9 @@
* @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 @@
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 @@
*/
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 @@
+ ",callingUid:" + tempAllowListReason.mCallingUid))
+ ">"
+ "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+ + "; callerTargetSdkVersion:" + callerTargetSdkVersion
+ + "; startForegroundCount:" + r.mStartForegroundCount
+ "]";
if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
r.mLoggedInfoAllowStartForeground = false;
@@ -5663,8 +5712,8 @@
}
if (ret == REASON_DENIED) {
- if (mAm.checkPermission(SYSTEM_ALERT_WINDOW, callingPid,
- callingUid) == PERMISSION_GRANTED) {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
}
}
@@ -5771,7 +5820,12 @@
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 5859cea..e16f4c9 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 @@
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 @@
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 @@
"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 @@
// 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 @@
*/
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 @@
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 @@
case KEY_FG_TO_BG_FGS_GRACE_DURATION:
updateFgToBgFgsGraceDuration();
break;
+ case KEY_FGS_START_FOREGROUND_TIMEOUT:
+ updateFgsStartForegroundTimeout();
+ break;
default:
break;
}
@@ -819,6 +846,13 @@
/*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 @@
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 @@
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 c4548a3..2bceb20 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -182,6 +182,7 @@
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
import android.app.backup.IBackupManager;
+import android.app.job.JobParameters;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManager;
@@ -2603,8 +2604,8 @@
}
@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"})
@@ -2638,8 +2639,7 @@
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;
}
}
@@ -2648,10 +2648,10 @@
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 */);
}
@@ -3490,7 +3490,9 @@
// Clear its scheduled jobs
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
- js.cancelJobsForUid(appInfo.uid, "clear data");
+ // Clearing data is akin to uninstalling. The app is force stopped before we
+ // get to this point, so the reason won't be checked by the app.
+ js.cancelJobsForUid(appInfo.uid, JobParameters.STOP_REASON_USER, "clear data");
// Clear its pending alarms
AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
@@ -3902,7 +3904,7 @@
// 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 {
@@ -6098,7 +6100,7 @@
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
- info.uid, true);
+ info.uid);
} else {
app = null;
}
@@ -7703,9 +7705,8 @@
*/
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();
@@ -7716,8 +7717,7 @@
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.");
@@ -7726,8 +7726,7 @@
if (incrementalService != null) {
final IncrementalManager incrementalManager = new IncrementalManager(
IIncrementalService.Stub.asInterface(incrementalService));
- IncrementalMetrics metrics = incrementalManager.getMetrics(codePath);
- millisSinceOldestPendingRead = metrics.getMillisSinceOldestPendingRead();
+ incrementalMetrics = incrementalManager.getMetrics(codePath);
}
}
}
@@ -7757,7 +7756,9 @@
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
@@ -7770,7 +7771,8 @@
}
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);
}
@@ -7952,7 +7954,8 @@
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;
}
@@ -7977,7 +7980,7 @@
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);
}
}
@@ -8066,12 +8069,15 @@
* @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.
@@ -8132,6 +8138,18 @@
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
@@ -11914,7 +11932,7 @@
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;
@@ -13624,7 +13642,7 @@
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,
@@ -13667,7 +13685,7 @@
ApplicationInfo targetInfo) {
ProcessRecord pr;
synchronized (this) {
- pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid, true);
+ pr = getProcessRecordLocked(targetInfo.processName, targetInfo.uid);
}
try {
@@ -15414,8 +15432,7 @@
@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);
@@ -15822,7 +15839,7 @@
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 31ea14a..074e8fe 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 @@
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/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index d8d6528..70a54cf 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -143,27 +143,45 @@
* Request that autofill data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
*/
public void requestAutofillData(List<IBinder> activityTokens, int callingUid,
String callingPackage) {
requestData(activityTokens, true /* requestAutofillData */,
true /* fetchData */, false /* fetchScreenshot */,
true /* allowFetchData */, false /* allowFetchScreenshot */,
- callingUid, callingPackage);
+ false /* ignoreTopActivityCheck */, callingUid, callingPackage);
}
/**
* Request that assist data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
*/
public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
int callingUid, String callingPackage) {
+ requestAssistData(activityTokens, fetchData, fetchScreenshot, allowFetchData,
+ allowFetchScreenshot, false /* ignoreTopActivityCheck */, callingUid,
+ callingPackage);
+ }
+
+ /**
+ * Request that assist data be loaded asynchronously. The resulting data will be provided
+ * through the {@link AssistDataRequesterCallbacks}.
+ *
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, int, String,
+ * boolean)}.
+ */
+ public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+ final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
+ boolean ignoreTopActivityCheck, int callingUid, String callingPackage) {
requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot,
- allowFetchData, allowFetchScreenshot, callingUid, callingPackage);
+ allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck, callingUid,
+ callingPackage);
}
/**
@@ -183,10 +201,13 @@
* is allowed to fetch the assist data
* @param allowFetchScreenshot to be joined with other checks, determines whether or not the
* requester is allowed to fetch the assist screenshot
+ * @param ignoreTopActivityCheck overrides the check for whether the activity is in focus when
+ * making the request. Used when passing an activity from Recents.
*/
private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData,
final boolean fetchData, final boolean fetchScreenshot, boolean allowFetchData,
- boolean allowFetchScreenshot, int callingUid, String callingPackage) {
+ boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
+ String callingPackage) {
// TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
// then no assist data is requested for any of the other activities
@@ -230,7 +251,8 @@
receiverExtras, topActivity, 0 /* flags */)
: mActivityTaskManager.requestAssistContextExtras(
ASSIST_CONTEXT_FULL, this, receiverExtras, topActivity,
- /* focused= */ i == 0, /* newSessionId= */ i == 0);
+ /* checkActivityIsTop= */ (i == 0)
+ && !ignoreTopActivityCheck, /* newSessionId= */ i == 0);
if (result) {
mPendingDataCount++;
} else if (i == 0) {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index e74c936..60e8d54 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 c3f97ad..5937a18 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 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 @@
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 e79f096..9ecae42 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 @@
}
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 @@
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 c5f082a..1839e2a 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 @@
@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 @@
&& 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 @@
@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 @@
@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 @@
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 2c8794d..ee4526b 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 @@
// 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 @@
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 4c9ab63..f9296a0 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 @@
/** 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 @@
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;
@@ -241,7 +255,7 @@
/**
* For a consumer of type {@link EnergyConsumerType#OTHER}, updates
* {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
- * charge consumed (in microcouloumbs) between the previously stored values and the passed-in
+ * charge consumed (in microcoulombs) between the previously stored values and the passed-in
* values.
*
* @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
@@ -341,11 +355,11 @@
return numOrdinals;
}
- /** Calculate charge consumption (in microcouloumbs) from a given energy and voltage */
+ /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
private long calculateChargeConsumedUC(long deltaEnergyUJ, int avgVoltageMV) {
// To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
- // since the last snapshot. Round up to the nearest whole long.
- return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV + 1) / 2) / avgVoltageMV;
+ // since the last snapshot. Round off to the nearest whole long.
+ return (deltaEnergyUJ * MILLIVOLTS_PER_VOLT + (avgVoltageMV / 2)) / avgVoltageMV;
}
}
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 4643610..f4ce723 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 @@
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 @@
}
@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 93f30cc..ab4a2d5 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 @@
}
// 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 @@
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 @@
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 @@
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 @@
: 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 51bcde8..2cde423 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_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_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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 42e7ff4..cb23b89 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 @@
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 9cd9902..54c3512 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 @@
// 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 @@
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 ec2020f..31183cac 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
}
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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
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 0eae661..57de708 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.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 @@
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 @@
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,10 +176,186 @@
}
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() + "]";
+ }
+ }
+
/**
* SystemService lifecycle for GameService.
*
@@ -150,6 +372,8 @@
public void onStart() {
mService = new GameManagerService(getContext());
publishBinderService(Context.GAME_SERVICE, mService);
+ mService.registerDeviceConfigListener();
+ mService.registerPackageReceiver();
}
@Override
@@ -171,9 +395,8 @@
}
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 @@
}
}
+ /**
+ * 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 @@
@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 @@
@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 @@
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
+ updateCompatModeDownscale(packageName, gameMode);
}
/**
@@ -258,6 +532,8 @@
@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 @@
}
}
+ 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 0000000..e4c0002
--- /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 ad5a65c..5a99e0e 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.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.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 @@
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 @@
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 @@
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 @@
@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 @@
* @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 @@
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 @@
* @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) {
@@ -293,6 +301,34 @@
}
/**
+ * Get the hibernating packages for the given user. This is equivalent to the list of
+ * packages for the user that return true for {@link #isHibernatingForUser}.
+ */
+ @NonNull List<String> getHibernatingPackagesForUser(int userId) {
+ ArrayList<String> hibernatingPackages = new ArrayList<>();
+ 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, methodName);
+ if (!checkUserStatesExist(userId, methodName)) {
+ return hibernatingPackages;
+ }
+ synchronized (mLock) {
+ Map<String, UserLevelState> userStates = mUserStates.get(userId);
+ for (UserLevelState state : userStates.values()) {
+ if (state.hibernated) {
+ hibernatingPackages.add(state.packageName);
+ }
+ }
+ return hibernatingPackages;
+ }
+ }
+
+ /**
* Put an app into hibernation for a given user, allowing user-level optimizations to occur.
*
* @param pkgState package hibernation state
@@ -335,7 +371,7 @@
@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);
}
@@ -439,10 +475,15 @@
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
@@ -512,6 +553,20 @@
}
}
+ 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));
@@ -610,6 +665,11 @@
}
@Override
+ public List<String> getHibernatingPackagesForUser(int userId) {
+ return mService.getHibernatingPackagesForUser(userId);
+ }
+
+ @Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err, @NonNull String[] args,
@Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
@@ -673,10 +733,14 @@
IPackageManager getPackageManager();
+ PackageManagerInternal getPackageManagerInternal();
+
IActivityManager getActivityManager();
UserManager getUserManager();
+ Executor getBackgroundExecutor();
+
HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore();
HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId);
@@ -705,6 +769,11 @@
}
@Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ @Override
public IActivityManager getActivityManager() {
return ActivityManager.getService();
}
@@ -715,6 +784,11 @@
}
@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 c83659d..24cf433 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 @@
* @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 109ffe3..7bc7105 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 @@
synchronized (this) {
if (mWriteScheduled) {
mWriteScheduled = false;
+ mFastWriteScheduled = false;
mHandler.removeCallbacks(mWriteRunner);
doWrite = true;
}
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index 2b0157c..e16a6cc 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -31,14 +31,18 @@
import static android.app.AppOpsManager.flagsToString;
import static android.app.AppOpsManager.getUidStateName;
+import static java.lang.Long.min;
import static java.lang.Math.max;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.os.AsyncTask;
+import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Process;
+import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.TypedXmlPullParser;
@@ -82,8 +86,16 @@
static final String TIMELINE_FILE_SUFFIX = "tl";
private static final String TAG = DiscreteRegistry.class.getSimpleName();
- private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis();
- private static final long TIMELINE_QUANTIZATION = Duration.ofMinutes(1).toMillis();
+ private static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis";
+ private static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION =
+ "discrete_history_quantization_millis";
+ private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis();
+ private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
+ private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
+ Duration.ofMinutes(1).toMillis();
+
+ private static long sDiscreteHistoryCutoff;
+ private static long sDiscreteHistoryQuantization;
private static final String TAG_HISTORY = "h";
private static final String ATTR_VERSION = "v";
@@ -116,7 +128,7 @@
private final @NonNull Object mInMemoryLock;
@GuardedBy("mOnDiskLock")
- private final File mDiscreteAccessDir;
+ private File mDiscreteAccessDir;
@GuardedBy("mInMemoryLock")
private DiscreteOps mDiscreteOps;
@@ -126,10 +138,42 @@
DiscreteRegistry(Object inMemoryLock) {
mInMemoryLock = inMemoryLock;
- mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"),
- "discrete");
- createDiscreteAccessDir();
- mDiscreteOps = new DiscreteOps();
+ }
+
+ void systemReady() {
+ synchronized (mOnDiskLock) {
+ mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"),
+ "discrete");
+ createDiscreteAccessDirLocked();
+ mDiscreteOps = new DiscreteOps();
+ }
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
+ AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> {
+ setDiscreteHistoryParameters(p);
+ });
+ sDiscreteHistoryCutoff = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_DISCRETE_HISTORY_CUTOFF, DEFAULT_DISCRETE_HISTORY_CUTOFF);
+ sDiscreteHistoryQuantization = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_DISCRETE_HISTORY_QUANTIZATION, DEFAULT_DISCRETE_HISTORY_QUANTIZATION);
+ }
+
+ private void setDiscreteHistoryParameters(DeviceConfig.Properties p) {
+ if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) {
+ sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF,
+ DEFAULT_DISCRETE_HISTORY_CUTOFF);
+ if (!Build.IS_DEBUGGABLE) {
+ sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF,
+ sDiscreteHistoryCutoff);
+ }
+ }
+ if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) {
+ sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION,
+ DEFAULT_DISCRETE_HISTORY_QUANTIZATION);
+ if (!Build.IS_DEBUGGABLE) {
+ sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION,
+ sDiscreteHistoryQuantization);
+ }
+ }
}
private void createDiscreteAccessDir() {
@@ -142,6 +186,7 @@
}
}
+ /* can be called only after HistoricalRegistry.isPersistenceInitialized() check */
void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime,
long accessDuration) {
@@ -156,6 +201,10 @@
void writeAndClearAccessHistory() {
synchronized (mOnDiskLock) {
+ if (mDiscreteAccessDir == null) {
+ Slog.e(TAG, "State not saved - persistence not initialized.");
+ return;
+ }
final File[] files = mDiscreteAccessDir.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
@@ -166,7 +215,7 @@
try {
long timestamp = Long.valueOf(fileName.substring(0,
fileName.length() - TIMELINE_FILE_SUFFIX.length()));
- if (Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
+ if (Instant.now().minus(sDiscreteHistoryCutoff,
ChronoUnit.MILLIS).toEpochMilli() > timestamp) {
f.delete();
Slog.e(TAG, "Deleting file " + fileName);
@@ -229,7 +278,7 @@
private void readDiscreteOpsFromDisk(DiscreteOps discreteOps) {
synchronized (mOnDiskLock) {
- long beginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
+ long beginTimeMillis = Instant.now().minus(sDiscreteHistoryCutoff,
ChronoUnit.MILLIS).toEpochMilli();
final File[] files = mDiscreteAccessDir.listFiles();
@@ -423,6 +472,16 @@
}
}
+ private void createDiscreteAccessDirLocked() {
+ if (!mDiscreteAccessDir.exists()) {
+ if (!mDiscreteAccessDir.mkdirs()) {
+ Slog.e(TAG, "Failed to create DiscreteRegistry directory");
+ }
+ FileUtils.setPermissions(mDiscreteAccessDir.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
+ }
+ }
+
private final class DiscreteUidOps {
ArrayMap<String, DiscretePackageOps> mPackages;
@@ -663,7 +722,7 @@
long accessTime, long accessDuration) {
List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList(
attributionTag);
- accessTime = accessTime / TIMELINE_QUANTIZATION * TIMELINE_QUANTIZATION;
+ accessTime = accessTime / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
int nAttributedOps = attributedOps.size();
int i = nAttributedOps;
@@ -674,7 +733,7 @@
}
if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) {
if (accessDuration != previousOp.mNoteDuration
- && accessDuration > TIMELINE_QUANTIZATION) {
+ && accessDuration > sDiscreteHistoryQuantization) {
break;
} else {
return;
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 4435c47..9ff5492 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -246,6 +246,7 @@
+ " by which to push history on next write");
}
}
+ mDiscreteRegistry.systemReady();
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 804550b..2ce60d0 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 @@
}
}
+ /** @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 @@
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 @@
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 81ce2d5..677ea5d 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 @@
*
* @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 @@
* @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;
@@ -197,7 +199,7 @@
return mListener;
}
- public final int getTargetUserId() {
+ public int getTargetUserId() {
return mTargetUserId;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 20c25c3..6c480f1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -55,7 +55,7 @@
private static final String BASE_TAG = "BiometricScheduler";
// Number of recent operations to keep in our logs for dumpsys
- private static final int LOG_NUM_RECENT_OPERATIONS = 50;
+ protected static final int LOG_NUM_RECENT_OPERATIONS = 50;
/**
* Contains all the necessary information for a HAL operation.
@@ -196,10 +196,10 @@
}
}
- @NonNull private final String mBiometricTag;
+ @NonNull protected final String mBiometricTag;
@Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@NonNull private final IBiometricService mBiometricService;
- @NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
+ @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalCallback mInternalCallback;
@VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
@VisibleForTesting @Nullable Operation mCurrentOperation;
@@ -294,11 +294,11 @@
return mInternalCallback;
}
- private String getTag() {
+ protected String getTag() {
return BASE_TAG + "/" + mBiometricTag;
}
- private void startNextOperationIfIdle() {
+ protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
return;
@@ -310,6 +310,7 @@
mCurrentOperation = mPendingOperations.poll();
final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor;
+ Slog.d(getTag(), "[Polled] " + mCurrentOperation);
// If the operation at the front of the queue has been marked for cancellation, send
// ERROR_CANCELED. No need to start this client.
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
new file mode 100644
index 0000000..3d69326
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.BiometricsProto;
+
+/**
+ * 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<U> mUserStartedCallback;
+
+ public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @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);
+ mUserStartedCallback = callback;
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_START_USER;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
new file mode 100644
index 0000000..1f6e1e9
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+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 {
+ void onUserStopped();
+ }
+
+ @NonNull @VisibleForTesting
+ 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,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mUserStoppedCallback = callback;
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_STOP_USER;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
new file mode 100644
index 0000000..f015a80
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -0,0 +1,140 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+
+/**
+ * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
+ */
+public class UserAwareBiometricScheduler extends BiometricScheduler {
+
+ private static final String BASE_TAG = "UaBiometricScheduler";
+
+ /**
+ * Interface to retrieve the owner's notion of the current userId. Note that even though
+ * the scheduler can determine this based on its history of processed clients, we should still
+ * query the owner since it may be cleared due to things like HAL death, etc.
+ */
+ public interface CurrentUserRetriever {
+ int getCurrentUserId();
+ }
+
+ public interface UserSwitchCallback {
+ @NonNull StopUserClient<?> getStopUserClient(int userId);
+ @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
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+ mHandler.post(() -> {
+ Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success);
+
+ startNextOperationIfIdle();
+ });
+ }
+ }
+
+ @VisibleForTesting
+ UserAwareBiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull IBiometricService biometricService,
+ @NonNull CurrentUserRetriever currentUserRetriever,
+ @NonNull UserSwitchCallback userSwitchCallback) {
+ super(tag, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS);
+
+ mCurrentUserRetriever = currentUserRetriever;
+ mUserSwitchCallback = userSwitchCallback;
+ mClientFinishedCallback = new ClientFinishedCallback();
+ }
+
+ public UserAwareBiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull CurrentUserRetriever currentUserRetriever,
+ @NonNull UserSwitchCallback userSwitchCallback) {
+ this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever,
+ userSwitchCallback);
+ }
+
+ @Override
+ protected String getTag() {
+ return BASE_TAG + "/" + mBiometricTag;
+ }
+
+ @Override
+ protected void startNextOperationIfIdle() {
+ if (mCurrentOperation != null) {
+ Slog.v(getTag(), "Not idle, current operation: " + mCurrentOperation);
+ return;
+ }
+ if (mPendingOperations.isEmpty()) {
+ Slog.d(getTag(), "No operations, returning to idle");
+ return;
+ }
+
+ final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
+ final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId();
+
+ if (nextUserId == currentUserId) {
+ super.startNextOperationIfIdle();
+ } else if (currentUserId == UserHandle.USER_NULL) {
+ final BaseClientMonitor startClient =
+ mUserSwitchCallback.getStartUserClient(nextUserId);
+ Slog.d(getTag(), "[Starting User] " + startClient);
+ startClient.start(mClientFinishedCallback);
+ } else {
+ 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 9f5dc69..d10fd4f 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 @@
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 07d173c..0c883b0 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 @@
@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 0eb51fd..58eb3ba 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 @@
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 7a846f5..8cbb896 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 @@
@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 773647b..af826c2 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 @@
@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 75888a5..0ece884 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 @@
@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 855ee1d..405e2b2 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 @@
@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 ca29057..a9be8e1 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 @@
@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 @@
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 @@
}
@Nullable
- private synchronized IFace getHalInstance() {
+ @VisibleForTesting
+ synchronized IFace getHalInstance() {
if (mTestHalEnabled) {
return new TestHal();
}
@@ -214,22 +213,6 @@
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,37 +221,21 @@
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;
- }
+ final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
- 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());
-
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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,
FaceUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@@ -303,25 +270,10 @@
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);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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 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 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 @@
@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 @@
@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);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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 @@
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());
-
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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;
- }
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
- 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);
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
- }
+ scheduleForSensor(sensorId, client);
});
}
@@ -580,29 +436,14 @@
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());
-
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
- } 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 48796c1..ba678f3 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 @@
@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 ce728bc..5e57950 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 @@
@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 2863f9f..2294173 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 @@
@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 0000000..c364dbb
--- /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 0000000..06328e3
--- /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 c6f39aa..ee36775 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.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.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 @@
@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 @@
@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 @@
}
@Override
- public void onStateChanged(int cookie, byte state) {
- // TODO(b/162973174)
- }
-
- @Override
public void onChallengeGenerated(long challenge) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
@@ -424,6 +426,10 @@
});
}
+ @Override
+ public void onSessionClosed() {
+ mHandler.post(mScheduler::onUserStopped);
+ }
}
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@@ -431,9 +437,53 @@
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;
@@ -447,11 +497,6 @@
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;
@@ -465,20 +510,6 @@
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;
}
@@ -499,7 +530,7 @@
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 4ca85d0..c63af7e 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.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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 50756c8..79e361f 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 @@
}
}
+ 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 76a47d3..4e5d12d 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 @@
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 620a9cf..9e9d0ee 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 @@
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 63fa66c..fd4aece 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 @@
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 3c9cced..83c6421 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 @@
@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 ce1a318..ed2345e 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 @@
@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 c930360..e20544a 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 @@
@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 80d1a0f..6cd2ef1 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 @@
@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 1b5def6..972071c 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 @@
@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 @@
mContext = context;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
- mLazyDaemon = this::getHalInstance;
mHandler = new Handler(Looper.getMainLooper());
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -169,7 +167,8 @@
}
@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 @@
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,62 +259,32 @@
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());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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,
FingerprintUtils.getInstance(sensorId));
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ scheduleForSensor(sensorId, client);
});
}
@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 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 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 @@
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 @@
@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);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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 @@
@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);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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 @@
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());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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 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());
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
- } 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 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);
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(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 c622208..9a9d6ab 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 @@
@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 adffba2..b00c592 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 @@
@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 ebb4fe6..d9bf1c3 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 @@
@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 0000000..2d40c91
--- /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 0000000..7055d65
--- /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 5631647..4862d849 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.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.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 @@
@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 @@
@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 @@
}
@Override
- public void onStateChanged(int cookie, byte state) {
- // TODO(b/162973174)
- }
-
- @Override
public void onChallengeGenerated(long challenge) {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
@@ -403,6 +404,11 @@
invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
});
}
+
+ @Override
+ public void onSessionClosed() {
+ mHandler.post(mScheduler::onUserStopped);
+ }
}
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
@@ -411,9 +417,53 @@
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;
@@ -427,11 +477,6 @@
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;
@@ -445,20 +490,6 @@
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;
}
@@ -479,7 +510,7 @@
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 0b7f3ab..abc3597 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.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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 40d2073..cd24576 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;
@@ -38,10 +54,13 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserManager;
-import android.stats.camera.nano.CameraStreamProto;
+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.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 @@
}
}
+ 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 @@
};
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 @@
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 71565301..874e9a6 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -106,15 +106,29 @@
return bits;
}
- private void openPipe() {
+ private boolean openPipe() {
try {
- mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
- mPipe.write(createOpenHandshake());
- } catch (IOException e) {
+ final RandomAccessFile pipe = new RandomAccessFile(PIPE_DEVICE, "rw");
try {
- if (mPipe != null) mPipe.close();
- } catch (IOException ee) {}
+ pipe.write(createOpenHandshake());
+ mPipe = pipe;
+ return true;
+ } catch (IOException ignore) {
+ pipe.close();
+ }
+ } catch (IOException ignore) {
+ }
+ return false;
+ }
+
+ private void closePipe() {
+ try {
+ final RandomAccessFile pipe = mPipe;
mPipe = null;
+ if (pipe != null) {
+ pipe.close();
+ }
+ } catch (IOException ignore) {
}
}
@@ -129,8 +143,7 @@
// There's no guarantee that QEMU pipes will be ready at the moment
// this method is invoked. We simply try to get the pipe open and
// retry on failure indefinitely.
- while (mPipe == null) {
- openPipe();
+ while ((mPipe == null) && !openPipe()) {
Thread.sleep(100);
}
int size = mPipe.readInt();
@@ -140,10 +153,7 @@
mHostClipboardCallback.onHostClipboardUpdated(
new String(receivedData));
} catch (IOException e) {
- try {
- mPipe.close();
- } catch (IOException ee) {}
- mPipe = null;
+ closePipe();
} catch (InterruptedException e) {}
}
}
@@ -1044,27 +1054,16 @@
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(
@@ -1075,4 +1074,21 @@
}
});
}
+
+ /**
+ * 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 028cfee..9326d69 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 @@
}
/**
+ * 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/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 7b20ded..acf39f0 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -26,6 +26,7 @@
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_NO_SUCH_SLOT;
import static android.net.SocketKeepalive.ERROR_STOP_REASON_UNINITIALIZED;
import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
@@ -36,6 +37,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.net.ConnectivityResources;
import android.net.ISocketKeepaliveCallback;
import android.net.InetAddresses;
import android.net.InvalidPacketException;
@@ -56,7 +58,7 @@
import android.util.Log;
import android.util.Pair;
-import com.android.internal.R;
+import com.android.connectivity.resources.R;
import com.android.internal.util.IndentingPrintWriter;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.IpUtils;
@@ -111,10 +113,19 @@
mTcpController = new TcpKeepaliveController(handler);
mContext = context;
mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
- mReservedPrivilegedSlots = mContext.getResources().getInteger(
- R.integer.config_reservedPrivilegedKeepaliveSlots);
- mAllowedUnprivilegedSlotsForUid = mContext.getResources().getInteger(
- R.integer.config_allowedUnprivilegedKeepalivePerUid);
+
+ // TODO (b/183076074): stop reading legacy resources after migrating overlays
+ final int legacyReservedSlots = mContext.getResources().getInteger(
+ mContext.getResources().getIdentifier(
+ "config_reservedPrivilegedKeepaliveSlots", "integer", "android"));
+ final int legacyAllowedSlots = mContext.getResources().getInteger(
+ mContext.getResources().getIdentifier(
+ "config_allowedUnprivilegedKeepalivePerUid", "integer", "android"));
+ final ConnectivityResources res = new ConnectivityResources(mContext);
+ mReservedPrivilegedSlots = Math.min(legacyReservedSlots, res.get().getInteger(
+ R.integer.config_reservedPrivilegedKeepaliveSlots));
+ mAllowedUnprivilegedSlotsForUid = Math.min(legacyAllowedSlots, res.get().getInteger(
+ R.integer.config_allowedUnprivilegedKeepalivePerUid));
}
/**
@@ -518,6 +529,8 @@
}
} else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
throw new IllegalStateException("Unexpected stop reason: " + reason);
+ } else if (reason == ERROR_NO_SUCH_SLOT) {
+ throw new IllegalStateException("No such slot: " + reason);
} else {
notifyErrorCallback(ki.mCallback, reason);
}
diff --git a/services/core/java/com/android/server/connectivity/LingerMonitor.java b/services/core/java/com/android/server/connectivity/LingerMonitor.java
index adec7ad..032612c 100644
--- a/services/core/java/com/android/server/connectivity/LingerMonitor.java
+++ b/services/core/java/com/android/server/connectivity/LingerMonitor.java
@@ -24,6 +24,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
+import android.net.ConnectivityResources;
import android.net.NetworkCapabilities;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -34,7 +36,7 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import com.android.internal.R;
+import com.android.connectivity.resources.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.MessageUtils;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
@@ -72,6 +74,7 @@
new Class[] { LingerMonitor.class }, new String[]{ "NOTIFY_TYPE_" });
private final Context mContext;
+ final Resources mResources;
private final NetworkNotificationManager mNotifier;
private final int mDailyLimit;
private final long mRateLimitMillis;
@@ -89,6 +92,7 @@
public LingerMonitor(Context context, NetworkNotificationManager notifier,
int dailyLimit, long rateLimitMillis) {
mContext = context;
+ mResources = new ConnectivityResources(mContext).get();
mNotifier = notifier;
mDailyLimit = dailyLimit;
mRateLimitMillis = rateLimitMillis;
@@ -128,8 +132,7 @@
@VisibleForTesting
public boolean isNotificationEnabled(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
// TODO: Evaluate moving to CarrierConfigManager.
- String[] notifySwitches =
- mContext.getResources().getStringArray(R.array.config_networkNotifySwitches);
+ String[] notifySwitches = mResources.getStringArray(R.array.config_networkNotifySwitches);
if (VDBG) {
Log.d(TAG, "Notify on network switches: " + Arrays.toString(notifySwitches));
@@ -178,8 +181,7 @@
// Notify the user of a network switch using a notification or a toast.
private void notify(NetworkAgentInfo fromNai, NetworkAgentInfo toNai, boolean forceToast) {
- int notifyType =
- mContext.getResources().getInteger(R.integer.config_networkNotifySwitchType);
+ int notifyType = mResources.getInteger(R.integer.config_networkNotifySwitchType);
if (notifyType == NOTIFY_TYPE_NOTIFICATION && forceToast) {
notifyType = NOTIFY_TYPE_TOAST;
}
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index f3d2012..6ea84ce 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 @@
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 @@
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 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 @@
}
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 @@
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 103ab95..a8cbcb5 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.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;
@@ -633,7 +634,13 @@
@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/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 0c0d459..b57ad5d8 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -84,7 +84,7 @@
// The context is for the current user (system server)
private final Context mContext;
- private final Resources mResources;
+ private final ConnectivityResources mResources;
private final TelephonyManager mTelephonyManager;
// The notification manager is created from a context for User.ALL, so notifications
// will be sent to all users.
@@ -99,7 +99,7 @@
(NotificationManager) c.createContextAsUser(UserHandle.ALL, 0 /* flags */)
.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationTypeMap = new SparseIntArray();
- mResources = new ConnectivityResources(mContext).get();
+ mResources = new ConnectivityResources(mContext);
}
@VisibleForTesting
@@ -118,11 +118,11 @@
}
private String getTransportName(final int transportType) {
- String[] networkTypes = mResources.getStringArray(R.array.network_switch_type_name);
+ String[] networkTypes = mResources.get().getStringArray(R.array.network_switch_type_name);
try {
return networkTypes[transportType];
} catch (IndexOutOfBoundsException e) {
- return mResources.getString(R.string.network_switch_type_name_unknown);
+ return mResources.get().getString(R.string.network_switch_type_name_unknown);
}
}
@@ -197,10 +197,11 @@
tag, nameOf(eventId), getTransportName(transportType), name, highPriority));
}
- final Resources r = mResources;
+ final Resources r = mResources.get();
final CharSequence title;
final CharSequence details;
- Icon icon = Icon.createWithResource(r, getIcon(transportType));
+ Icon icon = Icon.createWithResource(
+ mResources.getResourcesContext(), getIcon(transportType));
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.wifi_no_internet, name);
details = r.getString(R.string.wifi_no_internet_detailed);
@@ -355,7 +356,7 @@
public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
String fromTransport = getTransportName(approximateTransportType(fromNai));
String toTransport = getTransportName(approximateTransportType(toNai));
- String text = mResources.getString(
+ String text = mResources.get().getString(
R.string.network_switch_metered_toast, fromTransport, toTransport);
Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
}
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 0000000..548db6b
--- /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 0000000..57e3dcd
--- /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 488677a..3711679 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 @@
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 0f5400d..534dbe7 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.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 @@
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 8bda532..b6ab47b 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.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 @@
}
/**
- * 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 c480594..73f3475 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_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 @@
}
// 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 @@
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 @@
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 @@
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 a4df43c..a070f27 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,7 +17,6 @@
package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
-import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -1129,17 +1128,19 @@
}
/**
- * Return netId of current running VPN network.
+ * Return Network of current running VPN network.
*
- * @return a netId if there is a running VPN network or NETID_UNSET if there is no running VPN
+ * @return a Network if there is a running VPN network or null if there is no running VPN
* network or network is null.
*/
- public synchronized int getNetId() {
+ @VisibleForTesting
+ @Nullable
+ public synchronized Network getNetwork() {
final NetworkAgent agent = mNetworkAgent;
- if (null == agent) return NETID_UNSET;
+ if (null == agent) return null;
final Network network = agent.getNetwork();
- if (null == network) return NETID_UNSET;
- return network.getNetId();
+ if (null == network) return null;
+ return network;
}
private LinkProperties makeLinkProperties() {
@@ -1249,8 +1250,9 @@
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});
@@ -1288,7 +1290,6 @@
});
mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
? Arrays.asList(mConfig.underlyingNetworks) : null);
- mNetworkInfo.setIsAvailable(true);
updateState(DetailedState.CONNECTED, "agentConnect");
}
@@ -1860,22 +1861,13 @@
/**
* 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/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index aaf9cbc..1f46061 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -119,7 +119,7 @@
public boolean onStopJob(JobParameters params) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
- + params.getStopReason());
+ + params.getLegacyStopReason());
}
final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
if (op == null) {
@@ -161,9 +161,9 @@
m.obj = op;
// Reschedule if this job was NOT explicitly canceled.
- m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
+ m.arg1 = params.getLegacyStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
// Apply backoff only if stop is called due to timeout.
- m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
+ m.arg2 = params.getLegacyStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
SyncManager.sendMessage(m);
return false;
@@ -204,7 +204,8 @@
return "job:null";
} else {
return "job:#" + params.getJobId() + ":"
- + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:"
+ + "sr=[" + params.getLegacyStopReason()
+ + "/" + params.getDebugStopReason() + "]:"
+ SyncOperation.maybeCreateFromJobExtras(params.getExtras());
}
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index ac7e01e..1786a51 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 @@
* @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/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 4f95d27..7518130 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.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 @@
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 @@
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 @@
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 @@
}
// 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 d0e7e45..58308d8 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 @@
@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 @@
})
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 1643ec1..ad2ef2a 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 @@
}
@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 bdc4e66..505e743 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 @@
* @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 @@
}
@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 @@
case Constants.MESSAGE_GIVE_FEATURES:
return handleGiveFeatures(message);
default:
- return false;
+ return Constants.NOT_HANDLED;
}
}
@@ -375,7 +378,8 @@
}
@ServiceThreadOnly
- protected boolean handleGivePhysicalAddress(@Nullable SendMessageCallback callback) {
+ @Constants.HandleMessageResult
+ protected int handleGivePhysicalAddress(@Nullable SendMessageCallback callback) {
assertRunOnServiceThread();
int physicalAddress = mService.getPhysicalAddress();
@@ -383,76 +387,83 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
}
@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 @@
// 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 @@
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 @@
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 @@
|| 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 @@
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 bf5bf8b..790c067 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 @@
@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 @@
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 @@
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 @@
} 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 @@
@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 @@
// 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 @@
// 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 @@
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 @@
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 @@
/**
* 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 @@
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 @@
}
}
});
+ 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 2995252..10f6948f 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 @@
}
@ServiceThreadOnly
- protected boolean handleUserControlPressed(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleUserControlPressed(HdmiCecMessage message) {
assertRunOnServiceThread();
wakeUpIfActiveSource();
return super.handleUserControlPressed(message);
@@ -270,10 +271,11 @@
}
@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 @@
// 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 @@
// 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 @@
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 2ed8481..979a1d4 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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 90d6433..cd66a8f 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.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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
@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 @@
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 @@
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 @@
@Override
@ServiceThreadOnly
- protected boolean handleInitiateArc(HdmiCecMessage message) {
+ @Constants.HandleMessageResult
+ protected int handleInitiateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
if (!canStartArcUpdateAction(message.getSource(), true)) {
@@ -985,13 +1000,12 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
}
// 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 @@
// Seq #54 and #55
@ServiceThreadOnly
+ @Constants.HandleMessageResult
int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
assertRunOnServiceThread();
if (!mService.isControlEnabled()) {
@@ -1362,7 +1375,7 @@
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 @@
}
@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 03a8338..031c057 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 @@
private HdmiCecMessageValidator mMessageValidator;
- private final HdmiCecPowerStatusController mPowerStatusController =
- new HdmiCecPowerStatusController(this);
+ private HdmiCecPowerStatusController mPowerStatusController;
@ServiceThreadOnly
private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
@@ -427,7 +426,7 @@
// 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 @@
mIoLooper = mIoThread.getLooper();
}
+ if (mPowerStatusController == null) {
+ mPowerStatusController = new HdmiCecPowerStatusController(this);
+ }
mPowerStatusController.setPowerStatus(getInitialPowerStatus());
mProhibitMode = false;
mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
@@ -501,6 +503,9 @@
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 @@
/**
* Returns {@link Looper} for IO operation.
- *
- * <p>Declared as package-private.
*/
@Nullable
- Looper getIoLooper() {
+ @VisibleForTesting
+ protected Looper getIoLooper() {
return mIoLooper;
}
@@ -974,10 +978,9 @@
/**
* 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 @@
/**
* Returns version of CEC.
*/
+ @VisibleForTesting
@HdmiControlManager.HdmiCecVersion
- int getCecVersion() {
+ protected int getCecVersion() {
return mCecVersion;
}
@@ -1087,23 +1091,30 @@
}
@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 @@
}
@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 @@
}
@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 0925027..0f13741 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.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 @@
}
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 @@
}
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();
- if (mRepeat >= mPattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
} else {
- Slog.w(TAG, "Pre-baked and composed effects aren't supported on input devices");
+ 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("Repeat index " + mRepeat
+ + " must be within the bounds of the pattern.length "
+ + mPattern.length);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ffb532e..27f8fd3e 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 @@
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;
@@ -175,11 +176,9 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.BackgroundThread;
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;
@@ -199,6 +198,7 @@
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;
@@ -256,7 +256,6 @@
static final int MSG_SET_ACTIVE = 3020;
static final int MSG_SET_INTERACTIVE = 3030;
static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
- static final int MSG_APPLY_IME_VISIBILITY = 3070;
static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
@@ -1567,7 +1566,7 @@
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
@@ -3201,7 +3200,7 @@
boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
mShowRequested = true;
- if (mAccessibilityRequestingNoSoftKeyboard) {
+ if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
return false;
}
@@ -4102,7 +4101,6 @@
*/
@BinderThread
@Override
- @GuardedBy("mMethodMap")
public void startProtoDump(byte[] protoDump, int source, String where,
IVoidResultCallback resultCallback) {
CallbackUtils.onResult(resultCallback, () -> {
@@ -4199,7 +4197,6 @@
});
}
- @GuardedBy("mMethodMap")
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (mMethodMap) {
final long token = proto.start(fieldId);
@@ -4603,18 +4600,6 @@
}
return true;
}
- case MSG_APPLY_IME_VISIBILITY: {
- final boolean setVisible = msg.arg1 != 0;
- final ClientState clientState = (ClientState) msg.obj;
- try {
- clientState.client.applyImeVisibility(setVisible);
- } catch (RemoteException e) {
- Slog.w(TAG, "Got RemoteException sending "
- + "applyImeVisibility(" + setVisible + ") notification to pid="
- + clientState.pid + " uid=" + clientState.uid);
- }
- return true;
- }
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
@@ -4845,6 +4830,9 @@
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
@@ -4857,6 +4845,37 @@
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) {
@@ -5240,26 +5259,71 @@
}
}
+ 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);
+ }
+
+ @BinderThread
+ private void dumpAsProtoNoCheck(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+ proto.flush();
+ }
+ };
+
+ @BinderThread
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- if (ArrayUtils.contains(args, PROTO_ARG)) {
- final ImeTracing imeTracing = ImeTracing.getInstance();
- if (imeTracing.isEnabled()) {
- imeTracing.stopTrace(null, false /* writeToFile */);
- BackgroundThread.getHandler().post(() -> {
- imeTracing.writeTracesToFiles();
- imeTracing.startTrace(null);
- });
- }
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ }
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
- dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
- proto.flush();
- return;
- }
-
+ @BinderThread
+ private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean isCritical) {
IInputMethod method;
ClientState client;
ClientState focusedWindowClient;
@@ -5323,6 +5387,11 @@
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();
@@ -5831,7 +5900,7 @@
}
/**
- * Handles {@code adb shell ime tracing start/stop}.
+ * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
* @param shellCommand {@link ShellCommand} object that is handling this command.
* @return Exit code of the command.
*/
@@ -5843,16 +5912,19 @@
switch (cmd) {
case "start":
ImeTracing.getInstance().getInstance().startTrace(pw);
- break;
+ break; // proceed to the next step to update the IME client processes.
case "stop":
ImeTracing.getInstance().stopTrace(pw);
- break;
+ break; // proceed to the next step to update the IME client processes.
+ case "save-for-bugreport":
+ ImeTracing.getInstance().saveForBugreport(pw);
+ return ShellCommandResult.SUCCESS; // no need to update the IME client processes.
default:
pw.println("Unknown command: " + cmd);
pw.println("Input method trace options:");
pw.println(" start: Start tracing");
pw.println(" stop: Stop tracing");
- return ShellCommandResult.FAILURE;
+ return ShellCommandResult.FAILURE; // no need to update the IME client processes.
}
boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
ArrayMap<IBinder, ClientState> clients;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 0e908d4..ac3c31d 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 @@
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 @@
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/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index c93c4b1..dc3596b 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
import java.util.Collections;
@@ -54,9 +55,11 @@
private final ServiceWatcher mServiceWatcher;
private GeocoderProxy(Context context) {
- mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION, null, null,
- com.android.internal.R.bool.config_enableGeocoderOverlay,
- com.android.internal.R.string.config_geocoderProviderPackageName);
+ mServiceWatcher = ServiceWatcher.create(context, "GeocoderProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+ com.android.internal.R.bool.config_enableGeocoderOverlay,
+ com.android.internal.R.string.config_geocoderProviderPackageName),
+ null);
}
private boolean register() {
@@ -72,7 +75,7 @@
*/
public void getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
@@ -97,7 +100,7 @@
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
index e1c8700..6ac6e77 100644
--- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
+++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
@@ -25,15 +25,15 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
/**
* Proxy class to bind GmsCore to the ActivityRecognitionHardware.
- *
- * @hide
*/
-public class HardwareActivityRecognitionProxy {
+public class HardwareActivityRecognitionProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "ARProxy";
private static final String SERVICE_ACTION =
@@ -66,12 +66,16 @@
mInstance = null;
}
- mServiceWatcher = new ServiceWatcher(context,
- SERVICE_ACTION,
- this::onBind,
- null,
- com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
- com.android.internal.R.string.config_activityRecognitionHardwarePackageName);
+ int useOverlayResId =
+ com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay;
+ int nonOverlayPackageResId =
+ com.android.internal.R.string.config_activityRecognitionHardwarePackageName;
+
+ mServiceWatcher = ServiceWatcher.create(context,
+ "HardwareActivityRecognitionProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION, useOverlayResId,
+ nonOverlayPackageResId),
+ this);
}
private boolean register() {
@@ -82,7 +86,8 @@
return resolves;
}
- private void onBind(IBinder binder, BoundService service) throws RemoteException {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
String descriptor = binder.getInterfaceDescriptor();
if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
@@ -99,4 +104,7 @@
Log.e(TAG, "Unknown descriptor: " + descriptor);
}
}
+
+ @Override
+ public void onUnbind() {}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2920ddb..864aa33 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -31,6 +31,7 @@
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -158,10 +159,9 @@
public Lifecycle(Context context) {
super(context);
- LocationEventLog eventLog = new LocationEventLog();
mUserInfoHelper = new LifecycleUserInfoHelper(context);
- mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog);
- mService = new LocationManagerService(context, mSystemInjector, eventLog);
+ mSystemInjector = new SystemInjector(context, mUserInfoHelper);
+ mService = new LocationManagerService(context, mSystemInjector);
}
@Override
@@ -233,7 +233,6 @@
private final Context mContext;
private final Injector mInjector;
- private final LocationEventLog mEventLog;
private final LocalService mLocalService;
private final GeofenceManager mGeofenceManager;
@@ -261,18 +260,20 @@
@GuardedBy("mLock")
private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener;
- LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) {
+ LocationManagerService(Context context, Injector injector) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
mInjector = injector;
- mEventLog = eventLog;
mLocalService = new LocalService();
LocalServices.addService(LocationManagerInternal.class, mLocalService);
mGeofenceManager = new GeofenceManager(mContext, injector);
+ mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
+ this::onLocationModeChanged);
+
// set up passive provider first since it will be required for all other location providers,
// which are loaded later once the system is ready.
- mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog);
+ mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext));
// TODO: load the gps provider here as well, which will require refactoring
@@ -313,7 +314,7 @@
}
LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
- mEventLog, providerName, mPassiveManager);
+ providerName, mPassiveManager);
addLocationProviderManager(manager, null);
return manager;
}
@@ -335,7 +336,7 @@
Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
if (enableStationaryThrottling) {
realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
- mInjector, realProvider, mEventLog);
+ mInjector, realProvider);
}
}
manager.setRealProvider(realProvider);
@@ -355,9 +356,6 @@
}
void onSystemReady() {
- mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
- this::onLocationModeChanged);
-
if (Build.IS_DEBUGGABLE) {
// on debug builds, watch for location noteOps while location is off. there are some
// scenarios (emergency location) where this is expected, but generally this should
@@ -380,12 +378,13 @@
// provider has unfortunate hard dependencies on the network provider
ProxyLocationProvider networkProvider = ProxyLocationProvider.create(
mContext,
+ NETWORK_PROVIDER,
ACTION_NETWORK_PROVIDER,
com.android.internal.R.bool.config_enableNetworkLocationOverlay,
com.android.internal.R.string.config_networkLocationProviderPackageName);
if (networkProvider != null) {
LocationProviderManager networkManager = new LocationProviderManager(mContext,
- mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager);
+ mInjector, NETWORK_PROVIDER, mPassiveManager);
addLocationProviderManager(networkManager, networkProvider);
} else {
Log.w(TAG, "no network location provider found");
@@ -399,12 +398,13 @@
ProxyLocationProvider fusedProvider = ProxyLocationProvider.create(
mContext,
+ FUSED_PROVIDER,
ACTION_FUSED_PROVIDER,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName);
if (fusedProvider != null) {
LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
- mEventLog, FUSED_PROVIDER, mPassiveManager);
+ FUSED_PROVIDER, mPassiveManager);
addLocationProviderManager(fusedManager, fusedProvider);
} else {
Log.wtf(TAG, "no fused location provider found");
@@ -419,7 +419,7 @@
mGnssManagerService.onSystemReady();
LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
- mEventLog, GPS_PROVIDER, mPassiveManager);
+ GPS_PROVIDER, mPassiveManager);
addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
}
@@ -476,7 +476,7 @@
Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
}
- mEventLog.logLocationEnabled(userId, enabled);
+ EVENT_LOG.logLocationEnabled(userId, enabled);
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -1268,7 +1268,7 @@
ipw.println("Event Log:");
ipw.increaseIndent();
- mEventLog.iterate(manager.getName(), ipw::println);
+ EVENT_LOG.iterate(manager.getName(), ipw::println);
ipw.decreaseIndent();
return;
}
@@ -1313,7 +1313,7 @@
ipw.println("Historical Aggregate Location Provider Data:");
ipw.increaseIndent();
ArrayMap<String, ArrayMap<CallerIdentity, LocationEventLog.AggregateStats>> aggregateStats =
- mEventLog.copyAggregateStats();
+ EVENT_LOG.copyAggregateStats();
for (int i = 0; i < aggregateStats.size(); i++) {
ipw.print(aggregateStats.keyAt(i));
ipw.println(":");
@@ -1344,7 +1344,7 @@
ipw.println("Event Log:");
ipw.increaseIndent();
- mEventLog.iterate(ipw::println);
+ EVENT_LOG.iterate(ipw::println);
ipw.decreaseIndent();
}
@@ -1456,7 +1456,7 @@
@GuardedBy("this")
private boolean mSystemReady;
- SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) {
+ SystemInjector(Context context, UserInfoHelper userInfoHelper) {
mContext = context;
mUserInfoHelper = userInfoHelper;
@@ -1466,7 +1466,7 @@
mAppOpsHelper);
mSettingsHelper = new SystemSettingsHelper(context);
mAppForegroundHelper = new SystemAppForegroundHelper(context);
- mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
+ mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context);
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
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 c44089b..830c3ea 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 @@
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(
@@ -691,6 +694,7 @@
sendLocationSettingUpdate();
sendWifiSettingUpdate(true /* forceUpdate */);
sendAirplaneModeSettingUpdate();
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
mTransactionManager.onHubReset();
queryNanoAppsInternal(contextHubId);
@@ -1123,6 +1127,7 @@
*/
public void onUserChanged() {
Log.d(TAG, "User changed to id: " + getCurrentUserId());
+ sendLocationSettingUpdate();
sendMicrophoneDisableSettingUpdateForCurrentUser();
}
}
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 29ce378..045e06d0 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -44,6 +44,8 @@
/** In memory event log for location events. */
public class LocationEventLog extends LocalEventLog {
+ public static final LocationEventLog EVENT_LOG = new LocationEventLog();
+
private static int getLogSize() {
if (Build.IS_DEBUGGABLE || D) {
return 500;
@@ -52,16 +54,17 @@
}
}
- private static final int EVENT_LOCATION_ENABLED = 1;
- private static final int EVENT_PROVIDER_ENABLED = 2;
- private static final int EVENT_PROVIDER_MOCKED = 3;
- private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
- private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
- private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
- private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
- private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
- private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
- private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
+ private static final int EVENT_USER_SWITCHED = 1;
+ private static final int EVENT_LOCATION_ENABLED = 2;
+ private static final int EVENT_PROVIDER_ENABLED = 3;
+ private static final int EVENT_PROVIDER_MOCKED = 4;
+ private static final int EVENT_PROVIDER_REGISTER_CLIENT = 5;
+ private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 6;
+ private static final int EVENT_PROVIDER_UPDATE_REQUEST = 7;
+ private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 8;
+ private static final int EVENT_PROVIDER_DELIVER_LOCATION = 9;
+ private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 10;
+ private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 11;
@GuardedBy("mAggregateStats")
private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
@@ -90,19 +93,24 @@
packageMap = new ArrayMap<>(2);
mAggregateStats.put(provider, packageMap);
}
- CallerIdentity stripped = identity.stripListenerId();
- AggregateStats stats = packageMap.get(stripped);
+ CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
+ AggregateStats stats = packageMap.get(aggregate);
if (stats == null) {
stats = new AggregateStats();
- packageMap.put(stripped, stats);
+ packageMap.put(aggregate, stats);
}
return stats;
}
}
+ /** Logs a user switched event. */
+ public void logUserSwitched(int userIdFrom, int userIdTo) {
+ addLogEvent(EVENT_USER_SWITCHED, userIdFrom, userIdTo);
+ }
+
/** Logs a location enabled/disabled event. */
public void logLocationEnabled(int userId, boolean enabled) {
- addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+ addLogEvent(EVENT_LOCATION_ENABLED, userId, enabled);
}
/** Logs a location provider enabled/disabled event. */
@@ -183,8 +191,10 @@
@Override
protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
switch (event) {
+ case EVENT_USER_SWITCHED:
+ return new UserSwitchedEvent(timeDelta, (Integer) args[0], (Integer) args[1]);
case EVENT_LOCATION_ENABLED:
- return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+ return new LocationEnabledEvent(timeDelta, (Integer) args[0], (Boolean) args[1]);
case EVENT_PROVIDER_ENABLED:
return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
(Boolean) args[2]);
@@ -397,6 +407,23 @@
}
}
+ private static final class UserSwitchedEvent extends LogEvent {
+
+ private final int mUserIdFrom;
+ private final int mUserIdTo;
+
+ UserSwitchedEvent(long timeDelta, int userIdFrom, int userIdTo) {
+ super(timeDelta);
+ mUserIdFrom = userIdFrom;
+ mUserIdTo = userIdTo;
+ }
+
+ @Override
+ public String getLogString() {
+ return "current user switched from u" + mUserIdFrom + " to u" + mUserIdTo;
+ }
+ }
+
private static final class LocationEnabledEvent extends LogEvent {
private final int mUserId;
@@ -410,7 +437,7 @@
@Override
public String getLogString() {
- return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+ return "location [u" + mUserId + "] " + (mEnabled ? "enabled" : "disabled");
}
}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
index c7071493..90b446ec 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
@@ -29,14 +29,17 @@
import android.os.UserHandle;
import android.util.Log;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
/**
* @hide
*/
-public final class GeofenceProxy {
+public final class GeofenceProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "GeofenceProxy";
private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider";
@@ -62,10 +65,12 @@
private GeofenceProxy(Context context, IGpsGeofenceHardware gpsGeofence) {
mGpsGeofenceHardware = Objects.requireNonNull(gpsGeofence);
- mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION,
- (binder, service) -> updateGeofenceHardware(binder), null,
- com.android.internal.R.bool.config_enableGeofenceOverlay,
- com.android.internal.R.string.config_geofenceProviderPackageName);
+ mServiceWatcher = ServiceWatcher.create(context,
+ "GeofenceProxy",
+ new CurrentUserServiceSupplier(context, SERVICE_ACTION,
+ com.android.internal.R.bool.config_enableGeofenceOverlay,
+ com.android.internal.R.string.config_geofenceProviderPackageName),
+ this);
mGeofenceHardware = null;
}
@@ -87,6 +92,14 @@
return resolves;
}
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
+ updateGeofenceHardware(binder);
+ }
+
+ @Override
+ public void onUnbind() {}
+
private class GeofenceProxyServiceConnection implements ServiceConnection {
GeofenceProxyServiceConnection() {}
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index bcdfed4..21946ca 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -81,7 +81,7 @@
mGnssNative = gnssNative;
mGnssMetrics = new GnssMetrics(mContext, IBatteryStats.Stub.asInterface(
- ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+ ServiceManager.getService(BatteryStats.SERVICE_NAME)), mGnssNative);
mGnssLocationProvider = new GnssLocationProvider(mContext, injector, mGnssNative,
mGnssMetrics);
diff --git a/services/core/java/com/android/server/location/gnss/GnssMetrics.java b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
index c7d8144..dbc903d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMetrics.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMetrics.java
@@ -35,6 +35,7 @@
import com.android.internal.location.nano.GnssLogsProto.PowerMetrics;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.location.gnss.hal.GnssNative;
import java.util.ArrayList;
import java.util.Arrays;
@@ -52,11 +53,14 @@
/** Default time between location fixes (in millisecs) */
private static final int DEFAULT_TIME_BETWEEN_FIXES_MILLISECS = 1000;
+ private static final int CONVERT_MILLI_TO_MICRO = 1000;
+ private static final int VENDOR_SPECIFIC_POWER_MODES_SIZE = 10;
/** Frequency range of GPS L5, Galileo E5a, QZSS J5 frequency band */
private static final double L5_CARRIER_FREQ_RANGE_LOW_HZ = 1164 * 1e6;
private static final double L5_CARRIER_FREQ_RANGE_HIGH_HZ = 1189 * 1e6;
+
private long mLogStartInElapsedRealtimeMs;
GnssPowerMetrics mGnssPowerMetrics;
@@ -88,8 +92,10 @@
long mL5SvStatusReportsUsedInFix;
private final StatsManager mStatsManager;
+ private final GnssNative mGnssNative;
- public GnssMetrics(Context context, IBatteryStats stats) {
+ public GnssMetrics(Context context, IBatteryStats stats, GnssNative gnssNative) {
+ mGnssNative = gnssNative;
mGnssPowerMetrics = new GnssPowerMetrics(stats);
mLocationFailureStatistics = new Statistics();
mTimeToFirstFixSecStatistics = new Statistics();
@@ -189,8 +195,8 @@
}
/**
- * Logs sv status data
- */
+ * Logs sv status data
+ */
public void logSvStatus(GnssStatus status) {
boolean isL5;
// Calculate SvStatus Information
@@ -216,8 +222,8 @@
}
/**
- * Logs CN0 when at least 4 SVs are available L5 Only
- */
+ * Logs CN0 when at least 4 SVs are available L5 Only
+ */
private void logCn0L5(GnssStatus gnssStatus) {
if (gnssStatus.getSatelliteCount() == 0) {
return;
@@ -432,7 +438,8 @@
private double mSumSquare;
private long mLongSum;
- Statistics() {}
+ Statistics() {
+ }
/** Resets statistics */
public synchronized void reset() {
@@ -498,7 +505,7 @@
GnssPowerMetrics(IBatteryStats stats) {
mBatteryStats = stats;
// Used to initialize the variable to a very small value (unachievable in practice)
- // so that
+ // so that
// the first CNO report will trigger an update to BatteryStats
mLastAverageCn0 = -100.0;
mLastSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
@@ -585,6 +592,10 @@
FrameworkStatsLog.GNSS_STATS,
null, // use default PullAtomMetadata values
ConcurrentUtils.DIRECT_EXECUTOR, pullAtomCallback);
+ mStatsManager.setPullAtomCallback(
+ FrameworkStatsLog.GNSS_POWER_STATS,
+ null, // use default PullAtomMetadata values
+ ConcurrentUtils.DIRECT_EXECUTOR, pullAtomCallback);
}
/**
@@ -593,26 +604,68 @@
*/
private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
- StatsPullAtomCallbackImpl() {}
+ StatsPullAtomCallbackImpl() {
+ }
@Override
public int onPullAtom(int atomTag, List<StatsEvent> data) {
- if (atomTag != FrameworkStatsLog.GNSS_STATS) {
+ if (atomTag == FrameworkStatsLog.GNSS_STATS) {
+ data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+ mLocationFailureReportsStatistics.getCount(),
+ mLocationFailureReportsStatistics.getLongSum(),
+ mTimeToFirstFixMilliSReportsStatistics.getCount(),
+ mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
+ mPositionAccuracyMetersReportsStatistics.getCount(),
+ mPositionAccuracyMetersReportsStatistics.getLongSum(),
+ mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
+ mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
+ mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
+ mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
+ mSvStatusReportsUsedInFix, mL5SvStatusReports,
+ mL5SvStatusReportsUsedInFix));
+ } else if (atomTag == FrameworkStatsLog.GNSS_POWER_STATS) {
+ mGnssNative.requestPowerStats();
+ GnssPowerStats gnssPowerStats = mGnssNative.getPowerStats();
+ if (gnssPowerStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ double[] otherModesEnergyMilliJoule = new double[VENDOR_SPECIFIC_POWER_MODES_SIZE];
+ double[] tempGnssPowerStatsOtherModes =
+ gnssPowerStats.getOtherModesEnergyMilliJoule();
+ if (tempGnssPowerStatsOtherModes.length < VENDOR_SPECIFIC_POWER_MODES_SIZE) {
+ System.arraycopy(tempGnssPowerStatsOtherModes, 0,
+ otherModesEnergyMilliJoule, 0,
+ tempGnssPowerStatsOtherModes.length);
+ } else {
+ System.arraycopy(tempGnssPowerStatsOtherModes, 0,
+ otherModesEnergyMilliJoule, 0,
+ VENDOR_SPECIFIC_POWER_MODES_SIZE);
+ }
+ data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+ (long) (gnssPowerStats.getElapsedRealtimeUncertaintyNanos()),
+ (long) (gnssPowerStats.getTotalEnergyMilliJoule() * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getSinglebandTrackingModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getMultibandTrackingModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getSinglebandAcquisitionModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (gnssPowerStats.getMultibandAcquisitionModeEnergyMilliJoule()
+ * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[0] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[1] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[2] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[3] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[4] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[5] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[6] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[7] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[8] * CONVERT_MILLI_TO_MICRO),
+ (long) (otherModesEnergyMilliJoule[9] * CONVERT_MILLI_TO_MICRO)));
+ } else {
throw new UnsupportedOperationException("Unknown tagId = " + atomTag);
}
- data.add(FrameworkStatsLog.buildStatsEvent(atomTag,
- mLocationFailureReportsStatistics.getCount(),
- mLocationFailureReportsStatistics.getLongSum(),
- mTimeToFirstFixMilliSReportsStatistics.getCount(),
- mTimeToFirstFixMilliSReportsStatistics.getLongSum(),
- mPositionAccuracyMetersReportsStatistics.getCount(),
- mPositionAccuracyMetersReportsStatistics.getLongSum(),
- mTopFourAverageCn0DbmHzReportsStatistics.getCount(),
- mTopFourAverageCn0DbmHzReportsStatistics.getLongSum(),
- mL5TopFourAverageCn0DbmHzReportsStatistics.getCount(),
- mL5TopFourAverageCn0DbmHzReportsStatistics.getLongSum(), mSvStatusReports,
- mSvStatusReportsUsedInFix, mL5SvStatusReports, mL5SvStatusReportsUsedInFix));
return StatsManager.PULL_SUCCESS;
}
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
index cc00d56..53407d9 100644
--- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
@@ -20,12 +20,11 @@
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import android.os.PowerManager.LocationPowerSaveMode;
import android.util.Log;
-import com.android.server.location.eventlog.LocationEventLog;
-
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -43,11 +42,9 @@
void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode);
}
- private final LocationEventLog mLocationEventLog;
private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners;
- public LocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
- mLocationEventLog = locationEventLog;
+ public LocationPowerSaveModeHelper() {
mListeners = new CopyOnWriteArrayList<>();
}
@@ -72,7 +69,7 @@
Log.d(TAG, "location power save mode is now " + locationPowerSaveModeToString(
locationPowerSaveMode));
}
- mLocationEventLog.logLocationPowerSaveMode(locationPowerSaveMode);
+ EVENT_LOG.logLocationPowerSaveMode(locationPowerSaveMode);
for (LocationPowerSaveModeChangedListener listener : mListeners) {
listener.onLocationPowerSaveModeChanged(locationPowerSaveMode);
diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
index c47a64d..a675f54 100644
--- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
@@ -25,7 +25,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import java.util.Objects;
import java.util.function.Consumer;
@@ -42,8 +41,7 @@
@LocationPowerSaveMode
private volatile int mLocationPowerSaveMode;
- public SystemLocationPowerSaveModeHelper(Context context, LocationEventLog locationEventLog) {
- super(locationEventLog);
+ public SystemLocationPowerSaveModeHelper(Context context) {
mContext = context;
}
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3f7345e..632ed6e 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -35,6 +35,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -348,31 +349,73 @@
*/
@Override
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
- int userId = ActivityManager.getCurrentUser();
+ int[] userIds;
+ try {
+ userIds = ActivityManager.getService().getRunningUserIds();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
- ipw.print("Location Enabled: ");
- ipw.println(isLocationEnabled(userId));
-
- List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(userId);
- if (!locationPackageBlacklist.isEmpty()) {
- ipw.println("Location Deny Packages:");
- ipw.increaseIndent();
- for (String packageName : locationPackageBlacklist) {
- ipw.println(packageName);
+ ipw.print("Location Setting: ");
+ ipw.increaseIndent();
+ if (userIds.length > 1) {
+ ipw.println();
+ for (int userId : userIds) {
+ ipw.print("[u");
+ ipw.print(userId);
+ ipw.print("] ");
+ ipw.println(isLocationEnabled(userId));
}
- ipw.decreaseIndent();
+ } else {
+ ipw.println(isLocationEnabled(userIds[0]));
+ }
+ ipw.decreaseIndent();
- List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
- userId);
- if (!locationPackageWhitelist.isEmpty()) {
- ipw.println("Location Allow Packages:");
+ ipw.println("Location Allow/Deny Packages:");
+ ipw.increaseIndent();
+ if (userIds.length > 1) {
+ for (int userId : userIds) {
+ List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+ userId);
+ if (locationPackageBlacklist.isEmpty()) {
+ continue;
+ }
+
+ ipw.print("user ");
+ ipw.print(userId);
+ ipw.println(":");
ipw.increaseIndent();
- for (String packageName : locationPackageWhitelist) {
+
+ for (String packageName : locationPackageBlacklist) {
+ ipw.print("[deny] ");
ipw.println(packageName);
}
+
+ List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+ userId);
+ for (String packageName : locationPackageWhitelist) {
+ ipw.print("[allow] ");
+ ipw.println(packageName);
+ }
+
ipw.decreaseIndent();
}
+ } else {
+ List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
+ userIds[0]);
+ for (String packageName : locationPackageBlacklist) {
+ ipw.print("[deny] ");
+ ipw.println(packageName);
+ }
+
+ List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
+ userIds[0]);
+ for (String packageName : locationPackageWhitelist) {
+ ipw.print("[allow] ");
+ ipw.println(packageName);
+ }
}
+ ipw.decreaseIndent();
Set<String> backgroundThrottlePackageWhitelist =
mBackgroundThrottlePackageWhitelist.getValue();
diff --git a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
index d4a8fbd..ed1e654 100644
--- a/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemUserInfoHelper.java
@@ -155,6 +155,11 @@
*/
@Override
public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ int[] runningUserIds = getRunningUserIds();
+ if (runningUserIds.length > 1) {
+ pw.println("running users: u" + Arrays.toString(runningUserIds));
+ }
+
ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
if (activityManagerInternal == null) {
return;
diff --git a/services/core/java/com/android/server/location/injector/UserInfoHelper.java b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
index 0fcc1ec..c835370 100644
--- a/services/core/java/com/android/server/location/injector/UserInfoHelper.java
+++ b/services/core/java/com/android/server/location/injector/UserInfoHelper.java
@@ -18,6 +18,7 @@
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static com.android.server.location.injector.UserInfoHelper.UserListener.CURRENT_USER_CHANGED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STARTED;
import static com.android.server.location.injector.UserInfoHelper.UserListener.USER_STOPPED;
@@ -105,6 +106,7 @@
Log.d(TAG, "current user changed from u" + Arrays.toString(fromUserIds) + " to u"
+ Arrays.toString(toUserIds));
}
+ EVENT_LOG.logUserSwitched(fromUserId, toUserId);
for (UserListener listener : mListeners) {
for (int userId : fromUserIds) {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index dc8b1d0..102263b 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -37,6 +37,7 @@
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -94,7 +95,6 @@
import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.fudger.LocationFudger;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
@@ -333,7 +333,7 @@
+ getRequest());
}
- mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+ EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -345,7 +345,7 @@
onProviderListenerRegister();
if (mForeground) {
- mEventLog.logProviderClientForeground(mName, getIdentity());
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
}
}
@@ -358,7 +358,7 @@
onProviderListenerUnregister();
- mEventLog.logProviderClientUnregistered(mName, getIdentity());
+ EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
@@ -383,7 +383,7 @@
Preconditions.checkState(Thread.holdsLock(mLock));
}
- mEventLog.logProviderClientActive(mName, getIdentity());
+ EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
@@ -406,7 +406,7 @@
onProviderListenerInactive();
- mEventLog.logProviderClientInactive(mName, getIdentity());
+ EVENT_LOG.logProviderClientInactive(mName, getIdentity());
}
/**
@@ -543,9 +543,9 @@
mForeground = foreground;
if (mForeground) {
- mEventLog.logProviderClientForeground(mName, getIdentity());
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
} else {
- mEventLog.logProviderClientBackground(mName, getIdentity());
+ EVENT_LOG.logProviderClientBackground(mName, getIdentity());
}
// note that onProviderLocationRequestChanged() is always called
@@ -654,7 +654,7 @@
protected abstract class LocationRegistration extends Registration implements
OnAlarmListener, ProviderEnabledListener {
- private final PowerManager.WakeLock mWakeLock;
+ final PowerManager.WakeLock mWakeLock;
private volatile ProviderTransport mProviderTransport;
private int mNumLocationsDelivered = 0;
@@ -879,7 +879,7 @@
listener.deliverOnLocationChanged(deliverLocationResult,
mUseWakeLock ? mWakeLock::release : null);
- mEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
+ EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
getIdentity());
}
@@ -1178,7 +1178,7 @@
// we currently don't hold a wakelock for getCurrentLocation deliveries
listener.deliverOnLocationChanged(deliverLocationResult, null);
- mEventLog.logProviderDeliveredLocations(mName,
+ EVENT_LOG.logProviderDeliveredLocations(mName,
locationResult != null ? locationResult.size() : 0, getIdentity());
}
@@ -1247,7 +1247,6 @@
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
- protected final LocationEventLog mEventLog;
protected final LocationManagerInternal mLocationManagerInternal;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserHelper;
@@ -1300,7 +1299,7 @@
@GuardedBy("mLock")
private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener;
- public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog,
+ public LocationProviderManager(Context context, Injector injector,
String name, @Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
mName = Objects.requireNonNull(name);
@@ -1312,7 +1311,6 @@
mEnabledListeners = new ArrayList<>();
mProviderRequestListeners = new CopyOnWriteArrayList<>();
- mEventLog = eventLog;
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mSettingsHelper = injector.getSettingsHelper();
@@ -1477,7 +1475,7 @@
synchronized (mLock) {
Preconditions.checkState(mState != STATE_STOPPED);
- mEventLog.logProviderMocked(mName, provider != null);
+ EVENT_LOG.logProviderMocked(mName, provider != null);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1966,8 +1964,8 @@
}
@GuardedBy("mLock")
- private void setProviderRequest(ProviderRequest request) {
- mEventLog.logProviderUpdateRequest(mName, request);
+ void setProviderRequest(ProviderRequest request) {
+ EVENT_LOG.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
FgThread.getHandler().post(() -> {
@@ -2324,7 +2322,7 @@
}
// don't log location received for passive provider because it's spammy
- mEventLog.logProviderReceivedLocations(mName, filtered.size());
+ EVENT_LOG.logProviderReceivedLocations(mName, filtered.size());
} else {
// passive provider should get already filtered results as input
filtered = locationResult;
@@ -2424,7 +2422,7 @@
if (D) {
Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
}
- mEventLog.logProviderEnabled(mName, userId, enabled);
+ EVENT_LOG.logProviderEnabled(mName, userId, enabled);
}
// clear last locations if we become disabled
@@ -2464,7 +2462,7 @@
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- private @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
+ @Nullable Location getPermittedLocation(@Nullable Location fineLocation,
@PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2477,7 +2475,7 @@
}
}
- private @Nullable LocationResult getPermittedLocationResult(
+ @Nullable LocationResult getPermittedLocationResult(
@Nullable LocationResult fineLocationResult, @PermissionLevel int permissionLevel) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2538,6 +2536,8 @@
private @Nullable Location mFineBypassLocation;
private @Nullable Location mCoarseBypassLocation;
+ LastLocation() {}
+
public void clearMock() {
if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
mFineLocation = null;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index 027f4e9..b35af4f 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -24,7 +24,6 @@
import android.os.Binder;
import com.android.internal.util.Preconditions;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.Injector;
import java.util.Collection;
@@ -34,9 +33,8 @@
*/
public class PassiveLocationProviderManager extends LocationProviderManager {
- public PassiveLocationProviderManager(Context context, Injector injector,
- LocationEventLog eventLog) {
- super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null);
+ public PassiveLocationProviderManager(Context context, Injector injector) {
+ super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 6f4aa64..ab7e526 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import android.annotation.Nullable;
import android.location.Location;
@@ -33,7 +34,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.DeviceIdleHelper;
import com.android.server.location.injector.DeviceStationaryHelper;
import com.android.server.location.injector.Injector;
@@ -54,12 +54,11 @@
private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
- private final Object mLock = new Object();
+ final Object mLock = new Object();
private final String mName;
private final DeviceIdleHelper mDeviceIdleHelper;
private final DeviceStationaryHelper mDeviceStationaryHelper;
- private final LocationEventLog mEventLog;
@GuardedBy("mLock")
private boolean mDeviceIdle = false;
@@ -72,21 +71,19 @@
@GuardedBy("mLock")
private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
@GuardedBy("mLock")
- private long mThrottlingIntervalMs = INTERVAL_DISABLED;
+ long mThrottlingIntervalMs = INTERVAL_DISABLED;
@GuardedBy("mLock")
- private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
-
+ @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
@GuardedBy("mLock")
- private @Nullable Location mLastLocation;
+ @Nullable Location mLastLocation;
public StationaryThrottlingLocationProvider(String name, Injector injector,
- AbstractLocationProvider delegate, LocationEventLog eventLog) {
+ AbstractLocationProvider delegate) {
super(DIRECT_EXECUTOR, delegate);
mName = name;
mDeviceIdleHelper = injector.getDeviceIdleHelper();
mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
- mEventLog = eventLog;
// must be last statement in the constructor because reference is escaping
initializeDelegate();
@@ -209,7 +206,7 @@
if (D) {
Log.d(TAG, mName + " provider stationary throttled");
}
- mEventLog.logProviderStationaryThrottled(mName, true);
+ EVENT_LOG.logProviderStationaryThrottled(mName, true);
}
if (mDeliverLastLocationCallback != null) {
@@ -227,7 +224,7 @@
}
} else {
if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
- mEventLog.logProviderStationaryThrottled(mName, false);
+ EVENT_LOG.logProviderStationaryThrottled(mName, false);
if (D) {
Log.d(TAG, mName + " provider stationary unthrottled");
}
@@ -257,6 +254,9 @@
}
private class DeliverLastLocationRunnable implements Runnable {
+
+ DeliverLastLocationRunnable() {}
+
@Override
public void run() {
Location location;
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index c86e49b..5df7870 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -19,7 +19,6 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.Context;
import android.location.Location;
import android.location.LocationResult;
@@ -28,42 +27,46 @@
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
import com.android.server.location.provider.AbstractLocationProvider;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
/**
* Proxy for ILocationProvider implementations.
*/
-public class ProxyLocationProvider extends AbstractLocationProvider {
+public class ProxyLocationProvider extends AbstractLocationProvider implements
+ ServiceListener<BoundServiceInfo> {
- private static final String KEY_EXTRA_ATTRIBUTION_TAGS = "android:location_allow_listed_tags";
- private static final String EXTRA_ATTRIBUTION_TAGS_SEPARATOR = ";";
+ private static final String EXTRA_LOCATION_TAGS = "android:location_allow_listed_tags";
+ private static final String LOCATION_TAGS_SEPARATOR = ";";
+
+ private static final long RESET_DELAY_MS = 1000;
/**
* Creates and registers this proxy. If no suitable service is available for the proxy, returns
* null.
*/
@Nullable
- public static ProxyLocationProvider create(Context context, String action,
+ public static ProxyLocationProvider create(Context context, String provider, String action,
int enableOverlayResId, int nonOverlayPackageResId) {
- ProxyLocationProvider proxy = new ProxyLocationProvider(context, action, enableOverlayResId,
- nonOverlayPackageResId);
+ ProxyLocationProvider proxy = new ProxyLocationProvider(context, provider, action,
+ enableOverlayResId, nonOverlayPackageResId);
if (proxy.checkServiceResolves()) {
return proxy;
} else {
@@ -80,21 +83,24 @@
final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0);
@GuardedBy("mLock")
- Proxy mProxy;
+ @Nullable Runnable mResetter;
@GuardedBy("mLock")
- @Nullable ComponentName mService;
+ @Nullable Proxy mProxy;
+ @GuardedBy("mLock")
+ @Nullable BoundServiceInfo mBoundServiceInfo;
private volatile ProviderRequest mRequest;
- private ProxyLocationProvider(Context context, String action, int enableOverlayResId,
- int nonOverlayPackageResId) {
+ private ProxyLocationProvider(Context context, String provider, String action,
+ int enableOverlayResId, int nonOverlayPackageResId) {
// safe to use direct executor since our locks are not acquired in a code path invoked by
// our owning provider
super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
mContext = context;
- mServiceWatcher = new ServiceWatcher(context, action, this::onBind,
- this::onUnbind, enableOverlayResId, nonOverlayPackageResId);
+ mServiceWatcher = ServiceWatcher.create(context, provider,
+ new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+ nonOverlayPackageResId), this);
mProxy = null;
mRequest = ProviderRequest.EMPTY_REQUEST;
@@ -104,21 +110,13 @@
return mServiceWatcher.checkServiceResolves();
}
- private void onBind(IBinder binder, BoundService boundService) throws RemoteException {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
synchronized (mLock) {
mProxy = new Proxy();
- mService = boundService.component;
-
- // update extra attribution tag info from manifest
- if (boundService.metadata != null) {
- String tagsList = boundService.metadata.getString(KEY_EXTRA_ATTRIBUTION_TAGS);
- if (tagsList != null) {
- setExtraAttributionTags(
- new ArraySet<>(tagsList.split(EXTRA_ATTRIBUTION_TAGS_SEPARATOR)));
- }
- }
+ mBoundServiceInfo = boundServiceInfo;
provider.setLocationProviderManager(mProxy);
@@ -129,12 +127,30 @@
}
}
- private void onUnbind() {
+ @Override
+ public void onUnbind() {
Runnable[] flushListeners;
synchronized (mLock) {
mProxy = null;
- mService = null;
- setState(prevState -> State.EMPTY_STATE);
+ mBoundServiceInfo = null;
+
+ // we need to clear the state - but most disconnections are very temporary. we give a
+ // grace period where we don't clear the state immediately so that transient
+ // interruptions are not necessarily visible to downstream clients
+ if (mResetter == null) {
+ mResetter = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mResetter == this) {
+ setState(prevState -> State.EMPTY_STATE);
+ }
+ }
+ }
+ };
+ FgThread.getHandler().postDelayed(mResetter, RESET_DELAY_MS);
+ }
+
flushListeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
@@ -166,7 +182,7 @@
@Override
protected void onFlush(Runnable callback) {
- mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+ mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
@@ -206,20 +222,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mServiceWatcher.dump(fd, pw, args);
- }
-
- private static String guessPackageName(Context context, int uid, String packageName) {
- String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
- if (packageNames == null || packageNames.length == 0) {
- // illegal state exception will propagate back through binders
- throw new IllegalStateException(
- "location provider from uid " + uid + " has no package information");
- } else if (ArrayUtils.contains(packageNames, packageName)) {
- return packageName;
- } else {
- return packageNames[0];
- }
+ mServiceWatcher.dump(pw);
}
private class Proxy extends ILocationProviderManager.Stub {
@@ -229,26 +232,37 @@
// executed on binder thread
@Override
public void onInitialize(boolean allowed, ProviderProperties properties,
- @Nullable String packageName, @Nullable String attributionTag) {
+ @Nullable String attributionTag) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
- CallerIdentity identity;
- if (packageName == null) {
- packageName = guessPackageName(mContext, Binder.getCallingUid(),
- Objects.requireNonNull(mService).getPackageName());
- // unsafe is ok since the package is coming direct from the package manager here
- identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
- } else {
- identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+ if (mResetter != null) {
+ FgThread.getHandler().removeCallbacks(mResetter);
+ mResetter = null;
}
- setState(prevState -> prevState
+ // set extra attribution tags from manifest if necessary
+ String[] attributionTags = new String[0];
+ if (mBoundServiceInfo.getMetadata() != null) {
+ String tagsStr = mBoundServiceInfo.getMetadata().getString(EXTRA_LOCATION_TAGS);
+ if (!TextUtils.isEmpty(tagsStr)) {
+ attributionTags = tagsStr.split(LOCATION_TAGS_SEPARATOR);
+ }
+ }
+ ArraySet<String> extraAttributionTags = new ArraySet<>(attributionTags);
+
+ // unsafe is ok since we trust the package name already
+ CallerIdentity identity = CallerIdentity.fromBinderUnsafe(
+ mBoundServiceInfo.getComponentName().getPackageName(),
+ attributionTag);
+
+ setState(prevState -> State.EMPTY_STATE
.withAllowed(allowed)
.withProperties(properties)
- .withIdentity(identity));
+ .withIdentity(identity)
+ .withExtraAttributionTags(extraAttributionTags));
}
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 6e99cba..76ecc1a 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.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 @@
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 @@
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 @@
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 @@
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 @@
if (rebootEscrowUsers.isEmpty()) {
Slog.i(TAG, "No reboot escrow data found for users,"
+ " skipping loading escrow data");
+ clearMetricsStorage();
return;
}
@@ -284,6 +340,7 @@
}
Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+ mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED;
onGetRebootEscrowKeyFailed(users, attemptNumber);
}
@@ -307,6 +364,17 @@
}
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 @@
// 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 @@
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 @@
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 @@
return;
}
- mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
+ clearMetricsStorage();
rebootEscrowProvider.clearRebootEscrowKey();
List<UserInfo> users = mUserManager.getUsers();
@@ -486,6 +608,9 @@
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 @@
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 4b00772..e8f6f4a 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 @@
}
@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 af6faad..e106d81 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 @@
* @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 697bf08..2866987 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 @@
}
@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 46ece74..a30d993 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -563,8 +563,8 @@
final PowerExemptionManager powerExemptionManager = userContext.getSystemService(
PowerExemptionManager.class);
powerExemptionManager.addToTemporaryAllowList(targetPackage,
- FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS,
- PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason);
+ PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason,
+ FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1225,6 +1225,70 @@
}
@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/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index 23195bb..1e0142c 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -75,6 +75,8 @@
.writeLong(metrics.getNetworkBytesRead())
.writeLong(metrics.getLocalBytesRead())
.writeLong(metrics.getNetworkTransferDurationMillis())
+ // Raw bytes type not allowed in atoms
+ .writeString(Base64.encodeToString(metrics.getDrmSessionId(), Base64.DEFAULT))
.usePooledBuffer()
.build();
StatsLog.write(statsEvent);
@@ -154,9 +156,10 @@
.writeString(event.getLanguage())
.writeString(event.getLanguageRegion())
.writeInt(event.getChannelCount())
- .writeInt(event.getSampleRate())
+ .writeInt(event.getAudioSampleRate())
.writeInt(event.getWidth())
.writeInt(event.getHeight())
+ .writeFloat(event.getVideoFrameRate())
.usePooledBuffer()
.build();
StatsLog.write(statsEvent);
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index cc3a002..df1eb6d 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -22,7 +22,6 @@
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
import android.net.ProxyInfo;
-import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
import android.util.ArrayMap;
@@ -42,6 +41,8 @@
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
public class IpConfigStore {
private static final String TAG = "IpConfigStore";
@@ -83,25 +84,25 @@
boolean written = false;
try {
- switch (config.ipAssignment) {
+ switch (config.getIpAssignment()) {
case STATIC:
out.writeUTF(IP_ASSIGNMENT_KEY);
- out.writeUTF(config.ipAssignment.toString());
- StaticIpConfiguration staticIpConfiguration = config.staticIpConfiguration;
+ out.writeUTF(config.getIpAssignment().toString());
+ StaticIpConfiguration staticIpConfiguration = config.getStaticIpConfiguration();
if (staticIpConfiguration != null) {
- if (staticIpConfiguration.ipAddress != null) {
- LinkAddress ipAddress = staticIpConfiguration.ipAddress;
+ if (staticIpConfiguration.getIpAddress() != null) {
+ LinkAddress ipAddress = staticIpConfiguration.getIpAddress();
out.writeUTF(LINK_ADDRESS_KEY);
out.writeUTF(ipAddress.getAddress().getHostAddress());
out.writeInt(ipAddress.getPrefixLength());
}
- if (staticIpConfiguration.gateway != null) {
+ if (staticIpConfiguration.getGateway() != null) {
out.writeUTF(GATEWAY_KEY);
out.writeInt(0); // Default route.
out.writeInt(1); // Have a gateway.
- out.writeUTF(staticIpConfiguration.gateway.getHostAddress());
+ out.writeUTF(staticIpConfiguration.getGateway().getHostAddress());
}
- for (InetAddress inetAddr : staticIpConfiguration.dnsServers) {
+ for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
out.writeUTF(DNS_KEY);
out.writeUTF(inetAddr.getHostAddress());
}
@@ -110,7 +111,7 @@
break;
case DHCP:
out.writeUTF(IP_ASSIGNMENT_KEY);
- out.writeUTF(config.ipAssignment.toString());
+ out.writeUTF(config.getIpAssignment().toString());
written = true;
break;
case UNASSIGNED:
@@ -121,13 +122,13 @@
break;
}
- switch (config.proxySettings) {
+ switch (config.getProxySettings()) {
case STATIC:
- ProxyInfo proxyProperties = config.httpProxy;
+ ProxyInfo proxyProperties = config.getHttpProxy();
String exclusionList = ProxyUtils.exclusionListAsString(
proxyProperties.getExclusionList());
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
out.writeUTF(PROXY_HOST_KEY);
out.writeUTF(proxyProperties.getHost());
out.writeUTF(PROXY_PORT_KEY);
@@ -139,16 +140,16 @@
written = true;
break;
case PAC:
- ProxyInfo proxyPacProperties = config.httpProxy;
+ ProxyInfo proxyPacProperties = config.getHttpProxy();
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
out.writeUTF(PROXY_PAC_FILE);
out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
written = true;
break;
case NONE:
out.writeUTF(PROXY_SETTINGS_KEY);
- out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(config.getProxySettings().toString());
written = true;
break;
case UNASSIGNED:
@@ -267,11 +268,14 @@
IpAssignment ipAssignment = IpAssignment.DHCP;
ProxySettings proxySettings = ProxySettings.NONE;
StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+ LinkAddress linkAddress = null;
+ InetAddress gatewayAddress = null;
String proxyHost = null;
String pacFileUrl = null;
int proxyPort = -1;
String exclusionList = null;
String key;
+ final List<InetAddress> dnsServers = new ArrayList<>();
do {
key = in.readUTF();
@@ -286,15 +290,15 @@
} else if (key.equals(IP_ASSIGNMENT_KEY)) {
ipAssignment = IpAssignment.valueOf(in.readUTF());
} else if (key.equals(LINK_ADDRESS_KEY)) {
- LinkAddress linkAddr =
+ LinkAddress parsedLinkAddress =
new LinkAddress(
InetAddresses.parseNumericAddress(in.readUTF()),
in.readInt());
- if (linkAddr.getAddress() instanceof Inet4Address &&
- staticIpConfiguration.ipAddress == null) {
- staticIpConfiguration.ipAddress = linkAddr;
+ if (parsedLinkAddress.getAddress() instanceof Inet4Address
+ && linkAddress == null) {
+ linkAddress = parsedLinkAddress;
} else {
- loge("Non-IPv4 or duplicate address: " + linkAddr);
+ loge("Non-IPv4 or duplicate address: " + parsedLinkAddress);
}
} else if (key.equals(GATEWAY_KEY)) {
LinkAddress dest = null;
@@ -302,8 +306,8 @@
if (version == 1) {
// only supported default gateways - leave the dest/prefix empty
gateway = InetAddresses.parseNumericAddress(in.readUTF());
- if (staticIpConfiguration.gateway == null) {
- staticIpConfiguration.gateway = gateway;
+ if (gatewayAddress == null) {
+ gatewayAddress = gateway;
} else {
loge("Duplicate gateway: " + gateway.getHostAddress());
}
@@ -317,17 +321,18 @@
if (in.readInt() == 1) {
gateway = InetAddresses.parseNumericAddress(in.readUTF());
}
- RouteInfo route = new RouteInfo(dest, gateway);
- if (route.isIPv4Default() &&
- staticIpConfiguration.gateway == null) {
- staticIpConfiguration.gateway = gateway;
+ // If the destination is a default IPv4 route, use the gateway
+ // address unless already set.
+ if (dest.getAddress() instanceof Inet4Address
+ && dest.getPrefixLength() == 0 && gatewayAddress == null) {
+ gatewayAddress = gateway;
} else {
- loge("Non-IPv4 default or duplicate route: " + route);
+ loge("Non-IPv4 default or duplicate route: "
+ + dest.getAddress());
}
}
} else if (key.equals(DNS_KEY)) {
- staticIpConfiguration.dnsServers.add(
- InetAddresses.parseNumericAddress(in.readUTF()));
+ dnsServers.add(InetAddresses.parseNumericAddress(in.readUTF()));
} else if (key.equals(PROXY_SETTINGS_KEY)) {
proxySettings = ProxySettings.valueOf(in.readUTF());
} else if (key.equals(PROXY_HOST_KEY)) {
@@ -348,25 +353,31 @@
}
} while (true);
+ staticIpConfiguration = new StaticIpConfiguration.Builder()
+ .setIpAddress(linkAddress)
+ .setGateway(gatewayAddress)
+ .setDnsServers(dnsServers)
+ .build();
+
if (uniqueToken != null) {
IpConfiguration config = new IpConfiguration();
networks.put(uniqueToken, config);
switch (ipAssignment) {
case STATIC:
- config.staticIpConfiguration = staticIpConfiguration;
- config.ipAssignment = ipAssignment;
+ config.setStaticIpConfiguration(staticIpConfiguration);
+ config.setIpAssignment(ipAssignment);
break;
case DHCP:
- config.ipAssignment = ipAssignment;
+ config.setIpAssignment(ipAssignment);
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED IP on file, use DHCP");
- config.ipAssignment = IpAssignment.DHCP;
+ config.setIpAssignment(IpAssignment.DHCP);
break;
default:
loge("Ignore invalid ip assignment while reading.");
- config.ipAssignment = IpAssignment.UNASSIGNED;
+ config.setIpAssignment(IpAssignment.UNASSIGNED);
break;
}
@@ -374,25 +385,25 @@
case STATIC:
ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
ProxyUtils.exclusionStringAsList(exclusionList));
- config.proxySettings = proxySettings;
- config.httpProxy = proxyInfo;
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyInfo);
break;
case PAC:
ProxyInfo proxyPacProperties =
ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
- config.proxySettings = proxySettings;
- config.httpProxy = proxyPacProperties;
+ config.setProxySettings(proxySettings);
+ config.setHttpProxy(proxyPacProperties);
break;
case NONE:
- config.proxySettings = proxySettings;
+ config.setProxySettings(proxySettings);
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED proxy on file, use NONE");
- config.proxySettings = ProxySettings.NONE;
+ config.setProxySettings(ProxySettings.NONE);
break;
default:
loge("Ignore invalid proxy settings while reading");
- config.proxySettings = ProxySettings.UNASSIGNED;
+ config.setProxySettings(ProxySettings.UNASSIGNED);
break;
}
} else {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 39ed7e8..2e4d41c 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 @@
/**
* 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 350563f..3859285 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_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;
@@ -58,7 +66,9 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_MASK;
+import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_SYSTEM;
import static android.net.NetworkPolicyManager.ALLOWED_METERED_REASON_USER_EXEMPTED;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
@@ -66,15 +76,7 @@
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;
@@ -178,7 +180,6 @@
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
-import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkIdentity;
@@ -421,15 +422,15 @@
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.
@@ -1919,16 +1920,7 @@
* Collect all ifaces from a {@link NetworkStateSnapshot} into the given set.
*/
private static void collectIfaces(ArraySet<String> ifaces, NetworkStateSnapshot snapshot) {
- final String baseIface = snapshot.linkProperties.getInterfaceName();
- if (baseIface != null) {
- ifaces.add(baseIface);
- }
- for (LinkProperties stackedLink : snapshot.linkProperties.getStackedLinks()) {
- final String stackedIface = stackedLink.getInterfaceName();
- if (stackedIface != null) {
- ifaces.add(stackedIface);
- }
- }
+ ifaces.addAll(snapshot.linkProperties.getAllInterfaceNames());
}
/**
@@ -2042,39 +2034,45 @@
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);
+ 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 (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;
+ // 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);
}
}
@@ -2097,7 +2095,7 @@
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);
}
}
@@ -2109,7 +2107,7 @@
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;
@@ -4634,8 +4632,8 @@
newBlockedReasons |= (mRestrictBackground ? BLOCKED_METERED_REASON_DATA_SAVER : 0);
newBlockedReasons |= (isDenied ? BLOCKED_METERED_REASON_USER_RESTRICTED : 0);
- newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
- newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
+ newAllowedReasons |= (isSystem(uid) ? ALLOWED_METERED_REASON_SYSTEM : 0);
+ newAllowedReasons |= (isForeground ? ALLOWED_METERED_REASON_FOREGROUND : 0);
newAllowedReasons |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0);
if (LOGV) {
@@ -4709,18 +4707,18 @@
// Dispatch changed rule to existing listeners.
mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
+ }
- final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
- uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
- & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
- uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
- & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
- uidBlockedState.updateEffectiveBlockedReasons();
- if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
- mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
- uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
- .sendToTarget();
- }
+ final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+ uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+ & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+ uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+ & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+ uidBlockedState.updateEffectiveBlockedReasons();
+ if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
+ mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
+ uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
+ .sendToTarget();
}
}
@@ -4978,7 +4976,7 @@
mListeners.finishBroadcast();
return true;
}
- case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+ case MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED: {
mNetworkStats.forceUpdate();
synchronized (mNetworkPoliciesSecondLock) {
@@ -5049,19 +5047,20 @@
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: {
@@ -5209,15 +5208,32 @@
}
}
- 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 setInterfaceQuota(String iface, long quotaBytes) {
+ private void setInterfaceQuotasAsync(@NonNull String iface,
+ long warningBytes, long limitBytes) {
+ mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS,
+ new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget();
+ }
+
+ 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) {
@@ -5225,11 +5241,11 @@
}
}
- 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) {
@@ -5734,9 +5750,9 @@
}
@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();
}
}
@@ -5868,12 +5884,17 @@
return;
}
if ((allowedReasons & ALLOWED_REASON_SYSTEM) != 0) {
- effectiveBlockedReasons = BLOCKED_REASON_NONE;
+ effectiveBlockedReasons = (blockedReasons & ALLOWED_METERED_REASON_MASK);
+ }
+ if ((allowedReasons & ALLOWED_METERED_REASON_SYSTEM) != 0) {
+ effectiveBlockedReasons = (blockedReasons & ~ALLOWED_METERED_REASON_MASK);
}
if ((allowedReasons & ALLOWED_REASON_FOREGROUND) != 0) {
effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ }
+ if ((allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED;
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 0cb0bc2c..0e9a9da 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 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 7b376847..de5aae0 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -92,7 +92,6 @@
import android.net.INetworkManagementEventObserver;
import android.net.INetworkStatsService;
import android.net.INetworkStatsSession;
-import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkIdentity;
@@ -131,6 +130,7 @@
import android.service.NetworkStatsServiceDumpProto;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionPlan;
+import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1358,17 +1358,18 @@
// (or non eBPF offloaded) TX they would appear on both, however egress interface
// accounting is explicitly bypassed for traffic from the clat uid.
//
- final List<LinkProperties> stackedLinks = snapshot.linkProperties.getStackedLinks();
- for (LinkProperties stackedLink : stackedLinks) {
- final String stackedIface = stackedLink.getInterfaceName();
- if (stackedIface != null) {
- findOrCreateNetworkIdentitySet(mActiveIfaces, stackedIface).add(ident);
- findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident);
+ // TODO: This code might be combined to above code.
+ for (String iface : snapshot.linkProperties.getAllInterfaceNames()) {
+ // baseIface has been handled, so ignore it.
+ if (TextUtils.equals(baseIface, iface)) continue;
+ if (iface != null) {
+ findOrCreateNetworkIdentitySet(mActiveIfaces, iface).add(ident);
+ findOrCreateNetworkIdentitySet(mActiveUidIfaces, iface).add(ident);
if (isMobile) {
- mobileIfaces.add(stackedIface);
+ mobileIfaces.add(iface);
}
- mStatsFactory.noteStackedIface(stackedIface, baseIface);
+ mStatsFactory.noteStackedIface(iface, baseIface);
}
}
}
@@ -1673,9 +1674,14 @@
}
@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));
}
}
@@ -2070,10 +2076,10 @@
}
@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/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 78c1a95..3238f1f 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -25,6 +25,7 @@
import android.net.Uri;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -41,8 +42,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.notification.NotificationManagerService.DumpFilter;
-import org.xmlpull.v1.XmlSerializer;
-
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -78,8 +77,8 @@
public void addSystemProvider(SystemConditionProviderService service) {
mSystemConditionProviders.add(service);
service.attachBase(mContext);
- registerSystemService(
- service.asInterface(), service.getComponent(), UserHandle.USER_SYSTEM);
+ registerSystemService(service.asInterface(), service.getComponent(), UserHandle.USER_SYSTEM,
+ Process.SYSTEM_UID);
}
public Iterable<SystemConditionProviderService> getSystemProviders() {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index bbdcac2..3007515 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -317,9 +317,9 @@
private ManagedServiceInfo newServiceInfo(IInterface service,
ComponentName component, int userId, boolean isSystem, ServiceConnection connection,
- int targetSdkVersion) {
+ int targetSdkVersion, int uid) {
return new ManagedServiceInfo(service, component, userId, isSystem, connection,
- targetSdkVersion);
+ targetSdkVersion, uid);
}
public void onBootPhaseAppsCanStart() {}
@@ -974,10 +974,11 @@
unregisterServiceImpl(service, userid);
}
- public void registerSystemService(IInterface service, ComponentName component, int userid) {
+ public void registerSystemService(IInterface service, ComponentName component, int userid,
+ int uid) {
checkNotNull(service);
ManagedServiceInfo info = registerServiceImpl(
- service, component, userid, Build.VERSION_CODES.CUR_DEVELOPMENT);
+ service, component, userid, Build.VERSION_CODES.CUR_DEVELOPMENT, uid);
if (info != null) {
onServiceAdded(info);
}
@@ -1441,6 +1442,7 @@
}
final int targetSdkVersion =
appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
+ final int uid = appInfo != null ? appInfo.uid : -1;
try {
Slog.v(TAG, "binding: " + intent);
@@ -1457,7 +1459,7 @@
try {
mService = asInterface(binder);
info = newServiceInfo(mService, name,
- userid, isSystem, this, targetSdkVersion);
+ userid, isSystem, this, targetSdkVersion, uid);
binder.linkToDeath(info, 0);
added = mServices.add(info);
} catch (RemoteException e) {
@@ -1576,9 +1578,9 @@
}
private ManagedServiceInfo registerServiceImpl(final IInterface service,
- final ComponentName component, final int userid, int targetSdk) {
+ final ComponentName component, final int userid, int targetSdk, int uid) {
ManagedServiceInfo info = newServiceInfo(service, component, userid,
- true /*isSystem*/, null /*connection*/, targetSdk);
+ true /*isSystem*/, null /*connection*/, targetSdk, uid);
return registerServiceImpl(info);
}
@@ -1624,15 +1626,18 @@
public ServiceConnection connection;
public int targetSdkVersion;
public Pair<ComponentName, Integer> mKey;
+ public int uid;
public ManagedServiceInfo(IInterface service, ComponentName component,
- int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) {
+ int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion,
+ int uid) {
this.service = service;
this.component = component;
this.userid = userid;
this.isSystem = isSystem;
this.connection = connection;
this.targetSdkVersion = targetSdkVersion;
+ this.uid = uid;
mKey = Pair.create(component, userid);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index eb4f9d3..08dbd77 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -60,7 +60,6 @@
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.PERMISSION_GRANTED;
-import static android.media.AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
@@ -85,6 +84,8 @@
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_REMOVED;
+import static android.service.notification.NotificationListenerService.REASON_CLEAR_DATA;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_ERROR;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
@@ -456,6 +457,13 @@
private static final long NOTIFICATION_TRAMPOLINE_BLOCK = 167676448L;
/**
+ * Whether a notification listeners can understand new, more specific, cancellation reasons.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ private static final long NOTIFICATION_CANCELLATION_REASONS = 175319604L;
+
+ /**
* Rate limit showing toasts, on a per package basis.
*
* It limits the number of {@link android.widget.Toast#show()} calls to prevent overburdening
@@ -3599,7 +3607,7 @@
}
enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- callingUser, REASON_CHANNEL_BANNED, null);
+ callingUser, REASON_CHANNEL_REMOVED, null);
mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
@@ -3623,7 +3631,7 @@
final String channelId = nc.getId();
mAmi.stopForegroundServicesForChannel(pkg, appUserId, channelId);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
- appUserId, REASON_CHANNEL_BANNED, null);
+ appUserId, REASON_CHANNEL_REMOVED, null);
mPreferencesHelper.deleteNotificationChannel(pkg, uid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(uid),
@@ -3672,7 +3680,7 @@
final NotificationChannel deletedChannel = deletedChannels.get(i);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
true,
- userId, REASON_CHANNEL_BANNED,
+ userId, REASON_CHANNEL_REMOVED,
null);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
@@ -3852,7 +3860,7 @@
// Cancel posted notifications
final int userId = UserHandle.getUserId(uid);
cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA, null);
// Zen
packagesChanged |=
@@ -4128,7 +4136,7 @@
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
enforceSystemOrSystemUI("INotificationManager.registerListener");
- mListeners.registerSystemService(listener, component, userid);
+ mListeners.registerSystemService(listener, component, userid, Binder.getCallingUid());
}
/**
@@ -6075,7 +6083,7 @@
mPreferencesHelper.deleteConversations(pkg, uid, shortcuts);
for (String channelId : deletedChannelIds) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
+ UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED,
null);
}
handleSavePolicyFile();
@@ -7482,9 +7490,11 @@
boolean rateLimitingEnabled =
!mToastRateLimitingDisabledUids.contains(record.uid);
boolean isWithinQuota =
- mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
+ mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG)
+ || isExemptFromRateLimiting(record.pkg, userId);
- if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
+ if (tryShowToast(
+ record, rateLimitingEnabled, isWithinQuota)) {
scheduleDurationReachedLocked(record, lastToastWasTextRecord);
mIsCurrentToastShown = true;
if (rateLimitingEnabled) {
@@ -7518,6 +7528,18 @@
return record.show();
}
+ private boolean isExemptFromRateLimiting(String pkg, int userId) {
+ boolean isExemptFromRateLimiting = false;
+ try {
+ isExemptFromRateLimiting = mPackageManager.checkPermission(
+ android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
+ == PackageManager.PERMISSION_GRANTED;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to connect with package manager");
+ }
+ return isExemptFromRateLimiting;
+ }
+
/** Reports rate limiting toasts compat change (used when the toast was blocked). */
private void reportCompatRateLimitingToastsChange(int uid) {
final long id = Binder.clearCallingIdentity();
@@ -10324,6 +10346,10 @@
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
+ if (!CompatChanges.isChangeEnabled(NOTIFICATION_CANCELLATION_REASONS, info.uid)
+ && (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) {
+ reason = REASON_CHANNEL_BANNED;
+ }
listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 619fc4e..dc3b78a 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.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.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 @@
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 @@
updateBadgingEnabled();
updateBubblesEnabled();
updateMediaNotificationFilteringEnabled();
- syncChannelsBypassingDnd(mContext.getUserId());
+ mCurrentUserId = ActivityManager.getCurrentUser();
+ syncChannelsBypassingDnd();
}
public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
@@ -806,7 +809,7 @@
// but the system can
if (group.isBlocked() != oldGroup.isBlocked()) {
group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
}
}
@@ -888,13 +891,13 @@
// 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 @@
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 @@
if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
|| channel.getImportance() != updatedChannel.getImportance()) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
}
updateConfig();
@@ -1145,7 +1148,7 @@
mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg);
if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
}
}
@@ -1512,7 +1515,7 @@
}
}
if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd(mContext.getUserId());
+ updateChannelsBypassingDnd();
}
return deletedChannelIds;
}
@@ -1658,29 +1661,29 @@
}
/**
- * 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 @@
* 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/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9c4c510..cc6a824 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -411,8 +411,13 @@
processName = stream.readString(Tombstone.PROCESS_NAME);
break;
- case (int) Tombstone.CAUSE:
- long token = stream.start(Tombstone.CAUSE);
+ case (int) Tombstone.CAUSES:
+ if (!crashReason.equals("")) {
+ // Causes appear in decreasing order of likelihood. For now we only
+ // want the most likely crash reason here, so ignore all others.
+ break;
+ }
+ long token = stream.start(Tombstone.CAUSES);
cause:
while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (stream.getFieldNumber()) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 7aaab0c..e0a39f3 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 @@
// 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 bafe987..67638bc 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 @@
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 43eeb2a5..764fa02 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.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.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 @@
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 @@
// 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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
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);
@@ -22903,9 +22890,14 @@
@Override
public String getWellbeingPackageName() {
- return CollectionUtils.firstOrNull(
- mContext.getSystemService(RoleManager.class).getRoleHolders(
- RoleManager.ROLE_SYSTEM_WELLBEING));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return CollectionUtils.firstOrNull(
+ mContext.getSystemService(RoleManager.class).getRoleHolders(
+ RoleManager.ROLE_SYSTEM_WELLBEING));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
@@ -27324,6 +27316,11 @@
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 b5765b5..0ddb6cd 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.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 @@
}
}
- 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 @@
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 @@
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 302e657..fd8ec7f 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -20,8 +20,17 @@
import android.annotation.UserIdInt;
import android.app.Person;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+import android.app.appsearch.ReportUsageRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
+import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.content.ComponentName;
import android.content.Intent;
@@ -36,6 +45,7 @@
import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.PersistableBundle;
+import android.os.StrictMode;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -48,6 +58,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -70,13 +81,15 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -145,9 +158,9 @@
private static final String KEY_BITMAP_BYTES = "bitmapBytes";
/**
- * All the shortcuts from the package, keyed on IDs.
+ * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
- final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+ final ArraySet<ShortcutInfo> mShortcuts = new ArraySet<>();
/**
* All the share targets from the package
@@ -207,7 +220,9 @@
}
public int getShortcutCount() {
- return mShortcuts.size();
+ final int[] count = new int[1];
+ forEachShortcut(si -> count[0]++);
+ return count[0];
}
@Override
@@ -221,17 +236,20 @@
// - Unshadow all shortcuts.
// - Set disabled reason.
// - Disable if needed.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_SHADOW);
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasFlags(ShortcutInfo.FLAG_SHADOW)
+ && si.getDisabledReason() == restoreBlockReason
+ && restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ return false;
+ }
+ si.clearFlags(ShortcutInfo.FLAG_SHADOW);
- shortcut.setDisabledReason(restoreBlockReason);
- if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
- shortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
- }
- });
- }
+ si.setDisabledReason(restoreBlockReason);
+ if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.addFlags(ShortcutInfo.FLAG_DISABLED);
+ }
+ return true;
+ });
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags();
@@ -242,7 +260,7 @@
*/
@Nullable
public ShortcutInfo findShortcutById(String id) {
- return mShortcuts.get(id);
+ return getShortcutById(id);
}
public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -265,7 +283,7 @@
}
public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
- ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
+ ensureNotImmutable(findShortcutById(id), ignoreInvisible);
}
public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
@@ -308,8 +326,9 @@
* Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
+ final ShortcutInfo shortcut = getShortcutById(id);
if (shortcut != null) {
+ removeShortcut(id);
mShortcutUser.mService.removeIconLocked(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
| ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
@@ -326,10 +345,10 @@
forceDeleteShortcutInner(newShortcut.getId());
- // Extract Icon and update the icon res ID and the bitmap path.
s.saveIconAndFixUpShortcutLocked(newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
- mShortcuts.put(newShortcut.getId(), newShortcut);
+
+ saveShortcut(newShortcut);
}
/**
@@ -347,7 +366,7 @@
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
if (oldShortcut != null) {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
@@ -379,7 +398,7 @@
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
changedShortcuts.clear();
- final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
boolean deleted = false;
if (oldShortcut == null) {
@@ -418,6 +437,16 @@
}
forceReplaceShortcutInner(newShortcut);
+ // TODO: Report usage can be filed async
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.reportUsage(
+ new ReportUsageRequest.Builder(getPackageName())
+ .setUri(newShortcut.getId()).build(),
+ mShortcutUser.mExecutor, result -> future.complete(result.isSuccess()));
+ return future;
+ });
+
return deleted;
}
@@ -427,19 +456,12 @@
* @return List of removed shortcuts.
*/
private List<ShortcutInfo> removeOrphans() {
- List<ShortcutInfo> removeList = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.isAlive()) continue;
-
- if (removeList == null) {
- removeList = new ArrayList<>();
- }
+ final List<ShortcutInfo> removeList = new ArrayList<>(1);
+ forEachShortcut(si -> {
+ if (si.isAlive()) return;
removeList.add(si);
- }
- if (removeList != null) {
+ });
+ if (!removeList.isEmpty()) {
for (int i = removeList.size() - 1; i >= 0; i--) {
forceDeleteShortcutInner(removeList.get(i).getId());
}
@@ -456,20 +478,19 @@
public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
- boolean changed = false;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final boolean[] changed = new boolean[1];
+ forEachShortcutMutateIf(si -> {
if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
- changed = true;
+ changed[0] = true;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- shortcut.setRank(0); // It may still be pinned, so clear the rank.
- });
+ si.setTimestamp(now);
+ si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ si.setRank(0); // It may still be pinned, so clear the rank.
+ return true;
}
- }
- if (changed) {
+ return false;
+ });
+ if (changed[0]) {
return removeOrphans();
}
return null;
@@ -508,7 +529,7 @@
* @return The deleted shortcut, or null if it was not actually removed because it's pinned.
*/
public ShortcutInfo deleteLongLivedWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
- final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
+ final ShortcutInfo shortcut = findShortcutById(shortcutId);
if (shortcut != null) {
mutateShortcut(shortcutId, null, si -> si.clearFlags(ShortcutInfo.FLAG_CACHED_ALL));
}
@@ -551,7 +572,7 @@
Preconditions.checkState(
(disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
"disable and disabledReason disagree: " + disable + " vs " + disabledReason);
- final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
+ final ShortcutInfo oldShortcut = findShortcutById(shortcutId);
if (oldShortcut == null || !oldShortcut.isEnabled()
&& (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
@@ -567,7 +588,7 @@
si.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
si.addFlags(ShortcutInfo.FLAG_DISABLED);
- // Do not overwrite the disabled reason if one is alreay set.
+ // Do not overwrite the disabled reason if one is already set.
if (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
si.setDisabledReason(disabledReason);
}
@@ -579,7 +600,6 @@
si.setActivity(null);
}
});
-
return null;
} else {
forceDeleteShortcutInner(shortcutId);
@@ -596,7 +616,7 @@
}
public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
- final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+ final ShortcutInfo source = findShortcutById(shortcut.getId());
Objects.requireNonNull(source);
mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
@@ -615,13 +635,6 @@
* <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
*/
public void refreshPinnedFlags() {
- // TODO: rewrite this function with proper query (i.e. fetch only pinned shortcuts and
- // unpin if it's no longer pinned by any launcher and vice versa)
- final List<ShortcutInfo> shortcuts = new ArrayList<>(mShortcuts.values());
- final Map<String, ShortcutInfo> shortcutMap = new ArrayMap<>(shortcuts.size());
- for (ShortcutInfo si : shortcuts) {
- shortcutMap.put(si.getId(), si);
- }
final Set<String> pinnedShortcuts = new ArraySet<>();
// First, for the pinned set for each launcher, keep track of their id one by one.
@@ -631,31 +644,20 @@
if (pinned == null || pinned.size() == 0) {
return;
}
- for (int i = pinned.size() - 1; i >= 0; i--) {
- final String id = pinned.valueAt(i);
- final ShortcutInfo si = shortcutMap.get(id);
- if (si == null) {
- // This happens if a launcher pinned shortcuts from this package, then backup&
- // restored, but this package doesn't allow backing up.
- // In that case the launcher ends up having a dangling pinned shortcuts.
- // That's fine, when the launcher is restored, we'll fix it.
- continue;
- }
- pinnedShortcuts.add(si.getId());
- }
+ pinnedShortcuts.addAll(pinned);
});
// Then, update the pinned state if necessary
- for (int i = shortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = shortcuts.get(i);
+ forEachShortcutMutateIf(si -> {
if (pinnedShortcuts.contains(si.getId()) && !si.isPinned()) {
- mutateShortcut(si.getId(), si,
- shortcut -> shortcut.addFlags(ShortcutInfo.FLAG_PINNED));
+ si.addFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) {
- mutateShortcut(si.getId(), si, shortcut ->
- shortcut.clearFlags(ShortcutInfo.FLAG_PINNED));
+ si.clearFlags(ShortcutInfo.FLAG_PINNED);
+ return true;
}
- }
+ return false;
+ });
// Lastly, remove the ones that are no longer pinned, cached nor dynamic.
removeOrphans();
@@ -764,17 +766,13 @@
// Restored and the app not installed yet, so don't return any.
return;
}
-
final ShortcutService s = mShortcutUser.mService;
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
- .getPinnedShortcutIds(getPackageName(), getPackageUserId());
-
- for (int i = 0; i < mShortcuts.size(); i++) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ .getPinnedShortcutIds(getPackageName(), getPackageUserId());
+ forEachShortcut(si -> {
// Need to adjust PINNED flag depending on the caller.
// Basically if the caller is a launcher (callingLauncher != null) and the launcher
// isn't pinning it, then we need to clear PINNED for this caller.
@@ -784,7 +782,7 @@
if (!getPinnedByAnyLauncher) {
if (si.isFloating() && !si.isCached()) {
if (!isPinnedByCaller) {
- continue;
+ return;
}
}
}
@@ -804,7 +802,7 @@
}
result.add(clone);
}
- }
+ });
}
public void resetThrottling() {
@@ -874,7 +872,7 @@
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- if (mShortcuts.isEmpty() || mShareTargets.isEmpty()) {
+ if (getShortcutCount() == 0 || mShareTargets.isEmpty()) {
return 0;
}
@@ -912,14 +910,12 @@
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
public ArraySet<String> getUsedBitmapFiles() {
- final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final ArraySet<String> usedFiles = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.getBitmapPath() != null) {
usedFiles.add(getFileName(si.getBitmapPath()));
}
- }
+ });
return usedFiles;
}
@@ -936,30 +932,29 @@
* @return false if any of the target activities are no longer enabled.
*/
private boolean areAllActivitiesStillEnabled() {
- if (mShortcuts.size() == 0) {
- return true;
- }
final ShortcutService s = mShortcutUser.mService;
// Normally the number of target activities is 1 or so, so no need to use a complex
// structure like a set.
final ArrayList<ComponentName> checked = new ArrayList<>(4);
+ final boolean[] reject = new boolean[1];
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutStopWhen(si -> {
final ComponentName activity = si.getActivity();
if (checked.contains(activity)) {
- continue; // Already checked.
+ return false; // Already checked.
}
checked.add(activity);
if ((activity != null)
&& !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
- return false;
+ reject[0] = true;
+ return true; // Found at least 1 activity is disabled, so skip the rest.
}
- }
- return true;
+ return false;
+ });
+ return !reject[0];
}
/**
@@ -1042,33 +1037,32 @@
// See if there are any shortcuts that were prevented restoring because the app was of a
// lower version, and re-enable them.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
- continue;
+ return false;
}
if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
si.getId(), getPackageInfo().getBackupSourceVersionCode()));
}
- continue;
+ return false;
}
Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
- shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
- });
- }
+ if (si.hasFlags(ShortcutInfo.FLAG_DISABLED)
+ || si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+ si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+ si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+ return true;
+ }
+ return false;
+ });
// For existing shortcuts, update timestamps if they have any resources.
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
if (!isNewApp) {
- Resources publisherRes = null;
-
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ final Resources publisherRes = getPackageResources();
+ forEachShortcutMutateIf(si -> {
// Disable dynamic shortcuts whose target activity is gone.
if (si.isDynamic()) {
if (si.getActivity() == null) {
@@ -1081,33 +1075,26 @@
getPackageName(), si.getId()));
if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
ShortcutInfo.DISABLED_REASON_APP_CHANGED) != null) {
- continue; // Actually removed.
+ return false; // Actually removed.
}
// Still pinned, so fall-through and possibly update the resources.
}
}
- if (si.hasAnyResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- if (!shortcut.isOriginallyFromManifest()) {
- shortcut.lookupAndFillInResourceIds(res);
- }
-
- // If this shortcut is not from a manifest, then update all resource IDs
- // from resource names. (We don't allow resource strings for
- // non-manifest at the moment, but icons can still be resources.)
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
+ if (!si.hasAnyResources() || publisherRes == null) {
+ return false;
}
- }
+
+ if (!si.isOriginallyFromManifest()) {
+ si.lookupAndFillInResourceIds(publisherRes);
+ }
+
+ // If this shortcut is not from a manifest, then update all resource IDs
+ // from resource names. (We don't allow resource strings for
+ // non-manifest at the moment, but icons can still be resources.)
+ si.setTimestamp(s.injectCurrentTimeMillis());
+ return true;
+ });
}
// (Re-)publish manifest shortcut.
@@ -1133,17 +1120,12 @@
boolean changed = false;
// Keep the previous IDs.
- ArraySet<String> toDisableList = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
+ final ArraySet<String> toDisableList = new ArraySet<>(1);
+ forEachShortcut(si -> {
if (si.isManifestShortcut()) {
- if (toDisableList == null) {
- toDisableList = new ArraySet<>();
- }
toDisableList.add(si.getId());
}
- }
+ });
// Publish new ones.
if (newManifestShortcutList != null) {
@@ -1156,7 +1138,7 @@
final boolean newDisabled = !newShortcut.isEnabled();
final String id = newShortcut.getId();
- final ShortcutInfo oldShortcut = mShortcuts.get(id);
+ final ShortcutInfo oldShortcut = findShortcutById(id);
boolean wasPinned = false;
@@ -1183,7 +1165,7 @@
// regardless.
forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
- if (!newDisabled && toDisableList != null) {
+ if (!newDisabled && !toDisableList.isEmpty()) {
// Still alive, don't remove.
toDisableList.remove(id);
}
@@ -1191,7 +1173,7 @@
}
// Disable the previous manifest shortcuts that are no longer in the manifest.
- if (toDisableList != null) {
+ if (!toDisableList.isEmpty()) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format(
"Package %s: disabling %d stale shortcuts", getPackageName(),
@@ -1206,8 +1188,9 @@
/* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
ShortcutInfo.DISABLED_REASON_APP_CHANGED);
}
- removeOrphans();
}
+ removeOrphans();
+
adjustRanks();
return changed;
}
@@ -1279,25 +1262,21 @@
private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
= new ArrayMap<>();
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (si.isFloating()) {
- continue; // Ignore floating shortcuts, which are not tied to any activities.
+ return; // Ignore floating shortcuts, which are not tied to any activities.
}
final ComponentName activity = si.getActivity();
if (activity == null) {
mShortcutUser.mService.wtf("null activity detected.");
- continue;
+ return;
}
- ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
- if (list == null) {
- list = new ArrayList<>();
- activitiesToShortcuts.put(activity, list);
- }
+ ArrayList<ShortcutInfo> list = activitiesToShortcuts.computeIfAbsent(activity,
+ k -> new ArrayList<>());
list.add(si);
- }
+ });
return activitiesToShortcuts;
}
@@ -1333,15 +1312,13 @@
// (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
// anyway.)
final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo shortcut = mShortcuts.valueAt(i);
-
+ forEachShortcut(shortcut -> {
if (shortcut.isManifestShortcut()) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
} else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
incrementCountForActivity(counts, shortcut.getActivity(), 1);
}
- }
+ });
for (int i = newList.size() - 1; i >= 0; i--) {
final ShortcutInfo newShortcut = newList.get(i);
@@ -1354,7 +1331,7 @@
continue; // Activity can be null for update.
}
- final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
+ final ShortcutInfo original = findShortcutById(newShortcut.getId());
if (original == null) {
if (operation == ShortcutService.OPERATION_UPDATE) {
continue; // When updating, ignore if there's no target.
@@ -1391,34 +1368,19 @@
* For all the text fields, refresh the string values if they're from resources.
*/
public void resolveResourceStrings() {
- // TODO: update resource strings in AppSearch
final ShortcutService s = mShortcutUser.mService;
- List<ShortcutInfo> changedShortcuts = null;
+ final Resources publisherRes = getPackageResources();
+ final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);
- Resources publisherRes = null;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
-
- if (si.hasStringResources()) {
- if (publisherRes == null) {
- publisherRes = getPackageResources();
- if (publisherRes == null) {
- break; // Resources couldn't be loaded.
- }
- }
-
- final Resources res = publisherRes;
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.resolveResourceStrings(res);
- shortcut.setTimestamp(s.injectCurrentTimeMillis());
- });
-
- if (changedShortcuts == null) {
- changedShortcuts = new ArrayList<>(1);
- }
+ if (publisherRes != null) {
+ forEachShortcutMutateIf(si -> {
+ if (!si.hasStringResources()) return false;
+ si.resolveResourceStrings(publisherRes);
+ si.setTimestamp(s.injectCurrentTimeMillis());
changedShortcuts.add(si);
- }
+ return true;
+ });
}
if (!CollectionUtils.isEmpty(changedShortcuts)) {
s.packageShortcutsChanged(getPackageName(), getPackageUserId(), changedShortcuts, null);
@@ -1427,10 +1389,7 @@
/** Clears the implicit ranks for all shortcuts. */
public void clearAllImplicitRanks() {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- mutateShortcut(si.getId(), si, ShortcutInfo::clearImplicitRankAndRankChangedFlag);
- }
+ forEachShortcutMutate(ShortcutInfo::clearImplicitRankAndRankChangedFlag);
}
/**
@@ -1470,17 +1429,16 @@
final long now = s.injectCurrentTimeMillis();
// First, clear ranks for floating shortcuts.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcutMutateIf(si -> {
if (si.isFloating()) {
if (si.getRank() != 0) {
- mutateShortcut(si.getId(), si, shortcut -> {
- shortcut.setTimestamp(now);
- shortcut.setRank(0);
- });
+ si.setTimestamp(now);
+ si.setRank(0);
+ return true;
}
}
- }
+ return false;
+ });
// Then adjust ranks. Ranks are unique for each activity, so we first need to sort
// shortcuts to each activity.
@@ -1505,7 +1463,7 @@
}
// At this point, it must be dynamic.
if (!si.isDynamic()) {
- s.wtf("Non-dynamic shortcut found.");
+ s.wtf("Non-dynamic shortcut found.: " + si.toInsecureString());
continue;
}
final int thisRank = rank++;
@@ -1521,13 +1479,15 @@
/** @return true if there's any shortcuts that are not manifest shortcuts. */
public boolean hasNonManifestShortcuts() {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ final boolean[] condition = new boolean[1];
+ forEachShortcutStopWhen(si -> {
if (!si.isDeclaredInManifest()) {
+ condition[0] = true;
return true;
}
- }
- return false;
+ return false;
+ });
+ return condition[0];
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
@@ -1567,11 +1527,8 @@
pw.print(prefix);
pw.println(" Shortcuts:");
- long totalBitmapSize = 0;
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ final long[] totalBitmapSize = new long[1];
+ forEachShortcut(si -> {
pw.println(si.toDumpString(prefix + " "));
if (si.getBitmapPath() != null) {
final long len = new File(si.getBitmapPath()).length();
@@ -1580,15 +1537,15 @@
pw.print("bitmap size=");
pw.println(len);
- totalBitmapSize += len;
+ totalBitmapSize[0] += len;
}
- }
+ });
pw.print(prefix);
pw.print(" ");
pw.print("Total bitmap size: ");
- pw.print(totalBitmapSize);
+ pw.print(totalBitmapSize[0]);
pw.print(" (");
- pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
+ pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
pw.println(")");
}
@@ -1603,46 +1560,39 @@
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
| (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
+ forEachShortcut(si -> {
if ((si.getFlags() & shortcutFlags) != 0) {
pw.println(si.toDumpString(""));
}
- }
+ });
}
@Override
public JSONObject dumpCheckin(boolean clear) throws JSONException {
final JSONObject result = super.dumpCheckin(clear);
- int numDynamic = 0;
- int numPinned = 0;
- int numManifest = 0;
- int numBitmaps = 0;
- long totalBitmapSize = 0;
+ final int[] numDynamic = new int[1];
+ final int[] numPinned = new int[1];
+ final int[] numManifest = new int[1];
+ final int[] numBitmaps = new int[1];
+ final long[] totalBitmapSize = new long[1];
- final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
- final int size = shortcuts.size();
- for (int i = 0; i < size; i++) {
- final ShortcutInfo si = shortcuts.valueAt(i);
-
- if (si.isDynamic()) numDynamic++;
- if (si.isDeclaredInManifest()) numManifest++;
- if (si.isPinned()) numPinned++;
+ forEachShortcut(si -> {
+ if (si.isDynamic()) numDynamic[0]++;
+ if (si.isDeclaredInManifest()) numManifest[0]++;
+ if (si.isPinned()) numPinned[0]++;
if (si.getBitmapPath() != null) {
- numBitmaps++;
- totalBitmapSize += new File(si.getBitmapPath()).length();
+ numBitmaps[0]++;
+ totalBitmapSize[0] += new File(si.getBitmapPath()).length();
}
- }
+ });
- result.put(KEY_DYNAMIC, numDynamic);
- result.put(KEY_MANIFEST, numManifest);
- result.put(KEY_PINNED, numPinned);
- result.put(KEY_BITMAPS, numBitmaps);
- result.put(KEY_BITMAP_BYTES, totalBitmapSize);
+ result.put(KEY_DYNAMIC, numDynamic[0]);
+ result.put(KEY_MANIFEST, numManifest[0]);
+ result.put(KEY_PINNED, numPinned[0]);
+ result.put(KEY_BITMAPS, numBitmaps[0]);
+ result.put(KEY_BITMAP_BYTES, totalBitmapSize[0]);
// TODO Log update frequency too.
@@ -1652,7 +1602,7 @@
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- final int size = mShortcuts.size();
+ final int size = getShortcutCount();
final int shareTargetSize = mShareTargets.size();
if (size == 0 && shareTargetSize == 0 && mApiCallCount == 0) {
@@ -1666,9 +1616,15 @@
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
- for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup,
- getPackageInfo().isBackupAllowed());
+ if (forBackup) {
+ // Shortcuts are persisted in AppSearch, xml is only needed for backup.
+ forEachShortcut(si -> {
+ try {
+ saveShortcut(out, si, forBackup, getPackageInfo().isBackupAllowed());
+ } catch (IOException | XmlPullParserException e) {
+ throw new RuntimeException(e);
+ }
+ });
}
if (!forBackup) {
@@ -1785,12 +1741,14 @@
}
final Intent[] intentsNoExtras = si.getIntentsNoExtras();
final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
- final int numIntents = intentsNoExtras.length;
- for (int i = 0; i < numIntents; i++) {
- out.startTag(null, TAG_INTENT);
- ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
- ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
- out.endTag(null, TAG_INTENT);
+ if (intentsNoExtras != null && intentsExtras != null) {
+ final int numIntents = intentsNoExtras.length;
+ for (int i = 0; i < numIntents; i++) {
+ out.startTag(null, TAG_INTENT);
+ ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+ ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+ out.endTag(null, TAG_INTENT);
+ }
}
ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
@@ -1877,9 +1835,8 @@
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName,
shortcutUser.getUserId(), fromBackup);
-
// Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
+ ret.mShortcuts.add(si);
continue;
case TAG_SHARE_TARGET:
ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
@@ -2075,7 +2032,9 @@
@VisibleForTesting
List<ShortcutInfo> getAllShortcutsForTest() {
- return new ArrayList<>(mShortcuts.values());
+ final List<ShortcutInfo> ret = new ArrayList<>(1);
+ forEachShortcut(ret::add);
+ return ret;
}
@VisibleForTesting
@@ -2087,7 +2046,7 @@
public void verifyStates() {
super.verifyStates();
- boolean failed = false;
+ final boolean[] failed = new boolean[1];
final ShortcutService s = mShortcutUser.mService;
@@ -2098,7 +2057,7 @@
for (int outer = all.size() - 1; outer >= 0; outer--) {
final ArrayList<ShortcutInfo> list = all.valueAt(outer);
if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
+ " has " + all.valueAt(outer).size() + " shortcuts.");
}
@@ -2118,61 +2077,60 @@
}
// Verify each shortcut's status.
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
+ forEachShortcut(si -> {
if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned() || si.isCached())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not manifest, dynamic or pinned.");
}
if (si.isDeclaredInManifest() && si.isDynamic()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is both dynamic and manifest at the same time.");
}
if (si.getActivity() == null && !si.isFloating()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has null activity, but not floating.");
}
if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not floating, but is disabled.");
}
if (si.isFloating() && si.getRank() != 0) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is floating, but has rank=" + si.getRank());
}
if (si.getIcon() != null) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " still has an icon");
}
if (si.hasAdaptiveBitmap() && !(si.hasIconFile() || si.hasIconUri())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has adaptive bitmap but was not saved to a file nor has icon uri.");
}
if (si.hasIconFile() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both resource and bitmap icons");
}
if (si.hasIconFile() && si.hasIconUri()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and bitmap icons");
}
if (si.hasIconUri() && si.hasIconResource()) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has both url and resource icons");
}
if (si.isEnabled()
!= (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " isEnabled() and getDisabledReason() disagree: "
+ si.isEnabled() + " vs " + si.getDisabledReason());
@@ -2180,18 +2138,18 @@
if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
&& (getPackageInfo().getBackupSourceVersionCode()
== ShortcutInfo.VERSION_CODE_UNKNOWN)) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " RESTORED_VERSION_LOWER with no backup source version code.");
}
if (s.isDummyMainActivity(si.getActivity())) {
- failed = true;
+ failed[0] = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has a dummy target activity");
}
- }
+ });
- if (failed) {
+ if (failed[0]) {
throw new IllegalStateException("See logcat for errors");
}
}
@@ -2202,7 +2160,7 @@
} else {
mPackageIdentifiers.remove(packageName);
}
- resetAppSearch(null);
+ resetAppSearch(session -> AndroidFuture.completedFuture(true));
}
void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
@@ -2212,23 +2170,284 @@
synchronized (mLock) {
if (shortcut != null) {
transform.accept(shortcut);
- } else {
- transform.accept(findShortcutById(id));
}
- // TODO: Load ShortcutInfo from AppSearch, apply transformation logic and save
+ final ShortcutInfo si = getShortcutById(id);
+ if (si == null) {
+ return;
+ }
+ transform.accept(si);
+ saveShortcut(si);
}
}
+ private void saveShortcut(@NonNull final ShortcutInfo... shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ saveShortcut(Arrays.asList(shortcuts));
+ }
+
+ private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
+ Objects.requireNonNull(shortcuts);
+ ConcurrentUtils.waitForFutureNoInterrupt(
+ runInAppSearch(session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.put(new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ AppSearchShortcutInfo.toGenericDocuments(shortcuts))
+ .build(),
+ mShortcutUser.mExecutor,
+ result -> {
+ if (!result.isSuccess()) {
+ for (AppSearchResult<Void> k : result.getFailures().values()) {
+ Slog.e(TAG, k.getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "failed to save shortcuts"));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ }),
+ "saving shortcut");
+ }
+
/**
* Removes shortcuts from AppSearch.
*/
void removeShortcuts() {
+ awaitInAppSearch("removing shortcuts", session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove("",
+ new SearchSpec.Builder()
+ .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(new RuntimeException(
+ "Failed to cleanup shortcuts " + result.getErrorMessage()));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private void removeShortcut(@NonNull final String id) {
+ Objects.requireNonNull(id);
+ awaitInAppSearch("removing shortcut with id=" + id, session -> {
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
+ session.remove(new RemoveByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ final Map<String, AppSearchResult<Void>> failures =
+ result.getFailures();
+ for (String key : failures.keySet()) {
+ Slog.e(TAG, "Failed deleting " + key + ", error message:"
+ + failures.get(key).getErrorMessage());
+ }
+ future.completeExceptionally(new RuntimeException(
+ "Failed to delete shortcut: " + id));
+ return;
+ }
+ future.complete(true);
+ });
+ return future;
+ });
+ }
+
+ private ShortcutInfo getShortcutById(String id) {
+ return awaitInAppSearch("getting shortcut with id=" + id, session -> {
+ final AndroidFuture<ShortcutInfo> future = new AndroidFuture<>();
+ session.getByUri(
+ new GetByUriRequest.Builder(getPackageName()).addUris(id).build(),
+ mShortcutUser.mExecutor,
+ results -> {
+ if (results.isSuccess()) {
+ Map<String, GenericDocument> documents = results.getSuccesses();
+ for (GenericDocument doc : documents.values()) {
+ final ShortcutInfo info = new AppSearchShortcutInfo(doc)
+ .toShortcutInfo(mShortcutUser.getUserId());
+ future.complete(info);
+ return;
+ }
+ }
+ future.complete(null);
+ });
+ return future;
+ });
+ }
+
+ private void forEachShortcut(
+ @NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutStopWhen(si -> {
+ cb.accept(si);
+ return false;
+ });
+ }
+
+ private void forEachShortcutMutate(@NonNull final Consumer<ShortcutInfo> cb) {
+ forEachShortcutMutateIf(si -> {
+ cb.accept(si);
+ return true;
+ });
+ }
+
+ private void forEachShortcutMutateIf(@NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("mutating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ final List<ShortcutInfo> changed = new ArrayList<>(1);
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) changed.add(si);
+ }
+ saveShortcut(changed);
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ forEachShortcutStopWhen("", cb);
+ }
+
+ private void forEachShortcutStopWhen(
+ @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) {
+ final SearchResults res = awaitInAppSearch("iterating shortcuts", session ->
+ AndroidFuture.completedFuture(session.search(query, new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
+ if (res == null) return;
+ List<ShortcutInfo> shortcuts = getNextPage(res);
+ while (!shortcuts.isEmpty()) {
+ for (ShortcutInfo si : shortcuts) {
+ if (cb.apply(si)) return;
+ }
+ shortcuts = getNextPage(res);
+ }
+ }
+
+ private List<ShortcutInfo> getNextPage(@NonNull final SearchResults res) {
+ final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
+ final List<ShortcutInfo> ret = new ArrayList<>();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ res.getNextPage(mShortcutUser.mExecutor, nextPage -> {
+ if (!nextPage.isSuccess()) {
+ future.complete(ret);
+ return;
+ }
+ final List<SearchResult> results = nextPage.getResultValue();
+ if (results.isEmpty()) {
+ future.complete(ret);
+ return;
+ }
+ final List<ShortcutInfo> page = new ArrayList<>(results.size());
+ for (SearchResult result : results) {
+ final ShortcutInfo si = new AppSearchShortcutInfo(result.getGenericDocument())
+ .toShortcutInfo(mShortcutUser.getUserId());
+ page.add(si);
+ }
+ ret.addAll(page);
+ future.complete(ret);
+ });
+ return ConcurrentUtils.waitForFutureNoInterrupt(future,
+ "getting next batch of shortcuts");
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Nullable
+ private <T> T awaitInAppSearch(
+ @NonNull final String description,
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ return ConcurrentUtils.waitForFutureNoInterrupt(runInAppSearch(cb), description);
+ }
+
+ @Nullable
+ private <T> CompletableFuture<T> runInAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ synchronized (mLock) {
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ try {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
+ .build());
+ if (mAppSearchSession != null) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return AndroidFuture.supply(() -> mAppSearchSession).thenCompose(cb);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ } else {
+ return resetAppSearch(cb);
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+ }
+
+ private <T> CompletableFuture<T> resetAppSearch(
+ @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ final AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder(getPackageName()).build();
+ final AppSearchSession session;
+ try {
+ session = ConcurrentUtils.waitForFutureNoInterrupt(
+ mShortcutUser.getAppSearch(searchContext), "resetting app search");
+ ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "setting up schema");
+ 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);
+ }
+ }
+
+ @NonNull
+ private AndroidFuture<AppSearchSession> setupSchema(
+ @NonNull final AppSearchSession session) {
+ SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
+ for (PackageIdentifier pi : mPackageIdentifiers.values()) {
+ schemaBuilder = schemaBuilder
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchPerson.SCHEMA_TYPE, true, pi)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
+ }
+ final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
+ session.setSchema(
+ schemaBuilder.build(), mShortcutUser.mExecutor, mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new IllegalArgumentException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(session);
+ });
+ return future;
}
/**
* Merge/replace shortcuts parsed from xml file.
*/
void restoreParsedShortcuts(final boolean replace) {
+ if (replace) {
+ removeShortcuts();
+ }
+ saveShortcut(mShortcuts);
}
private boolean verifyRanksSequential(List<ShortcutInfo> list) {
@@ -2239,133 +2458,9 @@
if (si.getRank() != i) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
- + " rank=" + si.getRank() + " but expected to be "+ i);
+ + " rank=" + si.getRank() + " but expected to be " + i);
}
}
return failed;
}
-
- private void runInAppSearch(
- Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
- if (mShortcutUser == null) {
- Slog.w(TAG, "shortcut user is null");
- return;
- }
- synchronized (mLock) {
- if (mAppSearchSession != null) {
- final CountDownLatch latch = new CountDownLatch(1);
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- final SearchSessionObservable upstream =
- new SearchSessionObservable(mAppSearchSession, latch);
- for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
- : observers) {
- upstream.map(observer);
- }
- upstream.next();
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
- }
- ConcurrentUtils.waitForCountDownNoInterrupt(latch, 500,
- "timeout accessing shortcut");
- } else {
- resetAppSearch(observers);
- }
- }
- }
-
- private void resetAppSearch(
- Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
- final CountDownLatch latch = new CountDownLatch(1);
- final AppSearchManager.SearchContext searchContext =
- new AppSearchManager.SearchContext.Builder()
- .setDatabaseName(getPackageName()).build();
- mShortcutUser.runInAppSearch(searchContext, result -> {
- if (!result.isSuccess()) {
- Slog.e(TAG, "error getting search session during lazy init, "
- + result.getErrorMessage());
- latch.countDown();
- return;
- }
- // TODO: Flatten callback chain with proper async framework
- final SearchSessionObservable upstream =
- new SearchSessionObservable(result.getResultValue(), latch)
- .map(this::setupSchema);
- if (observers != null) {
- for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
- : observers) {
- upstream.map(observer);
- }
- }
- upstream.map(observable -> session -> {
- mAppSearchSession = session;
- observable.next();
- });
- upstream.next();
- });
- ConcurrentUtils.waitForCountDownNoInterrupt(latch, 1500,
- "timeout accessing shortcut during lazy initialization");
- }
-
- /**
- * creates the schema for shortcut in the database
- */
- private Consumer<AppSearchSession> setupSchema(SearchSessionObservable observable) {
- return session -> {
- SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
- .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
- for (PackageIdentifier pi : mPackageIdentifiers.values()) {
- schemaBuilder = schemaBuilder
- .setSchemaTypeVisibilityForPackage(
- AppSearchPerson.SCHEMA_TYPE, true, pi)
- .setSchemaTypeVisibilityForPackage(
- AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
- }
- session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
- if (!result.isSuccess()) {
- observable.error("failed to instantiate app search schema: "
- + result.getErrorMessage());
- return;
- }
- observable.next();
- });
- };
- }
-
- /**
- * TODO: Replace this temporary implementation with proper async framework
- */
- private class SearchSessionObservable {
-
- final AppSearchSession mSession;
- final CountDownLatch mLatch;
- final ArrayList<Consumer<AppSearchSession>> mObservers = new ArrayList<>(1);
-
- SearchSessionObservable(@NonNull final AppSearchSession session,
- @NonNull final CountDownLatch latch) {
- mSession = session;
- mLatch = latch;
- }
-
- SearchSessionObservable map(
- Function<SearchSessionObservable, Consumer<AppSearchSession>> observer) {
- mObservers.add(observer.apply(this));
- return this;
- }
-
- void next() {
- if (mObservers.isEmpty()) {
- mLatch.countDown();
- return;
- }
- mObservers.remove(0).accept(mSession);
- }
-
- void error(@Nullable final String errorMessage) {
- if (errorMessage != null) {
- Slog.e(TAG, errorMessage);
- }
- mLatch.countDown();
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 38cba4c..e1d1c26 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 @@
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 @@
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;
@@ -2590,7 +2590,6 @@
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
ps.findAll(ret, query, cloneFlags);
-
return new ParceledListSlice<>(setReturnedByServer(ret));
}
@@ -5078,6 +5077,17 @@
}
@VisibleForTesting
+ void updatePackageShortcutForTest(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ synchronized (mLock) {
+ final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
+ if (pkg == null) return;
+
+ pkg.mutateShortcut(shortcutId, null, cb);
+ }
+ }
+
+ @VisibleForTesting
ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
synchronized (mLock) {
final ShortcutUser user = mUsers.get(userId);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index ec784d0..ce49d88 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
import android.content.pm.ShortcutManager;
import android.metrics.LogMaker;
@@ -35,6 +34,7 @@
import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.FgThread;
@@ -715,17 +715,32 @@
.setSubtype(totalSharingShortcutCount));
}
- void runInAppSearch(@NonNull final AppSearchManager.SearchContext searchContext,
- @NonNull final Consumer<AppSearchResult<AppSearchSession>> callback) {
+ AndroidFuture<AppSearchSession> getAppSearch(
+ @NonNull final AppSearchManager.SearchContext searchContext) {
+ final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
if (mAppSearchManager == null) {
- Slog.e(TAG, "app search manager is null");
- return;
+ 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, callback);
+ mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> {
+ if (!result.isSuccess()) {
+ future.completeExceptionally(
+ new RuntimeException(result.getErrorMessage()));
+ return;
+ }
+ future.complete(result.getResultValue());
+ });
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
+ return future;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fe19956..2a0257d 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 @@
}
@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 17ce386..6824f7d 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 @@
*/
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 @@
@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 @@
this.mBadgeLabels = badgeLabels;
this.mBadgeColors = badgeColors;
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
+ this.mSharesMediaWithParent = sharesMediaWithParent;
}
/**
@@ -291,6 +300,13 @@
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 @@
: 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 @@
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 @@
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 @@
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 6aac0b2..e8421a5 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_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 @@
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 @@
}
/**
+ * 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 629f120..21e44ab 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 @@
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 27bf8a13..e16c67f 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 @@
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 @@
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 @@
// 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 @@
@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/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index b421cfc..cda4806 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -435,11 +435,12 @@
}
}
}
+ boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
+ && !permission.isRuntime();
if (permission == null) {
permission = new Permission(permissionInfo.name, permissionInfo.packageName,
TYPE_MANIFEST);
}
- boolean wasNonRuntime = !permission.isRuntime();
StringBuilder r = null;
if (!permission.mReconciled) {
if (permission.mPermissionInfo.packageName == null
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 616058f..e63426f 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_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 @@
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 @@
| 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;
@@ -2937,7 +2939,8 @@
>= Build.VERSION_CODES.M;
for (String permission : ps.getGrantedPermissions()) {
- if (!pkg.getImplicitPermissions().contains(permission)) {
+ if (pkg.getRequestedPermissions().contains(permission)
+ && !pkg.getImplicitPermissions().contains(permission)) {
Permission bp = mRegistry.getPermission(permission);
if (bp != null && bp.isRuntime()) {
int flags = ps.getPermissionFlags(permission);
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 f4bcd3e..0b48b5c 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 @@
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 a0e252a..1b2ff08 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 @@
@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 @@
@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 @@
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 @@
}
if (sendBroadcast) {
- sendBroadcastForPackage(pkgName);
+ sendBroadcast(pkgName);
}
}
@@ -936,7 +946,7 @@
}
if (sendBroadcast && hasAutoVerifyDomains) {
- sendBroadcastForPackage(pkgName);
+ sendBroadcast(pkgName);
}
}
@@ -1097,8 +1107,19 @@
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 @@
@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 @@
}
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 @@
}
if (!packagesToBroadcast.isEmpty()) {
- mProxy.sendBroadcastForPackages(packagesToBroadcast);
+ sendBroadcast(packagesToBroadcast);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index cb3b5c9..44ff3eb 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -22,7 +22,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Binder;
import com.android.internal.util.CollectionUtils;
import com.android.server.compat.PlatformCompat;
@@ -77,9 +76,7 @@
static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,
long changeId) {
- //noinspection ConstantConditions
- return Binder.withCleanCallingIdentity(
- () -> platformCompat.isChangeEnabled(changeId, buildMockAppInfo(pkg)));
+ return platformCompat.isChangeEnabledInternalNoLogging(changeId, buildMockAppInfo(pkg));
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d5fc9ff..f185464 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2341,8 +2341,6 @@
params.packageName = packageName;
params.windowAnimations = win.getWindowStyle().getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- params.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
diff --git a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
index 2fcd178..9732eba3 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 @@
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 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 d2a4cd6..54d5b7a 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_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.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 @@
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 @@
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 @@
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 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 @@
mContext.getOpPackageName(), "groupId: " + groupId);
}
- mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
+ mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
updatePowerStateLocked();
}
}
@@ -1059,6 +1061,10 @@
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 @@
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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"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 @@
*/
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);
+ if ((dirty & (DIRTY_DISPLAY_GROUP_WAKEFULNESS | DIRTY_WAKE_LOCKS
+ | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) == 0) {
+ return;
+ }
+ 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;
+ 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 @@
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 @@
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 @@
| 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 @@
}
/**
- * 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 @@
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 @@
}
// 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 @@
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 @@
&& mBatteryLevel < mDreamsBatteryLevelMinimumWhenNotPoweredConfig) {
return false;
}
- if (mIsPowered
- && mDreamsBatteryLevelMinimumWhenPoweredConfig >= 0
- && mBatteryLevel < mDreamsBatteryLevelMinimumWhenPoweredConfig) {
- return false;
- }
+ return !mIsPowered
+ || mDreamsBatteryLevelMinimumWhenPoweredConfig < 0
+ || mBatteryLevel >= mDreamsBatteryLevelMinimumWhenPoweredConfig;
}
return true;
}
@@ -3002,7 +3060,7 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 beebb31..fe21201 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 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 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 @@
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 @@
*/
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index a7f3cdb..c029bf5 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.hardware.SensorPrivacyManager;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ResultReceiver;
@@ -79,6 +80,7 @@
FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__FAILURE;
private final Context mContext;
+ private final SensorPrivacyManager mPrivacyManager;
boolean mIsServiceEnabled;
public RotationResolverManagerService(Context context) {
@@ -89,6 +91,7 @@
PACKAGE_UPDATE_POLICY_REFRESH_EAGER
| /*To avoid high rotation latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
mContext = context;
+ mPrivacyManager = SensorPrivacyManager.getInstance(context);
}
@Override
@@ -156,15 +159,22 @@
Objects.requireNonNull(callbackInternal);
Objects.requireNonNull(cancellationSignalInternal);
synchronized (mLock) {
- if (mIsServiceEnabled) {
- final RotationResolverManagerPerUserService service = getServiceForUserLocked(
- UserHandle.getCallingUserId());
+ final boolean isCameraAvailable = !mPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.Sensors.CAMERA);
+ if (mIsServiceEnabled && isCameraAvailable) {
+ final RotationResolverManagerPerUserService service =
+ getServiceForUserLocked(
+ UserHandle.getCallingUserId());
final RotationResolutionRequest request = new RotationResolutionRequest("",
currentRotation, proposedRotation, true, timeout);
service.resolveRotationLocked(callbackInternal, request,
cancellationSignalInternal);
} else {
- Slog.w(TAG, "Rotation Resolver service is disabled.");
+ if (isCameraAvailable) {
+ Slog.w(TAG, "Rotation Resolver service is disabled.");
+ } else {
+ Slog.w(TAG, "Camera is locked by a toggle.");
+ }
callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
logRotationStats(proposedRotation, currentRotation, RESOLUTION_DISABLED);
}
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
new file mode 100644
index 0000000..3ca8a5a
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -0,0 +1,302 @@
+/*
+ * 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.servicewatcher;
+
+import static android.content.pm.PackageManager.GET_META_DATA;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
+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_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import android.annotation.BoolRes;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Supplies services based on the current active user and version as defined in the service
+ * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
+ * ensure only system (ie, privileged) services are matched. It also handles services that are not
+ * direct boot aware, and will automatically pick the best service as the user's direct boot state
+ * changes.
+ *
+ * <p>Optionally, two permissions may be specified: (1) a caller permission - any service that does
+ * not require callers to hold this permission is rejected (2) a service permission - any service
+ * whose package does not hold this permission is rejected.
+ */
+public class CurrentUserServiceSupplier extends BroadcastReceiver implements
+ ServiceSupplier<CurrentUserServiceSupplier.BoundServiceInfo> {
+
+ private static final String TAG = "CurrentUserServiceSupplier";
+
+ private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+ private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+
+ private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
+ if (o1 == o2) {
+ return 0;
+ } else if (o1 == null) {
+ return -1;
+ } else if (o2 == null) {
+ return 1;
+ }
+
+ // ServiceInfos with higher version numbers always win. if version numbers are equal
+ // then we prefer components that work for all users vs components that only work for a
+ // single user at a time. otherwise everything's equal.
+ int ret = Integer.compare(o1.getVersion(), o2.getVersion());
+ if (ret == 0) {
+ if (o1.getUserId() != USER_SYSTEM && o2.getUserId() == USER_SYSTEM) {
+ ret = -1;
+ } else if (o1.getUserId() == USER_SYSTEM && o2.getUserId() != USER_SYSTEM) {
+ ret = 1;
+ }
+ }
+ return ret;
+ };
+
+ /** Bound service information with version information. */
+ public static class BoundServiceInfo extends ServiceWatcher.BoundServiceInfo {
+
+ private static int parseUid(ResolveInfo resolveInfo) {
+ int uid = resolveInfo.serviceInfo.applicationInfo.uid;
+ Bundle metadata = resolveInfo.serviceInfo.metaData;
+ if (metadata != null && metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false)) {
+ // reconstruct a uid for the same app but with the system user - hope this exists
+ uid = UserHandle.getUid(USER_SYSTEM, UserHandle.getAppId(uid));
+ }
+ return uid;
+ }
+
+ private static int parseVersion(ResolveInfo resolveInfo) {
+ int version = Integer.MIN_VALUE;
+ if (resolveInfo.serviceInfo.metaData != null) {
+ version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
+ }
+ return version;
+ }
+
+ private final int mVersion;
+ private final @Nullable Bundle mMetadata;
+
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(action, parseUid(resolveInfo), resolveInfo.serviceInfo.getComponentName(),
+ parseVersion(resolveInfo), resolveInfo.serviceInfo.metaData);
+ }
+
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName, int version,
+ @Nullable Bundle metadata) {
+ super(action, uid, componentName);
+
+ mVersion = version;
+ mMetadata = metadata;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public @Nullable Bundle getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "@" + mVersion;
+ }
+ }
+
+ private static @Nullable String retrieveExplicitPackage(Context context,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+ Resources resources = context.getResources();
+ boolean enableOverlay = resources.getBoolean(enableOverlayResId);
+ if (!enableOverlay) {
+ return resources.getString(nonOverlayPackageResId);
+ } else {
+ return null;
+ }
+ }
+
+ private final Context mContext;
+ private final ActivityManagerInternal mActivityManager;
+ private final Intent mIntent;
+ // a permission that the service forces callers (ie ServiceWatcher/system server) to hold
+ private final @Nullable String mCallerPermission;
+ // a permission that the service package should hold
+ private final @Nullable String mServicePermission;
+
+ private volatile ServiceChangedListener mListener;
+
+ public CurrentUserServiceSupplier(Context context, String action) {
+ this(context, action, null, null, null);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
+ this(context, action,
+ retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId), null,
+ null);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
+ @Nullable String callerPermission, @Nullable String servicePermission) {
+ this(context, action,
+ retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId),
+ callerPermission, servicePermission);
+ }
+
+ public CurrentUserServiceSupplier(Context context, String action,
+ @Nullable String explicitPackage, @Nullable String callerPermission,
+ @Nullable String servicePermission) {
+ mContext = context;
+ mActivityManager = Objects.requireNonNull(
+ LocalServices.getService(ActivityManagerInternal.class));
+ mIntent = new Intent(action);
+
+ if (explicitPackage != null) {
+ mIntent.setPackage(explicitPackage);
+ }
+
+ mCallerPermission = callerPermission;
+ mServicePermission = servicePermission;
+ }
+
+ @Override
+ public boolean hasMatchingService() {
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentServicesAsUser(mIntent,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
+ UserHandle.USER_SYSTEM);
+ return !resolveInfos.isEmpty();
+ }
+
+ @Override
+ public void register(ServiceChangedListener listener) {
+ Preconditions.checkState(mListener == null);
+
+ mListener = listener;
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mContext.registerReceiverAsUser(this, UserHandle.ALL, intentFilter, null,
+ FgThread.getHandler());
+ }
+
+ @Override
+ public void unregister() {
+ Preconditions.checkArgument(mListener != null);
+
+ mListener = null;
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public BoundServiceInfo getServiceInfo() {
+ BoundServiceInfo bestServiceInfo = null;
+
+ // only allow privileged services in the correct direct boot state to match
+ int currentUserId = mActivityManager.getCurrentUserId();
+ List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
+ mIntent,
+ GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
+ currentUserId);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo service = Objects.requireNonNull(resolveInfo.serviceInfo);
+
+ if (mCallerPermission != null) {
+ if (!mCallerPermission.equals(service.permission)) {
+ Log.d(TAG, service.getComponentName().flattenToShortString()
+ + " disqualified due to not requiring " + mCallerPermission);
+ continue;
+ }
+ }
+
+ BoundServiceInfo serviceInfo = new BoundServiceInfo(mIntent.getAction(), resolveInfo);
+
+ if (mServicePermission != null) {
+ if (PermissionManager.checkPackageNamePermission(mServicePermission,
+ service.packageName, serviceInfo.getUserId()) != PERMISSION_GRANTED) {
+ Log.d(TAG, serviceInfo.getComponentName().flattenToShortString()
+ + " disqualified due to not holding " + mCallerPermission);
+ continue;
+ }
+ }
+
+ if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
+ bestServiceInfo = serviceInfo;
+ }
+ }
+
+ return bestServiceInfo;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId == UserHandle.USER_NULL) {
+ return;
+ }
+ ServiceChangedListener listener = mListener;
+ if (listener == null) {
+ return;
+ }
+
+ switch (action) {
+ case Intent.ACTION_USER_SWITCHED:
+ listener.onServiceChanged();
+ break;
+ case Intent.ACTION_USER_UNLOCKED:
+ // user unlocked implies direct boot mode may have changed
+ if (userId == mActivityManager.getCurrentUserId()) {
+ listener.onServiceChanged();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 5d49663..030bbd2 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 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,516 +16,244 @@
package com.android.server.servicewatcher;
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-import static android.content.Context.BIND_NOT_VISIBLE;
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
-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_SYSTEM_ONLY;
-
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-import com.android.internal.annotations.Immutable;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.function.Predicate;
/**
- * Maintains a binding to the best service that matches the given intent information. Bind and
- * unbind callbacks, as well as all binder operations, will all be run on a single thread.
+ * A ServiceWatcher is responsible for continuously maintaining an active binding to a service
+ * selected by it's {@link ServiceSupplier}. The {@link ServiceSupplier} may change the service it
+ * selects over time, and the currently bound service may crash, restart, have a user change, have
+ * changes made to its package, and so on and so forth. The ServiceWatcher is responsible for
+ * maintaining the binding across all these changes.
+ *
+ * <p>Clients may invoke {@link BinderOperation}s on the ServiceWatcher, and it will make a best
+ * effort to run these on the currently bound service, but individual operations may fail (if there
+ * is no service currently bound for instance). In order to help clients maintain the correct state,
+ * clients may supply a {@link ServiceListener}, which is informed when the ServiceWatcher connects
+ * and disconnects from a service. This allows clients to bring a bound service back into a known
+ * state on connection, and then run binder operations from there. In order to help clients
+ * accomplish this, ServiceWatcher guarantees that {@link BinderOperation}s and the
+ * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
+ * can be established between them.
+ *
+ * There is never any guarantee of whether a ServiceWatcher is currently connected to a service, and
+ * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
+ * on this, and instead use {@link ServiceListener} notifications as necessary to recover from
+ * failures.
*/
-public class ServiceWatcher implements ServiceConnection {
+public interface ServiceWatcher {
- private static final String TAG = "ServiceWatcher";
- private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
- private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
-
- private static final long RETRY_DELAY_MS = 15 * 1000;
-
- private static final Predicate<ResolveInfo> DEFAULT_SERVICE_CHECK_PREDICATE = x -> true;
-
- /** Function to run on binder interface. */
- public interface BinderRunner {
- /** Called to run client code with the binder. */
+ /**
+ * Operation to run on a binder interface. All operations will be run on the thread used by the
+ * ServiceWatcher this is run with.
+ */
+ interface BinderOperation {
+ /** Invoked to run the operation. Run on the ServiceWatcher thread. */
void run(IBinder binder) throws RemoteException;
+
/**
- * Called if an error occurred and the function could not be run. This callback is only
- * intended for resource deallocation and cleanup in response to a single binder operation,
- * it should not be used to propagate errors further.
+ * Invoked if {@link #run(IBinder)} could not be invoked because there was no current
+ * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
+ * {@link RuntimeException}). This callback is only intended for resource deallocation and
+ * cleanup in response to a single binder operation, it should not be used to propagate
+ * errors further. Run on the ServiceWatcher thread.
*/
default void onError() {}
}
- /** Function to run on binder interface when first bound. */
- public interface OnBindRunner {
- /** Called to run client code with the binder. */
- void run(IBinder binder, BoundService service) throws RemoteException;
+ /**
+ * Listener for bind and unbind events. All operations will be run on the thread used by the
+ * ServiceWatcher this is run with.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
+ /** Invoked when a service is bound. Run on the ServiceWatcher thread. */
+ void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
+
+ /** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
+ void onUnbind();
}
/**
- * Information on the service ServiceWatcher has selected as the best option for binding.
+ * A listener for when a {@link ServiceSupplier} decides that the current service has changed.
*/
- @Immutable
- public static final class BoundService implements Comparable<BoundService> {
+ interface ServiceChangedListener {
+ /**
+ * Should be invoked when the current service may have changed.
+ */
+ void onServiceChanged();
+ }
- public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null,
- false, null, -1);
+ /**
+ * This supplier encapsulates the logic of deciding what service a {@link ServiceWatcher} should
+ * be bound to at any given moment.
+ *
+ * @param <TBoundServiceInfo> type of bound service
+ */
+ interface ServiceSupplier<TBoundServiceInfo extends BoundServiceInfo> {
+ /**
+ * Should return true if there exists at least one service capable of meeting the criteria
+ * of this supplier. This does not imply that {@link #getServiceInfo()} will always return a
+ * non-null result, as any service may be disqualified for various reasons at any point in
+ * time. May be invoked at any time from any thread and thus should generally not have any
+ * dependency on the other methods in this interface.
+ */
+ boolean hasMatchingService();
- public final int version;
- @Nullable
- public final ComponentName component;
- public final boolean serviceIsMultiuser;
- public final int uid;
- @Nullable
- public final Bundle metadata;
+ /**
+ * Invoked when the supplier should start monitoring for any changes that could result in a
+ * different service selection, and should invoke
+ * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
+ * may be invoked after this method is called.
+ */
+ void register(ServiceChangedListener listener);
- BoundService(ResolveInfo resolveInfo) {
- Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null);
+ /**
+ * Invoked when the supplier should stop monitoring for any changes that could result in a
+ * different service selection, should no longer invoke
+ * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
+ * invoked after this method is called.
+ */
+ void unregister();
- metadata = resolveInfo.serviceInfo.metaData;
- if (metadata != null) {
- version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
- serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
- } else {
- version = Integer.MIN_VALUE;
- serviceIsMultiuser = false;
- }
+ /**
+ * Must be implemented to return the current service selected by this supplier. May return
+ * null if no service currently meets the criteria. Only invoked while registered.
+ */
+ @Nullable TBoundServiceInfo getServiceInfo();
+ }
- component = resolveInfo.serviceInfo.getComponentName();
- uid = resolveInfo.serviceInfo.applicationInfo.uid;
+ /**
+ * Information on the service selected as the best option for binding.
+ */
+ class BoundServiceInfo {
+
+ protected final @Nullable String mAction;
+ protected final int mUid;
+ protected final ComponentName mComponentName;
+
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(action, resolveInfo.serviceInfo.applicationInfo.uid,
+ resolveInfo.serviceInfo.getComponentName());
}
- private BoundService(int version, @Nullable ComponentName component,
- boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) {
- Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE);
- this.version = version;
- this.component = component;
- this.serviceIsMultiuser = serviceIsMultiuser;
- this.metadata = metadata;
- this.uid = uid;
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
+ mAction = action;
+ mUid = uid;
+ mComponentName = Objects.requireNonNull(componentName);
}
- public @Nullable String getPackageName() {
- return component != null ? component.getPackageName() : null;
+ /** Returns the action associated with this bound service. */
+ public @Nullable String getAction() {
+ return mAction;
+ }
+
+ /** Returns the component of this bound service. */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /** Returns the user id for this bound service. */
+ public @UserIdInt int getUserId() {
+ return UserHandle.getUserId(mUid);
}
@Override
- public boolean equals(Object o) {
+ public final boolean equals(Object o) {
if (this == o) {
return true;
}
- if (!(o instanceof BoundService)) {
+ if (!(o instanceof BoundServiceInfo)) {
return false;
}
- BoundService that = (BoundService) o;
- return version == that.version && uid == that.uid
- && Objects.equals(component, that.component);
+
+ BoundServiceInfo that = (BoundServiceInfo) o;
+ return mUid == that.mUid
+ && Objects.equals(mAction, that.mAction)
+ && mComponentName.equals(that.mComponentName);
}
@Override
- public int hashCode() {
- return Objects.hash(version, component, uid);
- }
-
- @Override
- public int compareTo(BoundService that) {
- // ServiceInfos with higher version numbers always win (having a version number >
- // MIN_VALUE implies having a non-null component). if version numbers are equal, a
- // non-null component wins over a null component. if the version numbers are equal and
- // both components exist then we prefer components that work for all users vs components
- // that only work for a single user at a time. otherwise everything's equal.
- int ret = Integer.compare(version, that.version);
- if (ret == 0) {
- if (component == null && that.component != null) {
- ret = -1;
- } else if (component != null && that.component == null) {
- ret = 1;
- } else {
- if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM
- && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) {
- ret = -1;
- } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM
- && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) {
- ret = 1;
- }
- }
- }
- return ret;
+ public final int hashCode() {
+ return Objects.hash(mAction, mUid, mComponentName);
}
@Override
public String toString() {
- if (component == null) {
+ if (mComponentName == null) {
return "none";
} else {
- return component.toShortString() + "@" + version + "[u"
- + UserHandle.getUserId(uid) + "]";
+ return mUid + "/" + mComponentName.flattenToShortString();
}
}
}
- private final Context mContext;
- private final Handler mHandler;
- private final Intent mIntent;
- private final Predicate<ResolveInfo> mServiceCheckPredicate;
-
- private final PackageMonitor mPackageMonitor = new PackageMonitor() {
- @Override
- public boolean onPackageChanged(String packageName, int uid, String[] components) {
- return true;
- }
-
- @Override
- public void onSomePackagesChanged() {
- onBestServiceChanged(false);
- }
- };
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action == null) {
- return;
- }
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId == UserHandle.USER_NULL) {
- return;
- }
-
- switch (action) {
- case Intent.ACTION_USER_SWITCHED:
- onUserSwitched(userId);
- break;
- case Intent.ACTION_USER_UNLOCKED:
- onUserUnlocked(userId);
- break;
- default:
- break;
- }
-
- }
- };
-
- // read/write from handler thread only
- private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>();
-
- @Nullable
- private final OnBindRunner mOnBind;
-
- @Nullable
- private final Runnable mOnUnbind;
-
- // read/write from handler thread only
- private boolean mRegistered;
-
- // read/write from handler thread only
- private int mCurrentUserId;
-
- // write from handler thread only, read anywhere
- private volatile BoundService mTargetService;
- private volatile IBinder mBinder;
-
- public ServiceWatcher(Context context, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- this(context, FgThread.getHandler(), action, onBind, onUnbind, enableOverlayResId,
- nonOverlayPackageResId, DEFAULT_SERVICE_CHECK_PREDICATE);
- }
-
- public ServiceWatcher(Context context, Handler handler, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
- this(context, handler, action, onBind, onUnbind, enableOverlayResId, nonOverlayPackageResId,
- DEFAULT_SERVICE_CHECK_PREDICATE);
- }
-
- public ServiceWatcher(Context context, Handler handler, String action,
- @Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
- @BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
- @NonNull Predicate<ResolveInfo> serviceCheckPredicate) {
- mContext = context;
- mHandler = handler;
- mIntent = new Intent(Objects.requireNonNull(action));
- mServiceCheckPredicate = Objects.requireNonNull(serviceCheckPredicate);
-
- Resources resources = context.getResources();
- boolean enableOverlay = resources.getBoolean(enableOverlayResId);
- if (!enableOverlay) {
- mIntent.setPackage(resources.getString(nonOverlayPackageResId));
- }
-
- mOnBind = onBind;
- mOnUnbind = onUnbind;
-
- mCurrentUserId = UserHandle.USER_NULL;
-
- mTargetService = BoundService.NONE;
- mBinder = null;
- }
-
- /**
- * Returns true if there is at least one component that could satisfy the ServiceWatcher's
- * constraints.
- */
- public boolean checkServiceResolves() {
- List<ResolveInfo> resolveInfos = mContext.getPackageManager()
- .queryIntentServicesAsUser(mIntent,
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
- UserHandle.USER_SYSTEM);
- for (ResolveInfo resolveInfo : resolveInfos) {
- if (mServiceCheckPredicate.test(resolveInfo)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Starts the process of determining the best matching service and maintaining a binding to it.
- */
- public void register() {
- mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal,
- ServiceWatcher.this));
- }
-
- private void registerInternal() {
- Preconditions.checkState(!mRegistered);
-
- mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
- intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null,
- mHandler);
-
- // TODO: This makes the behavior of the class unpredictable as the caller needs
- // to know the internal impl detail that calling register would pick the current user.
- mCurrentUserId = ActivityManager.getCurrentUser();
-
- mRegistered = true;
-
- mHandler.post(() -> onBestServiceChanged(false));
- }
-
- /**
- * Stops the process of determining the best matching service and releases any binding.
- */
- public void unregister() {
- mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal,
- ServiceWatcher.this));
- }
-
- private void unregisterInternal() {
- Preconditions.checkState(mRegistered);
-
- mRegistered = false;
-
- mPackageMonitor.unregister();
- mContext.unregisterReceiver(mBroadcastReceiver);
-
- mHandler.post(() -> onBestServiceChanged(false));
- }
-
- private void onBestServiceChanged(boolean forceRebind) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- BoundService bestServiceInfo = BoundService.NONE;
-
- if (mRegistered) {
- List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
- mIntent,
- GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
- mCurrentUserId);
- for (ResolveInfo resolveInfo : resolveInfos) {
- if (!mServiceCheckPredicate.test(resolveInfo)) {
- continue;
- }
- BoundService serviceInfo = new BoundService(resolveInfo);
- if (serviceInfo.compareTo(bestServiceInfo) > 0) {
- bestServiceInfo = serviceInfo;
- }
- }
- }
-
- if (forceRebind || !bestServiceInfo.equals(mTargetService)) {
- rebind(bestServiceInfo);
- }
- }
-
- private void rebind(BoundService newServiceInfo) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (!mTargetService.equals(BoundService.NONE)) {
- if (D) {
- Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
- }
-
- mContext.unbindService(this);
- onServiceDisconnected(mTargetService.component);
- mPendingBinds.remove(mTargetService.component);
- mTargetService = BoundService.NONE;
- }
-
- mTargetService = newServiceInfo;
- if (mTargetService.equals(BoundService.NONE)) {
- return;
- }
-
- Preconditions.checkState(mTargetService.component != null);
-
- Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
-
- Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
- if (!mContext.bindServiceAsUser(bindIntent, this,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
- mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) {
- mTargetService = BoundService.NONE;
- Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later");
- mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS);
- } else {
- mPendingBinds.put(mTargetService.component, mTargetService);
- }
- }
-
- @Override
- public final void onServiceConnected(ComponentName component, IBinder binder) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- Preconditions.checkState(mBinder == null);
-
- if (D) {
- Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
- }
-
- final BoundService boundService = mPendingBinds.remove(component);
- if (boundService == null) {
- return;
- }
-
- mBinder = binder;
- if (mOnBind != null) {
- try {
- mOnBind.run(binder, boundService);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, getLogPrefix() + " exception running on " + component, e);
- }
- }
- }
-
- @Override
- public final void onServiceDisconnected(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- if (mBinder == null) {
- return;
- }
-
- if (D) {
- Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
- }
-
- mBinder = null;
- if (mOnUnbind != null) {
- mOnUnbind.run();
- }
- }
-
- @Override
- public final void onBindingDied(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-
- Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
-
- onBestServiceChanged(true);
- }
-
- @Override
- public final void onNullBinding(ComponentName component) {
- Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
- }
-
- void onUserSwitched(@UserIdInt int userId) {
- mCurrentUserId = userId;
- onBestServiceChanged(false);
- }
-
- void onUserUnlocked(@UserIdInt int userId) {
- if (userId == mCurrentUserId) {
- onBestServiceChanged(false);
- }
- }
-
/**
- * Runs the given function asynchronously if and only if currently connected. Suppresses any
- * RemoteException thrown during execution.
+ * Creates a new ServiceWatcher instance.
*/
- public final void runOnBinder(BinderRunner runner) {
- mHandler.post(() -> {
- if (mBinder == null) {
- runner.onError();
- return;
- }
-
- try {
- runner.run(mBinder);
- } catch (RuntimeException | RemoteException e) {
- // binders may propagate some specific non-RemoteExceptions from the other side
- // through the binder as well - we cannot allow those to crash the system server
- Log.e(TAG, getLogPrefix() + " exception running on " + mTargetService, e);
- runner.onError();
- }
- });
- }
-
- private String getLogPrefix() {
- return "[" + mIntent.getAction() + "]";
- }
-
- @Override
- public String toString() {
- return mTargetService.toString();
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+ Context context,
+ String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return create(context, FgThread.getHandler(), tag, serviceSupplier, serviceListener);
}
/**
- * Dump for debugging.
+ * Creates a new ServiceWatcher instance that runs on the given handler.
*/
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("target service=" + mTargetService);
- pw.println("connected=" + (mBinder != null));
+ static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
+ Context context,
+ Handler handler,
+ String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ @Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ return new ServiceWatcherImpl<>(context, handler, tag, serviceSupplier, serviceListener);
}
-}
+
+ /**
+ * Returns true if there is at least one service that the ServiceWatcher could hypothetically
+ * bind to, as selected by the {@link ServiceSupplier}.
+ */
+ boolean checkServiceResolves();
+
+ /**
+ * Registers the ServiceWatcher, so that it will begin maintaining an active binding to the
+ * service selected by {@link ServiceSupplier}, until {@link #unregister()} is called.
+ */
+ void register();
+
+ /**
+ * Unregisters the ServiceWatcher, so that it will release any active bindings. If the
+ * ServiceWatcher is currently bound, this will result in one final
+ * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
+ * (but which is guaranteed to occur before any further
+ * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
+ * call to {@link #register()}).
+ */
+ void unregister();
+
+ /**
+ * Runs the given binder operation on the currently bound service (if available). The operation
+ * will always fail if the ServiceWatcher is not currently registered.
+ */
+ void runOnBinder(BinderOperation operation);
+
+ /**
+ * Dumps ServiceWatcher information.
+ */
+ void dump(PrintWriter pw);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
new file mode 100644
index 0000000..e718ba3
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -0,0 +1,307 @@
+/*
+ * 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.servicewatcher;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.content.Context.BIND_NOT_VISIBLE;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.Preconditions;
+import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows
+ * us to store the generic relationship between the service supplier and the service listener, while
+ * hiding the generics from clients, simplifying the API.
+ */
+class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher,
+ ServiceChangedListener {
+
+ static final String TAG = "ServiceWatcher";
+ static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final long RETRY_DELAY_MS = 15 * 1000;
+
+ final Context mContext;
+ final Handler mHandler;
+ final String mTag;
+ final ServiceSupplier<TBoundServiceInfo> mServiceSupplier;
+ final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
+
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ return true;
+ }
+
+ @Override
+ public void onSomePackagesChanged() {
+ onServiceChanged(false);
+ }
+ };
+
+ @GuardedBy("this")
+ private boolean mRegistered = false;
+ @GuardedBy("this")
+ private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
+
+ ServiceWatcherImpl(Context context, Handler handler, String tag,
+ ServiceSupplier<TBoundServiceInfo> serviceSupplier,
+ ServiceListener<? super TBoundServiceInfo> serviceListener) {
+ mContext = context;
+ mHandler = handler;
+ mTag = tag;
+ mServiceSupplier = serviceSupplier;
+ mServiceListener = serviceListener;
+ }
+
+ @Override
+ public boolean checkServiceResolves() {
+ return mServiceSupplier.hasMatchingService();
+ }
+
+ @Override
+ public synchronized void register() {
+ Preconditions.checkState(!mRegistered);
+
+ mRegistered = true;
+ mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler);
+ mServiceSupplier.register(this);
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void unregister() {
+ Preconditions.checkState(mRegistered);
+
+ mServiceSupplier.unregister();
+ mPackageMonitor.unregister();
+ mRegistered = false;
+
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void onServiceChanged() {
+ onServiceChanged(false);
+ }
+
+ @Override
+ public synchronized void runOnBinder(BinderOperation operation) {
+ MyServiceConnection serviceConnection = mServiceConnection;
+ mHandler.post(() -> serviceConnection.runOnBinder(operation));
+ }
+
+ synchronized void onServiceChanged(boolean forceRebind) {
+ TBoundServiceInfo newBoundServiceInfo;
+ if (mRegistered) {
+ newBoundServiceInfo = mServiceSupplier.getServiceInfo();
+ } else {
+ newBoundServiceInfo = null;
+ }
+
+ if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
+ newBoundServiceInfo)) {
+ MyServiceConnection oldServiceConnection = mServiceConnection;
+ MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
+ mServiceConnection = newServiceConnection;
+ mHandler.post(() -> {
+ oldServiceConnection.unbind();
+ newServiceConnection.bind();
+ });
+ }
+ }
+
+ @Override
+ public String toString() {
+ MyServiceConnection serviceConnection;
+ synchronized (this) {
+ serviceConnection = mServiceConnection;
+ }
+
+ return serviceConnection.getBoundServiceInfo().toString();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ 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
+ private class MyServiceConnection implements ServiceConnection {
+
+ private final @Nullable TBoundServiceInfo mBoundServiceInfo;
+
+ // volatile so that isConnected can be called from any thread easily
+ private volatile @Nullable IBinder mBinder;
+ private @Nullable Runnable mRebinder;
+
+ MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
+ mBoundServiceInfo = boundServiceInfo;
+ }
+
+ // may be called from any thread
+ @Nullable TBoundServiceInfo getBoundServiceInfo() {
+ return mBoundServiceInfo;
+ }
+
+ // may be called from any thread
+ boolean isConnected() {
+ return mBinder != null;
+ }
+
+ void bind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ Log.i(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
+
+ Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
+ mBoundServiceInfo.getComponentName());
+ if (!mContext.bindServiceAsUser(bindIntent, this,
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+ mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
+ Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
+ mRebinder = this::bind;
+ mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
+ } else {
+ mRebinder = null;
+ }
+ }
+
+ void unbind() {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBoundServiceInfo == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
+ }
+
+ if (mRebinder != null) {
+ mHandler.removeCallbacks(mRebinder);
+ mRebinder = null;
+ } else {
+ mContext.unbindService(this);
+ }
+
+ onServiceDisconnected(mBoundServiceInfo.getComponentName());
+ }
+
+ void runOnBinder(BinderOperation operation) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ operation.onError();
+ return;
+ }
+
+ try {
+ operation.run(mBinder);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ operation.onError();
+ }
+ }
+
+ @Override
+ public final void onServiceConnected(ComponentName component, IBinder binder) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+ Preconditions.checkState(mBinder == null);
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] connected to " + component.toShortString());
+ }
+
+ mBinder = binder;
+
+ if (mServiceListener != null) {
+ try {
+ mServiceListener.onBind(binder, mBoundServiceInfo);
+ } catch (RuntimeException | RemoteException e) {
+ // binders may propagate some specific non-RemoteExceptions from the other side
+ // through the binder as well - we cannot allow those to crash the system server
+ Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
+ }
+ }
+ }
+
+ @Override
+ public final void onServiceDisconnected(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ if (mBinder == null) {
+ return;
+ }
+
+ if (D) {
+ Log.d(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
+ }
+
+ mBinder = null;
+ if (mServiceListener != null) {
+ mServiceListener.onUnbind();
+ }
+ }
+
+ @Override
+ public final void onBindingDied(ComponentName component) {
+ Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
+
+ Log.i(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
+
+ onServiceChanged(true);
+ }
+
+ @Override
+ public final void onNullBinding(ComponentName component) {
+ Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index dedb3ac..ff6c2f5 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.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 @@
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 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 @@
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 @@
return;
}
String sessionId = vol.getId();
- int userId = vol.getMountUserId();
+ int userId = getConnectionUserIdForVolume(vol);
StorageUserConnection connection = null;
synchronized (mLock) {
@@ -191,7 +218,7 @@
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 a0e2286..0b11b0b 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 @@
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 eefa045a..27e2ee5 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 @@
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/timezone/TimeZoneUpdateIdler.java b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
index a7767a4..23e3eba 100644
--- a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
+++ b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
@@ -16,8 +16,6 @@
package com.android.server.timezone;
-import com.android.server.LocalServices;
-
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -26,6 +24,8 @@
import android.content.Context;
import android.util.Slog;
+import com.android.server.LocalServices;
+
/**
* A JobService used to trigger time zone rules update work when a device falls idle.
*/
@@ -55,7 +55,7 @@
@Override
public boolean onStopJob(JobParameters params) {
// Reschedule if stopped unless it was cancelled due to unschedule().
- boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+ boolean reschedule = params.getStopReason() != JobParameters.STOP_REASON_CANCELLED_BY_APP;
Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
return reschedule;
}
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 2452c8d..222e852 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.net.ConnectivityManager;
import android.os.SystemProperties;
import android.util.ArraySet;
@@ -124,9 +124,7 @@
* device.
*/
public boolean isTelephonyTimeZoneDetectionFeatureSupported() {
- // TODO b/150583524 Avoid the use of a deprecated API.
- return mContext.getSystemService(ConnectivityManager.class)
- .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
}
/**
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index 4fa920e..f054c57 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -47,7 +47,7 @@
@NonNull String providerName,
@NonNull LocationTimeZoneProviderProxy proxy) {
super(providerMetricsLogger, threadingDomain, providerName,
- new ZoneInfoDbTimeZoneIdValidator());
+ new ZoneInfoDbTimeZoneProviderEventPreProcessor());
mProxy = Objects.requireNonNull(proxy);
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index cc815dc6..e116a87 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -20,7 +20,6 @@
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
@@ -86,18 +85,6 @@
}
/**
- * Used by {@link LocationTimeZoneProvider} to check if time zone IDs are understood
- * by the platform.
- */
- interface TimeZoneIdValidator {
-
- /**
- * Returns whether {@code timeZoneId} is supported by the platform or not.
- */
- boolean isValid(@NonNull String timeZoneId);
- }
-
- /**
* Listener interface used to log provider events for metrics.
*/
interface ProviderMetricsLogger {
@@ -386,19 +373,20 @@
// Non-null and effectively final after initialize() is called.
ProviderListener mProviderListener;
- @NonNull private TimeZoneIdValidator mTimeZoneIdValidator;
+ @NonNull private final TimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
/** Creates the instance. */
LocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
- @NonNull TimeZoneIdValidator timeZoneIdValidator) {
+ @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
mProviderMetricsLogger = Objects.requireNonNull(providerMetricsLogger);
mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
mSharedLock = threadingDomain.getLockObject();
mProviderName = Objects.requireNonNull(providerName);
- mTimeZoneIdValidator = Objects.requireNonNull(timeZoneIdValidator);
+ mTimeZoneProviderEventPreProcessor =
+ Objects.requireNonNull(timeZoneProviderEventPreProcessor);
}
/**
@@ -639,24 +627,8 @@
mThreadingDomain.assertCurrentThread();
Objects.requireNonNull(timeZoneProviderEvent);
- // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
- // the device's time zone. This logic prevents bad time zone IDs entering the time zone
- // detection logic from third party code.
- //
- // An event containing an unknown time zone ID could occur if the provider is using a
- // different TZDB version than the device. Provider developers are expected to take steps to
- // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
- // rules, or providing IDs based on the device's TZDB version, so this is not considered a
- // common case.
- //
- // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
- // enables immediate failover to a secondary provider, one that might provide valid IDs for
- // the same location, which should provide better behavior than just ignoring the event.
- if (hasInvalidTimeZones(timeZoneProviderEvent)) {
- infoLog("event=" + timeZoneProviderEvent + " has unsupported time zones. "
- + "Replacing it with uncertain event.");
- timeZoneProviderEvent = TimeZoneProviderEvent.createUncertainEvent();
- }
+ timeZoneProviderEvent =
+ mTimeZoneProviderEventPreProcessor.preProcess(timeZoneProviderEvent);
synchronized (mSharedLock) {
debugLog("handleTimeZoneProviderEvent: mProviderName=" + mProviderName
@@ -755,20 +727,6 @@
}
}
- private boolean hasInvalidTimeZones(@NonNull TimeZoneProviderEvent event) {
- if (event.getSuggestion() == null) {
- return false;
- }
-
- for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
- if (!mTimeZoneIdValidator.isValid(timeZone)) {
- return true;
- }
- }
-
- return false;
- }
-
@GuardedBy("mSharedLock")
private void assertIsStarted() {
ProviderState currentState = mCurrentState.get();
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
index 0b51488..6c3f016 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
@@ -16,19 +16,14 @@
package com.android.server.timezonedetector.location;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
+import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
-import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
-
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -39,11 +34,12 @@
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
-import com.android.server.servicewatcher.ServiceWatcher.BoundService;
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
-import java.util.function.Predicate;
/**
* System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the
@@ -52,7 +48,8 @@
* different process. As "remote" providers are bound / unbound this proxy will rebind to the "best"
* available remote process.
*/
-class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
+class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy implements
+ ServiceListener<BoundServiceInfo> {
@NonNull private final ServiceWatcher mServiceWatcher;
@@ -69,38 +66,13 @@
super(context, threadingDomain);
mManagerProxy = null;
mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
-
- // A predicate that is used to confirm that an intent service can be used as a
- // location-based TimeZoneProvider. The service must:
- // 1) Declare android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE" - this
- // ensures that the provider will only communicate with the system server.
- // 2) Be in an application that has been granted the
- // android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE permission. This
- // ensures only trusted time zone providers will be discovered.
- final String requiredClientPermission = Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
- final String requiredPermission =
- Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
- Predicate<ResolveInfo> intentServiceCheckPredicate = resolveInfo -> {
- ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-
- boolean hasClientPermissionRequirement =
- requiredClientPermission.equals(serviceInfo.permission);
-
- String packageName = serviceInfo.packageName;
- PackageManager packageManager = context.getPackageManager();
- int checkResult = packageManager.checkPermission(requiredPermission, packageName);
- boolean hasRequiredPermission = checkResult == PERMISSION_GRANTED;
-
- boolean result = hasClientPermissionRequirement && hasRequiredPermission;
- if (!result) {
- warnLog("resolveInfo=" + resolveInfo + " does not meet requirements:"
- + " hasClientPermissionRequirement=" + hasClientPermissionRequirement
- + ", hasRequiredPermission=" + hasRequiredPermission);
- }
- return result;
- };
- mServiceWatcher = new ServiceWatcher(context, handler, action, this::onBind, this::onUnbind,
- enableOverlayResId, nonOverlayPackageResId, intentServiceCheckPredicate);
+ mServiceWatcher = ServiceWatcher.create(context,
+ handler,
+ "RealLocationTimeZoneProviderProxy",
+ new CurrentUserServiceSupplier(context, action, enableOverlayResId,
+ nonOverlayPackageResId, BIND_TIME_ZONE_PROVIDER_SERVICE,
+ INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE),
+ this);
}
@Override
@@ -123,7 +95,8 @@
return resolves;
}
- private void onBind(IBinder binder, BoundService boundService) {
+ @Override
+ public void onBind(IBinder binder, BoundServiceInfo boundService) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -138,7 +111,8 @@
}
}
- private void onUnbind() {
+ @Override
+ public void onUnbind() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -199,7 +173,7 @@
synchronized (mSharedLock) {
ipw.println("{RealLocationTimeZoneProviderProxy}");
ipw.println("mRequest=" + mRequest);
- mServiceWatcher.dump(null, ipw, args);
+ mServiceWatcher.dump(ipw);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
similarity index 64%
rename from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
rename to services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
index cab5ad2..951e9d0 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEventPreProcessor.java
@@ -18,13 +18,16 @@
import android.annotation.NonNull;
-import com.android.i18n.timezone.ZoneInfoDb;
+/**
+ * Used by {@link LocationTimeZoneProvider} to ensure that all time zone IDs are understood by the
+ * platform.
+ */
+public interface TimeZoneProviderEventPreProcessor {
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
+ /**
+ * May return uncertain event if {@code timeZoneProviderEvent} is ill-formed or drop/rewrite
+ * time zone IDs.
+ */
+ TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent timeZoneProviderEvent);
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..0f4367d
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.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 com.android.server.timezonedetector.location;
+
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.infoLog;
+
+import android.annotation.NonNull;
+
+import com.android.i18n.timezone.ZoneInfoDb;
+
+/**
+ * {@link TimeZoneProviderEventPreProcessor} implementation which makes validations against
+ * {@link ZoneInfoDb}.
+ */
+public class ZoneInfoDbTimeZoneProviderEventPreProcessor
+ implements TimeZoneProviderEventPreProcessor {
+
+ /**
+ * Returns uncertain event if {@code event} has at least one unsupported time zone ID.
+ */
+ @Override
+ public TimeZoneProviderEvent preProcess(@NonNull TimeZoneProviderEvent event) {
+ if (event.getSuggestion() == null || event.getSuggestion().getTimeZoneIds().isEmpty()) {
+ return event;
+ }
+
+ // If the provider has made a suggestion with unknown time zone IDs it cannot be used to set
+ // the device's time zone. This logic prevents bad time zone IDs entering the time zone
+ // detection logic from third party code.
+ //
+ // An event containing an unknown time zone ID could occur if the provider is using a
+ // different TZDB version than the device. Provider developers are expected to take steps to
+ // avoid version skew problem, e.g. by ensuring atomic updates with the platform time zone
+ // rules, or providing IDs based on the device's TZDB version, so this is not considered a
+ // common case.
+ //
+ // Treating a suggestion containing unknown time zone IDs as "uncertain" in the primary
+ // enables immediate failover to a secondary provider, one that might provide valid IDs for
+ // the same location, which should provide better behavior than just ignoring the event.
+ if (hasInvalidZones(event)) {
+ return TimeZoneProviderEvent.createUncertainEvent();
+ }
+
+ return event;
+ }
+
+ private static boolean hasInvalidZones(TimeZoneProviderEvent event) {
+ for (String timeZone : event.getSuggestion().getTimeZoneIds()) {
+ if (!ZoneInfoDb.getInstance().hasTimeZone(timeZone)) {
+ infoLog("event=" + event + " has unsupported zone(" + timeZone + ")");
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 3f74938..89ed956 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.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 @@
@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 @@
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 @@
@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 @@
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();
- if (!mIsActive.getAndSet(true)) {
+ // 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();
+ }
+ }
+ }
+
+ // 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 @@
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 @@
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 0000000..953837a
--- /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 e84ee672..cd84058 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 @@
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 @@
}
/**
- * 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)));
}
- mEffect = newEffect;
+ }
+
+ /**
+ * 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;
+ }
}
/** Return true is current status is different from {@link Status#RUNNING}. */
@@ -272,57 +333,62 @@
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, StepSegment segment) {
+ final long token = proto.start(fieldId);
+ 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.Waveform effect) {
+ PrebakedSegment 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(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.Prebaked effect) {
+ PrimitiveSegment 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.end(token);
- }
-
- private void dumpEffect(ProtoOutputStream proto, long fieldId,
- VibrationEffect.Composed effect) {
- 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/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to services/core/java/com/android/server/vibrator/VibrationEffectModifier.java
index cab5ad2..d287c8f 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/services/core/java/com/android/server/vibrator/VibrationEffectModifier.java
@@ -14,17 +14,13 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+package com.android.server.vibrator;
-import android.annotation.NonNull;
+import android.os.VibrationEffect;
-import com.android.i18n.timezone.ZoneInfoDb;
+/** Function that applies a generic modifier to a {@link VibrationEffect}. */
+interface VibrationEffectModifier<T> {
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
+ /** 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 10393f6..f481772 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 @@
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 @@
}
/**
- * 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 @@
* 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 @@
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 b90408f..3090e6d 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.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 @@
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 @@
* 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 @@
}
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 @@
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 @@
/** 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);
+ VibrationEffect.Composed effect, long expectedVibratorStopTime) {
+ this(startTime, controller, effect, /* index= */ 0, expectedVibratorStopTime);
}
VibratorAmplitudeStep(long startTime, VibratorController controller,
- VibrationEffect.Waveform waveform, long expectedVibratorStopTime) {
- this(startTime, controller, waveform, /* 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 @@
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 @@
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 @@
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 @@
@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 >= effect.getSegments().size()) {
+ nextIndex = effect.getRepeatIndex();
}
- if (nextIndex < 0) {
- return Arrays.asList(new VibratorOffStep(nextStartTime, controller));
- }
- 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 @@
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 @@
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 @@
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 e3dc70b..c897702 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.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 @@
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 @@
* 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 @@
*
* @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 @@
}
/**
- * 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 @@
}
}
+ /**
+ * 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 @@
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 @@
}
/** 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 @@
}
/** 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 c9751bb..5fd1d7a 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.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 @@
}
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 @@
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 @@
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 @@
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 @@
* 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 @@
@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 @@
// 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 @@
}
}
+ @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 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/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index e6d37b6..b947c88 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1511,10 +1511,15 @@
IBinder topFocusedWindowToken = null;
synchronized (mService.mGlobalLock) {
- // Do not send the windows if there is no top focus as
- // the window manager is still looking for where to put it.
- // We will do the work when we get a focus change callback.
- final WindowState topFocusedWindowState = getTopFocusWindow();
+ // If there is a recents animation running, then use the animation target as the
+ // top window state. Otherwise,do not send the windows if there is no top focus as
+ // the window manager is still looking for where to put it. We will do the work when
+ // we get a focus change callback.
+ final RecentsAnimationController controller =
+ mService.getRecentsAnimationController();
+ final WindowState topFocusedWindowState = controller != null
+ ? controller.getTargetAppMainWindow()
+ : getTopFocusWindow();
if (topFocusedWindowState == null) {
if (DEBUG) {
Slog.d(LOG_TAG, "top focused window is null, compute it again later");
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 0000000..054044b
--- /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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 3a0eb39..dc69175 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -287,6 +287,11 @@
if (mLastLaunchedActivity == r) {
return;
}
+ if (mLastLaunchedActivity != null) {
+ // Transfer the launch cookie because it is a consecutive launch event.
+ r.mLaunchCookie = mLastLaunchedActivity.mLaunchCookie;
+ mLastLaunchedActivity.mLaunchCookie = null;
+ }
mLastLaunchedActivity = r;
if (!r.noDisplay && !r.isReportedDrawn()) {
if (DEBUG_METRICS) Slog.i(TAG, "Add pending draw " + r);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7c23661..eadfbe2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6790,7 +6790,12 @@
// The app bounds hasn't been computed yet.
return false;
}
- final Configuration parentConfig = getParent().getConfiguration();
+ final WindowContainer parent = getParent();
+ if (parent == null) {
+ // The parent of detached Activity can be null.
+ return false;
+ }
+ final Configuration parentConfig = parent.getConfiguration();
// Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
// fields should be changed with density and bounds, so here only compares the most
// significant field.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 060323c..c09136e 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.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 @@
* 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.
@@ -607,4 +606,10 @@
*/
void commit() throws RemoteException;
}
+
+ /**
+ * A utility method to check AppOps and PackageManager for SYSTEM_ALERT_WINDOW permission.
+ */
+ public abstract boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+ String callingPackage);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 09f5c93..c8fa50c 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.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;
@@ -251,6 +250,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto;
import com.android.server.am.AppTimeTracker;
+import com.android.server.am.AssistDataRequester;
import com.android.server.am.BaseErrorDialog;
import com.android.server.am.PendingIntentController;
import com.android.server.am.PendingIntentRecord;
@@ -933,7 +933,8 @@
return getUserManager().hasUserRestriction(restriction, userId);
}
- boolean hasSystemAlertWindowPermission(int callingUid, int callingPid, String callingPackage) {
+ boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+ String callingPackage) {
final int mode = getAppOpsManager().noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
callingUid, callingPackage, /* featureId */ null, "");
if (mode == AppOpsManager.MODE_DEFAULT) {
@@ -2827,10 +2828,45 @@
@Override
public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
- Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
+ Bundle receiverExtras, IBinder activityToken, boolean checkActivityIsTop,
+ boolean newSessionId) {
return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
- activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
- PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+ activityToken, checkActivityIsTop, newSessionId, UserHandle.getCallingUserId(),
+ null, PENDING_ASSIST_EXTRAS_LONG_TIMEOUT, 0) != null;
+ }
+
+ @Override
+ public boolean requestAssistDataForTask(IAssistDataReceiver receiver, int taskId,
+ String callingPackageName) {
+ mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
+ "requestAssistDataForTask()");
+ final long callingId = Binder.clearCallingIdentity();
+ LocalService.ActivityTokens tokens = null;
+ try {
+ tokens = mInternal.getTopActivityForTask(taskId);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ if (tokens == null) {
+ Log.e(TAG, "Could not find activity for task " + taskId);
+ return false;
+ }
+
+ final AssistDataReceiverProxy proxy =
+ new AssistDataReceiverProxy(receiver, callingPackageName);
+ Object lock = new Object();
+ AssistDataRequester requester = new AssistDataRequester(mContext, mWindowManager,
+ getAppOpsManager(), proxy, lock, AppOpsManager.OP_ASSIST_STRUCTURE,
+ AppOpsManager.OP_NONE);
+
+ List<IBinder> topActivityToken = new ArrayList<>();
+ topActivityToken.add(tokens.getActivityToken());
+ requester.requestAssistData(topActivityToken, true /* fetchData */,
+ false /* fetchScreenshot */, true /* allowFetchData */,
+ false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */,
+ Binder.getCallingUid(), callingPackageName);
+
+ return true;
}
@Override
@@ -2844,7 +2880,7 @@
@Override
public Bundle getAssistContextExtras(int requestType) {
PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
- null, null, true /* focused */, true /* newSessionId */,
+ null, null, true /* checkActivityIsTop */, true /* newSessionId */,
UserHandle.getCallingUserId(), null, PENDING_ASSIST_EXTRAS_TIMEOUT, 0);
if (pae == null) {
return null;
@@ -3047,8 +3083,8 @@
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
- boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
- int flags) {
+ boolean checkActivityIsTop, boolean newSessionId, int userHandle, Bundle args,
+ long timeout, int flags) {
mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"enqueueAssistContext()");
@@ -3064,7 +3100,7 @@
Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity);
return null;
}
- if (focused) {
+ if (checkActivityIsTop) {
if (activityToken != null) {
ActivityRecord caller = ActivityRecord.forTokenLocked(activityToken);
if (activity != caller) {
@@ -5075,7 +5111,7 @@
}
@Override
- public List<Pair<IBinder, Integer>> getTopVisibleActivities() {
+ public List<ActivityAssistInfo> getTopVisibleActivities() {
synchronized (mGlobalLock) {
return mRootWindowContainer.getTopVisibleActivities();
}
@@ -6370,6 +6406,13 @@
return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
}
}
+
+ @Override
+ public boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
+ String callingPackage) {
+ return ActivityTaskManagerService.this.hasSystemAlertWindowPermission(callingUid,
+ callingPid, callingPackage);
+ }
}
final class PackageConfigurationUpdaterImpl implements
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6d24105..a9d33dc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -516,7 +516,7 @@
/** The delay to avoid toggling the animation quickly. */
private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
- private FixedRotationAnimationController mFixedRotationAnimationController;
+ private FadeRotationAnimationController mFadeRotationAnimationController;
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1590,8 +1590,8 @@
}
@VisibleForTesting
- @Nullable FixedRotationAnimationController getFixedRotationAnimationController() {
- return mFixedRotationAnimationController;
+ @Nullable FadeRotationAnimationController getFadeRotationAnimationController() {
+ return mFadeRotationAnimationController;
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1601,13 +1601,13 @@
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
if (mFixedRotationLaunchingApp == null && r != null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
- startFixedRotationAnimation(
+ startFadeRotationAnimation(
// Delay the hide animation to avoid blinking by clicking navigation bar that
// may toggle fixed rotation in a short time.
r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
- finishFixedRotationAnimationIfPossible();
+ finishFadeRotationAnimationIfPossible();
}
mFixedRotationLaunchingApp = r;
}
@@ -1714,12 +1714,12 @@
*
* @return {@code true} if the animation is executed right now.
*/
- private boolean startFixedRotationAnimation(boolean shouldDebounce) {
+ private boolean startFadeRotationAnimation(boolean shouldDebounce) {
if (shouldDebounce) {
mWmService.mH.postDelayed(() -> {
synchronized (mWmService.mGlobalLock) {
if (mFixedRotationLaunchingApp != null
- && startFixedRotationAnimation(false /* shouldDebounce */)) {
+ && startFadeRotationAnimation(false /* shouldDebounce */)) {
// Apply the transaction so the animation leash can take effect immediately.
getPendingTransaction().apply();
}
@@ -1727,23 +1727,41 @@
}, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
return false;
}
- if (mFixedRotationAnimationController == null) {
- mFixedRotationAnimationController = new FixedRotationAnimationController(this);
- mFixedRotationAnimationController.hide();
+ if (mFadeRotationAnimationController == null) {
+ mFadeRotationAnimationController = new FadeRotationAnimationController(this);
+ mFadeRotationAnimationController.hide();
return true;
}
return false;
}
/** Re-show the previously hidden windows if all seamless rotated windows are done. */
- void finishFixedRotationAnimationIfPossible() {
- final FixedRotationAnimationController controller = mFixedRotationAnimationController;
+ void finishFadeRotationAnimationIfPossible() {
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
controller.show();
- mFixedRotationAnimationController = null;
+ mFadeRotationAnimationController = null;
}
}
+ /** Shows the given window which may be hidden for screen frozen. */
+ void finishFadeRotationAnimation(WindowState w) {
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ if (controller != null && controller.show(w.mToken)) {
+ mFadeRotationAnimationController = null;
+ }
+ }
+
+ /** Returns {@code true} if the display should wait for the given window to stop freezing. */
+ boolean waitForUnfreeze(WindowState w) {
+ if (w.mForceSeamlesslyRotate) {
+ // The window should look no different before and after rotation.
+ return false;
+ }
+ final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+ return controller == null || !controller.isTargetToken(w.mToken);
+ }
+
void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
if (mFixedRotationLaunchingApp != null) {
// The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2964,6 +2982,13 @@
mScreenRotationAnimation.kill();
}
mScreenRotationAnimation = screenRotationAnimation;
+
+ // Hide the windows which are not significant in rotation animation. So that the windows
+ // don't need to block the unfreeze time.
+ if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()
+ && mFadeRotationAnimationController == null) {
+ startFadeRotationAnimation(false /* shouldDebounce */);
+ }
}
public ScreenRotationAnimation getRotationAnimation() {
@@ -3699,10 +3724,15 @@
// 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/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d0e4c40..6046cc6 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -626,7 +626,7 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimationIfPossible();
+ mDisplayContent.finishFadeRotationAnimationIfPossible();
}
private void prepareSeamlessRotation() {
@@ -717,7 +717,7 @@
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimationIfPossible();
+ mDisplayContent.finishFadeRotationAnimationIfPossible();
updateRotationAndSendNewConfigIfChanged();
}
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
new file mode 100644
index 0000000..5ee6928
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -0,0 +1,135 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * Controller to fade out and in windows when the display is changing rotation. It can be used for
+ * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
+ * the windows until they are drawn with the new rotation.
+ */
+public class FadeRotationAnimationController extends FadeAnimationController {
+
+ private final ArrayList<WindowToken> mTargetWindowTokens = new ArrayList<>();
+ private final WindowManagerService mService;
+ /** If non-null, it usually indicates that there will be a screen rotation animation. */
+ private final Runnable mFrozenTimeoutRunnable;
+ private final WindowToken mNavBarToken;
+
+ public FadeRotationAnimationController(DisplayContent displayContent) {
+ super(displayContent);
+ mService = displayContent.mWmService;
+ mFrozenTimeoutRunnable = mService.mDisplayFrozen ? () -> {
+ synchronized (mService.mGlobalLock) {
+ displayContent.finishFadeRotationAnimationIfPossible();
+ mService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+ } : null;
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ final WindowState navigationBar = displayPolicy.getNavigationBar();
+ if (navigationBar != null) {
+ mNavBarToken = navigationBar.mToken;
+ final RecentsAnimationController controller = mService.getRecentsAnimationController();
+ final boolean navBarControlledByRecents =
+ controller != null && controller.isNavigationBarAttachedToApp();
+ // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation
+ // bar is currently controlled by recents animation.
+ if (!displayPolicy.navigationBarCanMove() && !navBarControlledByRecents) {
+ mTargetWindowTokens.add(mNavBarToken);
+ }
+ } else {
+ mNavBarToken = null;
+ }
+ displayContent.forAllWindows(w -> {
+ if (w.mActivityRecord == null && w.mHasSurface && !w.mForceSeamlesslyRotate
+ && !w.mIsWallpaper && !w.mIsImWindow && w != navigationBar) {
+ mTargetWindowTokens.add(w.mToken);
+ }
+ }, true /* traverseTopToBottom */);
+ }
+
+ /** Applies show animation on the previously hidden window tokens. */
+ void show() {
+ for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+ final WindowToken windowToken = mTargetWindowTokens.get(i);
+ fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+ }
+ mTargetWindowTokens.clear();
+ if (mFrozenTimeoutRunnable != null) {
+ mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ }
+ }
+
+ /**
+ * Returns {@code true} if all target windows are shown. It only takes effects if this
+ * controller is created for normal rotation.
+ */
+ boolean show(WindowToken token) {
+ if (mFrozenTimeoutRunnable != null && mTargetWindowTokens.remove(token)) {
+ fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
+ if (mTargetWindowTokens.isEmpty()) {
+ mService.mH.removeCallbacks(mFrozenTimeoutRunnable);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
+ void hide() {
+ for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+ final WindowToken windowToken = mTargetWindowTokens.get(i);
+ fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+ }
+ if (mFrozenTimeoutRunnable != null) {
+ mService.mH.postDelayed(mFrozenTimeoutRunnable,
+ WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
+ }
+ }
+
+ /** Returns {@code true} if the window is handled by this controller. */
+ boolean isTargetToken(WindowToken token) {
+ return token == mNavBarToken || mTargetWindowTokens.contains(token);
+ }
+
+ @Override
+ public Animation getFadeInAnimation() {
+ if (mFrozenTimeoutRunnable != null) {
+ // Use a shorter animation so it is easier to align with screen rotation animation.
+ return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
+ }
+ return super.getFadeInAnimation();
+ }
+
+ @Override
+ public Animation getFadeOutAnimation() {
+ if (mFrozenTimeoutRunnable != null) {
+ // Hide the window immediately because screen should have been covered by screenshot.
+ return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
+ }
+ return super.getFadeOutAnimation();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
deleted file mode 100644
index aa73170..0000000
--- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
-
-import java.util.ArrayList;
-
-/**
- * Controller to fade out and in system ui when applying a fixed rotation transform to a window
- * token.
- *
- * The system bars will be fade out when the fixed rotation transform starts and will be fade in
- * once all surfaces have been rotated.
- */
-public class FixedRotationAnimationController extends FadeAnimationController {
-
- private final WindowState mStatusBar;
- private final WindowState mNavigationBar;
- private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2);
-
- public FixedRotationAnimationController(DisplayContent displayContent) {
- super(displayContent);
- final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
- mStatusBar = displayPolicy.getStatusBar();
-
- final RecentsAnimationController controller =
- displayContent.mWmService.getRecentsAnimationController();
- final boolean navBarControlledByRecents =
- controller != null && controller.isNavigationBarAttachedToApp();
- // Do not animate movable navigation bar (e.g. non-gesture mode) or when the navigation bar
- // is currently controlled by recents animation.
- mNavigationBar = !displayPolicy.navigationBarCanMove()
- && !navBarControlledByRecents ? displayPolicy.getNavigationBar() : null;
- }
-
- /** Applies show animation on the previously hidden window tokens. */
- void show() {
- for (int i = mAnimatedWindowToken.size() - 1; i >= 0; i--) {
- final WindowToken windowToken = mAnimatedWindowToken.get(i);
- fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- }
-
- /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
- void hide() {
- if (mNavigationBar != null) {
- fadeWindowToken(false /* show */, mNavigationBar.mToken,
- ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- if (mStatusBar != null) {
- fadeWindowToken(false /* show */, mStatusBar.mToken,
- ANIMATION_TYPE_FIXED_TRANSFORM);
- }
- }
-
- @Override
- public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
- super.fadeWindowToken(show, windowToken, animationType);
- mAnimatedWindowToken.add(windowToken);
- }
-}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 465042d..cda3fda 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -25,6 +25,7 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -458,10 +459,8 @@
InsetsPolicyAnimationControlCallbacks mControlCallbacks;
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
-
- super(show, false /* hasCallbacks */, types, false /* disable */,
- (int) (mDisplayContent.getDisplayMetrics().density * FLOATING_IME_BOTTOM_INSET
- + 0.5f));
+ super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+ false /* disable */, 0 /* floatingImeBottomInsets */);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
@@ -497,7 +496,7 @@
// the controlled types should be animated regardless of the frame.
mAnimationControl = new InsetsAnimationControlImpl(controls,
null /* frame */, state, mListener, typesReady, this,
- mListener.getDurationMs(), InsetsController.SYSTEM_BARS_INTERPOLATOR,
+ mListener.getDurationMs(), getInsetsInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, null /* translator */);
SurfaceAnimationThread.getHandler().post(
() -> mListener.onReady(mAnimationControl, typesReady));
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 38ad4f0..7b0fb01 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.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.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 @@
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 @@
// 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 @@
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 @@
}
}
- 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 @@
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 b31c2e4..20216c3 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 @@
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/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 4ab5cd6..b1e12b6 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -77,7 +77,7 @@
final boolean shouldAttachNavBarToApp =
displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
&& service.getRecentsAnimationController() == null
- && displayContent.getFixedRotationAnimationController() == null;
+ && displayContent.getFadeRotationAnimationController() == null;
if (shouldAttachNavBarToApp) {
startNavigationBarWindowAnimation(
displayContent, durationHint, statusBarTransitionDelay, targets,
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index d81181d..20c0d41 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 @@
} 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/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 64ff108..bd84854 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -568,8 +568,9 @@
? mMinimizedHomeBounds
: null;
final Rect contentInsets;
- if (mTargetActivityRecord != null && mTargetActivityRecord.findMainWindow() != null) {
- contentInsets = mTargetActivityRecord.findMainWindow()
+ final WindowState targetAppMainWindow = getTargetAppMainWindow();
+ if (targetAppMainWindow != null) {
+ contentInsets = targetAppMainWindow
.getInsetsStateWithVisibilityOverride()
.calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(),
false /* ignoreVisibility */);
@@ -607,8 +608,8 @@
private void attachNavigationBarToApp() {
if (!mShouldAttachNavBarToAppDuringTransition
- // Skip the case where the nav bar is controlled by fixed rotation.
- || mDisplayContent.getFixedRotationAnimationController() != null) {
+ // Skip the case where the nav bar is controlled by fade rotation.
+ || mDisplayContent.getFadeRotationAnimationController() != null) {
return;
}
ActivityRecord topActivity = null;
@@ -1004,9 +1005,7 @@
boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle) {
// Update the input consumer touchable region to match the target app main window
- final WindowState targetAppMainWindow = mTargetActivityRecord != null
- ? mTargetActivityRecord.findMainWindow()
- : null;
+ final WindowState targetAppMainWindow = getTargetAppMainWindow();
if (targetAppMainWindow != null) {
targetAppMainWindow.getBounds(mTmpRect);
inputWindowHandle.touchableRegion.set(mTmpRect);
@@ -1026,6 +1025,13 @@
return mTargetActivityRecord.windowsCanBeWallpaperTarget();
}
+ WindowState getTargetAppMainWindow() {
+ if (mTargetActivityRecord == null) {
+ return null;
+ }
+ return mTargetActivityRecord.findMainWindow();
+ }
+
boolean isAnimatingTask(Task task) {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
if (task == mPendingAnimations.get(i).mTask) {
diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java
index 505af05..cd20c82 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 @@
*/
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 857217f..7261048 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 @@
* @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 @@
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 8915eba..5af44317 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 @@
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 @@
});
}
+ /** 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 7c5afa8..7644cf2 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.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 @@
}
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 a65adbb..8b08314 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.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 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 9245f8c..ffd6d21 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 @@
}
/**
- * @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 @@
}
/**
+ * @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 57394d6..d494b75 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -433,11 +433,6 @@
public static boolean sEnableRemoteKeyguardAnimation =
SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
- private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY =
- "ro.sf.disable_triple_buffer";
-
- static boolean sEnableTripleBuffering = !SystemProperties.getBoolean(
- DISABLE_TRIPLE_BUFFERING_PROPERTY, false);
/**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -1747,9 +1742,6 @@
if (mUseBLAST) {
res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
}
- if (sEnableTripleBuffering) {
- res |= WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING;
- }
if (displayContent.mCurrentFocus == null) {
displayContent.mWinAddedSinceNullFocus.add(win);
@@ -2236,18 +2228,6 @@
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;
@@ -2520,6 +2500,12 @@
}
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");
@@ -2715,7 +2701,7 @@
}
/**
- * 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/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 5ef9420..49e704f 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -1150,7 +1150,8 @@
FrameworkStatsLog.write(
FrameworkStatsLog.DEVICE_ROTATED,
event.timestamp,
- rotationToLogEnum(reportedRotation));
+ rotationToLogEnum(reportedRotation),
+ FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d88387..eb83152 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 @@
*/
private InsetsState mFrozenInsetsState;
- @Nullable InsetsSourceProvider mPendingPositionChanged;
-
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
@@ -763,6 +761,56 @@
* 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 @@
updateSurfacePosition(t);
};
+ private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
+ if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+ t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+ }
+ };
+
/**
* @see #setSurfaceTranslationY(int)
*/
@@ -1514,11 +1568,20 @@
}
void setOrientationChanging(boolean changing) {
- mOrientationChanging = changing;
mOrientationChangeTimedOut = false;
+ if (mOrientationChanging == changing) {
+ return;
+ }
+ mOrientationChanging = changing;
if (changing) {
mLastFreezeDuration = 0;
- mWmService.mRoot.mOrientationChangeComplete = false;
+ if (mWmService.mRoot.mOrientationChangeComplete
+ && mDisplayContent.waitForUnfreeze(this)) {
+ mWmService.mRoot.mOrientationChangeComplete = false;
+ }
+ } else {
+ // The orientation change is completed. If it was hidden by the animation, reshow it.
+ mDisplayContent.finishFadeRotationAnimation(this);
}
}
@@ -2121,18 +2184,7 @@
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);
}
@@ -2148,6 +2200,22 @@
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().)
@@ -3697,7 +3765,7 @@
final int displayId = getDisplayId();
fillClientWindowFrames(mClientWindowFrames);
- mRedrawForSyncReported = true;
+ markRedrawForSyncReported();
try {
mClient.resized(mClientWindowFrames, reportDraw, mergedConfiguration, forceRelayout,
@@ -5318,13 +5386,18 @@
// 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);
}
}
}
@@ -5841,7 +5914,7 @@
* "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}
@@ -5870,16 +5943,26 @@
* 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;
}
@@ -5897,4 +5980,8 @@
@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 ebbebbb..3ef7ccd 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 @@
}
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;
@@ -656,8 +657,10 @@
if (w.getOrientationChanging()) {
if (!w.isDrawn()) {
- w.mWmService.mRoot.mOrientationChangeComplete = false;
- mAnimator.mLastWindowFreezeSource = w;
+ if (w.mDisplayContent.waitForUnfreeze(w)) {
+ w.mWmService.mRoot.mOrientationChangeComplete = false;
+ mAnimator.mLastWindowFreezeSource = w;
+ }
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation continue waiting for draw in %s", w);
} else {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5276d9c8..d54cf5f 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.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 @@
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;
@@ -758,11 +759,15 @@
/** @see WindowState#freezeInsetsState() */
void setInsetsFrozen(boolean freeze) {
- if (freeze) {
- forAllWindows(WindowState::freezeInsetsState, true /* traverseTopToBottom */);
- } else {
- forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */);
- }
+ forAllWindows(w -> {
+ if (w.mToken == this) {
+ if (freeze) {
+ w.freezeInsetsState();
+ } else {
+ w.clearFrozenInsetsState();
+ }
+ }
+ }, true /* traverseTopToBottom */);
}
@Override
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index f60b354..7f8168a 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 @@
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 @@
{"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 @@
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 aed13b2..56e2385 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 cdd5a92b..55ab8c3 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 @@
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 283895b..8739a01 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.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.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 @@
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 @@
}
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 @@
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 @@
});
}
+ /**
+ * 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 @@
.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 @@
@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 @@
}
@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 @@
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle);
if (requiredAdmin != null) {
- return requiredAdmin.mNetworkSlicingEnabled;
+ return requiredAdmin.mEnterpriseNetworkPreferenceEnabled;
} else {
return false;
}
@@ -16040,7 +16118,6 @@
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 @@
.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 @@
*
* @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 @@
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 @@
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 @@
}
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 @@
am.set(AlarmManager.RTC, alarmTime, pi);
}
- return deadlineState;
+ return notificationState;
}
private void suspendPersonalAppsInternal(int userId, boolean suspended) {
@@ -16239,7 +16320,7 @@
@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 @@
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 @@
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 @@
}
@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 @@
}
}
- 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 fa6ef00..5f35a26 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 @@
.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 a88f2b4..94f8e59 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -81,8 +81,8 @@
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 @@
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::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()),
@@ -377,6 +400,7 @@
dprintf(fd, "Mounts (%d): {\n", int(mMounts.size()));
for (auto&& [id, ifs] : mMounts) {
+ std::unique_lock ll(ifs->lock);
const IncFsMount& mnt = *ifs;
dprintf(fd, " [%d]: {\n", id);
if (id != mnt.mountId) {
@@ -422,6 +446,9 @@
}
bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+ if (!ifs.dataLoaderStub) {
+ return false;
+ }
if (ifs.dataLoaderStub->isSystemDataLoader()) {
return true;
}
@@ -439,6 +466,8 @@
std::lock_guard l(mLock);
mounts.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
+ std::unique_lock ll(ifs->lock);
+
if (ifs->mountId != id) {
continue;
}
@@ -456,7 +485,10 @@
std::thread([this, mounts = std::move(mounts)]() {
mJni->initializeForCurrentThread();
for (auto&& ifs : mounts) {
- ifs->dataLoaderStub->requestStart();
+ std::unique_lock l(ifs->lock);
+ if (ifs->dataLoaderStub) {
+ ifs->dataLoaderStub->requestStart();
+ }
}
}).detach();
}
@@ -671,25 +703,39 @@
setUidReadTimeouts(storageId, std::move(perUidReadTimeouts));
}
+ IfsMountPtr ifs;
+ DataLoaderStubPtr dataLoaderStub;
+
// Re-initialize DataLoader.
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
- if (!ifs) {
- return false;
- }
- if (ifs->dataLoaderStub) {
- ifs->dataLoaderStub->cleanupResources();
- ifs->dataLoaderStub = {};
- }
- l.unlock();
+ {
+ ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
- // DataLoader.
- auto dataLoaderStub =
- prepareDataLoader(*ifs, std::move(dataLoaderParams), std::move(statusListener),
- healthCheckParams, std::move(healthListener));
- CHECK(dataLoaderStub);
+ std::unique_lock l(ifs->lock);
+ dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+ }
- if (dataLoaderStub->isSystemDataLoader()) {
+ if (dataLoaderStub) {
+ dataLoaderStub->cleanupResources();
+ dataLoaderStub = {};
+ }
+
+ {
+ std::unique_lock l(ifs->lock);
+ if (ifs->dataLoaderStub) {
+ LOG(INFO) << "Skipped data loader stub creation because it already exists";
+ return false;
+ }
+ prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams), std::move(statusListener),
+ healthCheckParams, std::move(healthListener));
+ CHECK(ifs->dataLoaderStub);
+ dataLoaderStub = ifs->dataLoaderStub;
+ }
+
+ if (dataLoaderStub->isSystemDataLoader() &&
+ !getEnforceReadLogsMaxIntervalForSystemDataLoaders()) {
// Readlogs from system dataloader (adb) can always be collected.
ifs->startLoadingTs = TimePoint::max();
} else {
@@ -697,7 +743,7 @@
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) {
@@ -705,13 +751,14 @@
<< storageId;
return;
}
+ std::unique_lock l(ifs->lock);
if (ifs->startLoadingTs != startLoadingTs) {
LOG(INFO) << "Can't disable the readlogs, timestamp mismatch (new "
"installation?): "
<< storageId;
return;
}
- setStorageParams(*ifs, storageId, /*enableReadLogs=*/false);
+ disableReadLogsLocked(*ifs);
});
}
@@ -733,17 +780,17 @@
}
void IncrementalService::disallowReadLogs(StorageId storageId) {
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
+ const auto ifs = getIfs(storageId);
if (!ifs) {
LOG(ERROR) << "disallowReadLogs failed, invalid storageId: " << storageId;
return;
}
+
+ std::unique_lock l(ifs->lock);
if (!ifs->readLogsAllowed()) {
return;
}
ifs->disallowReadLogs();
- l.unlock();
const auto metadata = constants().readLogsDisabledMarkerName;
if (auto err = mIncFs->makeFile(ifs->control,
@@ -755,7 +802,7 @@
return;
}
- setStorageParams(storageId, /*enableReadLogs=*/false);
+ disableReadLogsLocked(*ifs);
}
int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
@@ -764,61 +811,79 @@
LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
return -EINVAL;
}
- return setStorageParams(*ifs, storageId, enableReadLogs);
-}
-int IncrementalService::setStorageParams(IncFsMount& ifs, StorageId storageId,
- bool enableReadLogs) {
- const auto& params = ifs.dataLoaderStub->params();
- if (enableReadLogs) {
- if (!ifs.readLogsAllowed()) {
- LOG(ERROR) << "setStorageParams failed, readlogs disallowed for storageId: "
- << storageId;
+ std::string packageName;
+
+ {
+ std::unique_lock l(ifs->lock);
+ if (!enableReadLogs) {
+ return disableReadLogsLocked(*ifs);
+ }
+
+ if (!ifs->readLogsAllowed()) {
+ LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
return -EPERM;
}
- // Check loader usage stats permission and apop.
- if (auto status = mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage,
- params.packageName.c_str());
- !status.isOk()) {
- LOG(ERROR) << " Permission: " << kLoaderUsageStats
- << " check failed: " << status.toString8();
- return fromBinderStatus(status);
- }
-
- // Check multiuser permission.
- if (auto status = mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr,
- params.packageName.c_str());
- !status.isOk()) {
- LOG(ERROR) << " Permission: " << kInteractAcrossUsers
- << " check failed: " << status.toString8();
- return fromBinderStatus(status);
+ if (!ifs->dataLoaderStub) {
+ // This should never happen - only DL can call enableReadLogs.
+ LOG(ERROR) << "enableReadLogs failed: invalid state";
+ return -EPERM;
}
// Check installation time.
const auto now = mClock->now();
- const auto startLoadingTs = ifs.startLoadingTs;
- if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
- LOG(ERROR) << "setStorageParams failed, readlogs can't be enabled at this time, "
- "storageId: "
- << storageId;
+ 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);
}
- if (auto status = applyStorageParams(ifs, enableReadLogs); !status.isOk()) {
- LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
+ // Check loader usage stats permission and apop.
+ if (auto status =
+ mAppOpsManager->checkPermission(kLoaderUsageStats, kOpUsage, packageName.c_str());
+ !status.isOk()) {
+ LOG(ERROR) << " Permission: " << kLoaderUsageStats
+ << " check failed: " << status.toString8();
return fromBinderStatus(status);
}
- if (enableReadLogs) {
- registerAppOpsCallback(params.packageName);
+ // Check multiuser permission.
+ if (auto status =
+ mAppOpsManager->checkPermission(kInteractAcrossUsers, nullptr, packageName.c_str());
+ !status.isOk()) {
+ LOG(ERROR) << " Permission: " << kInteractAcrossUsers
+ << " check failed: " << status.toString8();
+ return fromBinderStatus(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);
+
return 0;
}
-binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enableReadLogs) {
+int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) {
+ ifs.setReadLogsRequested(false);
+ return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false);
+}
+
+int IncrementalService::applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs) {
os::incremental::IncrementalFileSystemControlParcel control;
control.cmd.reset(dup(ifs.control.cmd()));
control.pendingReads.reset(dup(ifs.control.pendingReads()));
@@ -832,8 +897,10 @@
if (status.isOk()) {
// Store enabled state.
ifs.setReadLogsEnabled(enableReadLogs);
+ } else {
+ LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
}
- return status;
+ return status.isOk() ? 0 : fromBinderStatus(status);
}
void IncrementalService::deleteStorage(StorageId storageId) {
@@ -1019,17 +1086,14 @@
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()) {
@@ -1224,9 +1288,14 @@
return;
}
- const auto timeout = std::chrono::duration_cast<milliseconds>(maxPendingTimeUs) -
- Constants::perUidTimeoutOffset;
- updateUidReadTimeouts(storage, Clock::now() + timeout);
+ const auto timeout = Clock::now() + maxPendingTimeUs - Constants::perUidTimeoutOffset;
+ addIfsStateCallback(storage, [this, timeout](StorageId storageId, IfsState state) -> bool {
+ if (checkUidReadTimeouts(storageId, state, timeout)) {
+ return true;
+ }
+ clearUidReadTimeouts(storageId);
+ return false;
+ });
}
void IncrementalService::clearUidReadTimeouts(StorageId storage) {
@@ -1234,39 +1303,32 @@
if (!ifs) {
return;
}
-
mIncFs->setUidReadTimeouts(ifs->control, {});
}
-void IncrementalService::updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit) {
- // Reached maximum timeout.
+bool IncrementalService::checkUidReadTimeouts(StorageId storage, IfsState state,
+ Clock::time_point timeLimit) {
if (Clock::now() >= timeLimit) {
- return clearUidReadTimeouts(storage);
+ // Reached maximum timeout.
+ return false;
+ }
+ if (state.error) {
+ // Something is wrong, abort.
+ return false;
}
// Still loading?
- const auto state = isMountFullyLoaded(storage);
- if (int(state) < 0) {
- // Something is wrong, abort.
- return clearUidReadTimeouts(storage);
- }
-
- if (state == incfs::LoadingState::Full) {
- // Fully loaded, check readLogs collection.
- const auto ifs = getIfs(storage);
- if (!ifs->readLogsEnabled()) {
- return clearUidReadTimeouts(storage);
- }
+ if (state.fullyLoaded && !state.readLogsEnabled) {
+ return false;
}
const auto timeLeft = timeLimit - Clock::now();
if (timeLeft < Constants::progressUpdateInterval) {
// Don't bother.
- return clearUidReadTimeouts(storage);
+ return false;
}
- addTimedJob(*mTimedQueue, storage, Constants::progressUpdateInterval,
- [this, storage, timeLimit]() { updateUidReadTimeouts(storage, timeLimit); });
+ return true;
}
std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() {
@@ -1533,7 +1595,7 @@
dataLoaderParams.arguments = loader.arguments();
}
- prepareDataLoader(*ifs, std::move(dataLoaderParams));
+ prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams));
CHECK(ifs->dataLoaderStub);
std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
@@ -1615,24 +1677,19 @@
}
}
-IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
- IncFsMount& ifs, DataLoaderParamsParcel&& params, DataLoaderStatusListener&& statusListener,
- const StorageHealthCheckParams& healthCheckParams, StorageHealthListener&& healthListener) {
- std::unique_lock l(ifs.lock);
- prepareDataLoaderLocked(ifs, std::move(params), std::move(statusListener), healthCheckParams,
- std::move(healthListener));
- return ifs.dataLoaderStub;
+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,
StorageHealthListener&& healthListener) {
- if (ifs.dataLoaderStub) {
- LOG(INFO) << "Skipped data loader preparation because it already exists";
- return;
- }
-
FileSystemControlParcel fsControlParcel;
fsControlParcel.incremental = std::make_optional<IncrementalFileSystemControlParcel>();
fsControlParcel.incremental->cmd.reset(dup(ifs.control.cmd()));
@@ -1647,6 +1704,45 @@
new DataLoaderStub(*this, ifs.mountId, std::move(params), std::move(fsControlParcel),
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;
+ }
+
+ DataLoaderStubPtr dataLoaderStub;
+ {
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
+
+ std::unique_lock l(ifs->lock);
+ dataLoaderStub = std::exchange(ifs->dataLoaderStub, nullptr);
+ }
+
+ if (dataLoaderStub) {
+ dataLoaderStub->cleanupResources();
+ }
+
+ return false;
+ });
}
template <class Duration>
@@ -2070,11 +2166,11 @@
StorageHealthListener healthListener) {
DataLoaderStubPtr dataLoaderStub;
{
- std::unique_lock l(mLock);
- const auto& ifs = getIfsLocked(storage);
+ const auto& ifs = getIfs(storage);
if (!ifs) {
return false;
}
+ std::unique_lock l(ifs->lock);
dataLoaderStub = ifs->dataLoaderStub;
if (!dataLoaderStub) {
return false;
@@ -2160,13 +2256,16 @@
std::lock_guard l(mLock);
affected.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
- if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) {
+ std::unique_lock ll(ifs->lock);
+ if (ifs->mountId == id && ifs->dataLoaderStub &&
+ ifs->dataLoaderStub->params().packageName == packageName) {
affected.push_back(ifs);
}
}
}
for (auto&& ifs : affected) {
- applyStorageParams(*ifs, false);
+ std::unique_lock ll(ifs->lock);
+ disableReadLogsLocked(*ifs);
}
}
@@ -2187,6 +2286,108 @@
return true;
}
+void IncrementalService::addIfsStateCallback(StorageId storageId, IfsStateCallback callback) {
+ bool wasEmpty;
+ {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ wasEmpty = mIfsStateCallbacks.empty();
+ mIfsStateCallbacks[storageId].emplace_back(std::move(callback));
+ }
+ if (wasEmpty) {
+ addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval,
+ [this]() { processIfsStateCallbacks(); });
+ }
+}
+
+void IncrementalService::processIfsStateCallbacks() {
+ StorageId storageId = kInvalidStorageId;
+ std::vector<IfsStateCallback> local;
+ while (true) {
+ {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ if (mIfsStateCallbacks.empty()) {
+ return;
+ }
+ IfsStateCallbacks::iterator it;
+ if (storageId == kInvalidStorageId) {
+ // First entry, initialize the |it|.
+ it = mIfsStateCallbacks.begin();
+ } else {
+ // 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()) {
+ // Nothing else left, too bad.
+ break;
+ }
+ if (it->first != storageId) {
+ local.clear(); // Was removed during processing, forget the old callbacks.
+ } else {
+ // 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;
+ }
+ }
+ }
+
+ if (it == mIfsStateCallbacks.end()) {
+ break;
+ }
+
+ storageId = it->first;
+ auto& callbacks = it->second;
+ if (callbacks.empty()) {
+ // Invalid case, one extra lookup should be ok.
+ continue;
+ }
+ std::swap(callbacks, local);
+ }
+
+ processIfsStateCallbacks(storageId, local);
+ }
+
+ addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval,
+ [this]() { processIfsStateCallbacks(); });
+}
+
+void IncrementalService::processIfsStateCallbacks(StorageId storageId,
+ std::vector<IfsStateCallback>& callbacks) {
+ const auto state = isMountFullyLoaded(storageId);
+ IfsState storageState = {};
+ storageState.error = int(state) < 0;
+ storageState.fullyLoaded = state == incfs::LoadingState::Full;
+ if (storageState.fullyLoaded) {
+ const auto ifs = getIfs(storageId);
+ storageState.readLogsEnabled = ifs && ifs->readLogsEnabled();
+ }
+
+ for (auto cur = callbacks.begin(); cur != callbacks.end();) {
+ if ((*cur)(storageId, storageState)) {
+ ++cur;
+ } else {
+ cur = callbacks.erase(cur);
+ }
+ }
+}
+
+void IncrementalService::removeIfsStateCallbacks(StorageId storageId) {
+ std::lock_guard l(mIfsStateCallbacksLock);
+ mIfsStateCallbacks.erase(storageId);
+}
+
void IncrementalService::getMetrics(StorageId storageId, android::os::PersistableBundle* result) {
const auto duration = getMillsSinceOldestPendingRead(storageId);
if (duration >= 0) {
@@ -2197,12 +2398,12 @@
}
long IncrementalService::getMillsSinceOldestPendingRead(StorageId storageId) {
- std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storageId);
+ const auto ifs = getIfs(storageId);
if (!ifs) {
LOG(ERROR) << "getMillsSinceOldestPendingRead failed, invalid storageId: " << storageId;
return -EINVAL;
}
+ std::unique_lock l(ifs->lock);
if (!ifs->dataLoaderStub) {
LOG(ERROR) << "getMillsSinceOldestPendingRead failed, no data loader: " << storageId;
return -EINVAL;
@@ -2248,6 +2449,7 @@
resetHealthControl();
mService.removeTimedJobs(*mService.mTimedQueue, mId);
}
+ mService.removeIfsStateCallbacks(mId);
requestDestroy();
@@ -2758,7 +2960,7 @@
mService.mLooper->addFd(
pendingReadsFd, android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
[](int, int, void* data) -> int {
- auto&& self = (DataLoaderStub*)data;
+ auto self = (DataLoaderStub*)data;
self->updateHealthStatus(/*baseline=*/true);
return 0;
},
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 4a5db06..fb6f56c 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -74,6 +74,17 @@
using PerUidReadTimeouts = ::android::os::incremental::PerUidReadTimeouts;
+struct IfsState {
+ // If mount is fully loaded.
+ bool fullyLoaded = false;
+ // If read logs are enabled on this mount. Populated only if fullyLoaded == true.
+ bool readLogsEnabled = false;
+ // If there was an error fetching any of the above.
+ bool error = false;
+};
+// Returns true if wants to be called again.
+using IfsStateCallback = std::function<bool(StorageId, IfsState)>;
+
class IncrementalService final {
public:
explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir);
@@ -84,7 +95,8 @@
#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();
@@ -105,6 +117,7 @@
enum StorageFlags {
ReadLogsAllowed = 1 << 0,
ReadLogsEnabled = 1 << 1,
+ ReadLogsRequested = 1 << 2,
};
struct LoadingProgress {
@@ -354,6 +367,9 @@
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);
};
@@ -366,7 +382,7 @@
void setUidReadTimeouts(StorageId storage,
std::vector<PerUidReadTimeouts>&& perUidReadTimeouts);
void clearUidReadTimeouts(StorageId storage);
- void updateUidReadTimeouts(StorageId storage, Clock::time_point timeLimit);
+ bool checkUidReadTimeouts(StorageId storage, IfsState state, Clock::time_point timeLimit);
std::unordered_set<std::string_view> adoptMountedInstances();
void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames);
@@ -387,11 +403,6 @@
bool needStartDataLoaderLocked(IncFsMount& ifs);
- DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
- content::pm::DataLoaderParamsParcel&& params,
- DataLoaderStatusListener&& statusListener = {},
- const StorageHealthCheckParams& healthCheckParams = {},
- StorageHealthListener&& healthListener = {});
void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params,
DataLoaderStatusListener&& statusListener = {},
const StorageHealthCheckParams& healthCheckParams = {},
@@ -410,8 +421,8 @@
std::string_view path) const;
int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode);
- int setStorageParams(IncFsMount& ifs, StorageId storageId, bool enableReadLogs);
- binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
+ int disableReadLogsLocked(IncFsMount& ifs);
+ int applyStorageParamsLocked(IncFsMount& ifs, bool enableReadLogs);
LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
@@ -431,10 +442,18 @@
bool addTimedJob(TimedQueueWrapper& timedQueue, MountId id, Milliseconds after, Job what);
bool removeTimedJobs(TimedQueueWrapper& timedQueue, MountId id);
+
+ void addIfsStateCallback(StorageId storageId, IfsStateCallback callback);
+ void removeIfsStateCallbacks(StorageId storageId);
+ void processIfsStateCallbacks();
+ void processIfsStateCallbacks(StorageId storageId, std::vector<IfsStateCallback>& callbacks);
+
bool updateLoadingProgress(int32_t storageId,
StorageLoadingProgressListener&& progressListener);
long getMillsSinceOldestPendingRead(StorageId storage);
+ void trimReservedSpaceV1(const IncFsMount& ifs);
+
private:
const std::unique_ptr<VoldServiceWrapper> mVold;
const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager;
@@ -456,6 +475,10 @@
std::mutex mCallbacksLock;
std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered;
+ using IfsStateCallbacks = std::map<StorageId, std::vector<IfsStateCallback>>;
+ std::mutex mIfsStateCallbacksLock;
+ IfsStateCallbacks mIfsStateCallbacks;
+
std::atomic_bool mSystemReady = false;
StorageId mNextId = 0;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 3465499..8e416f3 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -212,6 +212,9 @@
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 @@
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 @@
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 a787db5..d4cdcbe 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -84,6 +84,8 @@
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 @@
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 @@
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 338659d..bf4e9616 100644
--- a/services/incremental/path.cpp
+++ b/services/incremental/path.cpp
@@ -44,19 +44,20 @@
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 3e5fd21..e12e1d0 100644
--- a/services/incremental/path.h
+++ b/services/incremental/path.h
@@ -89,15 +89,25 @@
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 54bc95d..ddb7784 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -140,9 +140,7 @@
const content::pm::FileSystemControlParcel& control,
const sp<content::pm::IDataLoaderStatusListener>& listener) {
createOkNoStatus(id, params, control, listener);
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_CREATED);
- }
+ reportStatus(id);
return binder::Status::ok();
}
binder::Status createOkNoStatus(int32_t id, const content::pm::DataLoaderParamsParcel& params,
@@ -150,33 +148,26 @@
const sp<content::pm::IDataLoaderStatusListener>& listener) {
mServiceConnector = control.service;
mListener = listener;
+ mStatus = IDataLoaderStatusListener::DATA_LOADER_CREATED;
return binder::Status::ok();
}
binder::Status startOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STARTED);
return binder::Status::ok();
}
binder::Status stopOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_STOPPED);
return binder::Status::ok();
}
binder::Status destroyOk(int32_t id) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
mListener = nullptr;
return binder::Status::ok();
}
binder::Status prepareImageOk(int32_t id,
const ::std::vector<content::pm::InstallationFileParcel>&,
const ::std::vector<::std::string>&) {
- if (mListener) {
- mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
- }
+ setAndReportStatus(id, IDataLoaderStatusListener::DATA_LOADER_IMAGE_READY);
return binder::Status::ok();
}
binder::Status storageError(int32_t id) {
@@ -197,10 +188,22 @@
EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk());
return result;
}
+ int status() const { return mStatus; }
private:
+ void setAndReportStatus(int id, int status) {
+ mStatus = status;
+ reportStatus(id);
+ }
+ void reportStatus(int id) {
+ if (mListener) {
+ mListener->onStatusChanged(id, mStatus);
+ }
+ }
+
sp<IIncrementalServiceConnector> mServiceConnector;
sp<IDataLoaderStatusListener> mListener;
+ int mStatus = IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
};
class MockDataLoaderManager : public DataLoaderManagerWrapper {
@@ -269,12 +272,20 @@
}
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,
@@ -282,7 +293,7 @@
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,
@@ -290,7 +301,8 @@
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,
@@ -298,7 +310,7 @@
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);
}
@@ -367,6 +379,7 @@
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,
@@ -374,14 +387,15 @@
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());
@@ -862,10 +876,10 @@
}
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;
@@ -879,6 +893,11 @@
ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
.WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith1sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
&MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
mDataLoaderManager->setDataLoaderStatusDestroyed();
@@ -909,13 +928,13 @@
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(3);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -992,6 +1011,11 @@
// 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();
@@ -1126,7 +1150,7 @@
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(2);
EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(2);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(5);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(6);
sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
NiceMock<MockStorageHealthListener>* listenerMock = listener.get();
@@ -1314,7 +1338,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1355,7 +1379,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
// System data loader.
mDataLoaderParcel.packageName = "android";
TemporaryDir tempDir;
@@ -1366,9 +1390,9 @@
ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
{}, {}));
- // No readlogs callback.
- ASSERT_EQ(mTimedQueue->mAfter, 0ms);
- ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+ // IfsState callback.
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
// Now advance clock for 1hr.
@@ -1376,6 +1400,8 @@
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
// Now advance clock for 2hrs.
mClock->advance(readLogsMaxInterval);
+ // IfsStorage callback should not affect anything.
+ callback();
ASSERT_EQ(mDataLoader->setStorageParams(true), 0);
}
@@ -1394,7 +1420,7 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(4);
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -1570,7 +1596,7 @@
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);
@@ -1581,7 +1607,7 @@
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);
@@ -1592,7 +1618,7 @@
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"));
@@ -1685,6 +1711,144 @@
mIncrementalService->registerLoadingProgressListener(storageId, listener);
}
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDone) {
+ mFs->hasFiles();
+
+ const auto stateUpdateInterval = 1s;
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ // No unbinding just yet.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // System data loader to get rid of readlog timeout callback.
+ mDataLoaderParcel.packageName = "android";
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // IfsState callback present.
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
+
+ // Not loaded yet.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+ // Send the callback, should not do anything.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
+
+ // Fully loaded.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)).WillOnce(Return(incfs::LoadingState::Full));
+ // Expect the unbind.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+
+ callback();
+
+ // Destroyed.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
+TEST_F(IncrementalServiceTest, testStartDataLoaderUnbindOnAllDoneWithReadlogs) {
+ mFs->hasFiles();
+
+ // Readlogs.
+ mVold->setIncFsMountOptionsSuccess();
+ mAppOpsManager->checkPermissionSuccess();
+
+ const auto stateUpdateInterval = 1s;
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+ // No unbinding just yet.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // System data loader to get rid of readlog timeout callback.
+ mDataLoaderParcel.packageName = "android";
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // IfsState callback present.
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
+
+ // Not loaded yet.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+
+ // Send the callback, should not do anything.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
+
+ // Fully loaded.
+ EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
+ .WillOnce(Return(incfs::LoadingState::Full))
+ .WillOnce(Return(incfs::LoadingState::Full));
+ // But with readlogs.
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+
+ // Send the callback, still nothing.
+ callback();
+
+ // Still started.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
+
+ // Still present.
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
+ callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
+
+ // Disable readlogs and expect the unbind.
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ ASSERT_GE(mDataLoader->setStorageParams(false), 0);
+
+ callback();
+
+ // Destroyed.
+ ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+}
+
TEST_F(IncrementalServiceTest, testRegisterStorageHealthListenerSuccess) {
mIncFs->openMountSuccess();
sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
@@ -1801,7 +1965,7 @@
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
int storageId =
@@ -1829,7 +1993,6 @@
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
.WillOnce(Return(incfs::LoadingState::MissingBlocks))
.WillOnce(Return(incfs::LoadingState::MissingBlocks))
- .WillOnce(Return(incfs::LoadingState::Full))
.WillOnce(Return(incfs::LoadingState::Full));
// Mark DataLoader as 'system' so that readlogs don't pollute the timed queue.
@@ -1846,10 +2009,10 @@
{
// Timed callback present -> 0 progress.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Call it again.
timedCallback();
@@ -1857,10 +2020,10 @@
{
// Still present -> some progress.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Fully loaded but readlogs collection enabled.
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
@@ -1871,10 +2034,10 @@
{
// Still present -> fully loaded + readlogs.
- ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(storageId);
+ 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 cbe479e1..4a8ae5b 100644
--- a/services/incremental/test/path_test.cpp
+++ b/services/incremental/test/path_test.cpp
@@ -40,4 +40,24 @@
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 b01e425..800f7ad 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -83,3 +83,15 @@
"//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/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index e7d0121..eab3b77 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -388,9 +388,11 @@
private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
@Override
- public void onCreatePredictionSession(AppPredictionContext context,
+ public void onCreatePredictionSession(AppPredictionContext appPredictionContext,
AppPredictionSessionId sessionId) {
- mSessions.put(sessionId, new SessionInfo(context, mDataManager, sessionId.getUserId()));
+ mSessions.put(sessionId,
+ new SessionInfo(appPredictionContext, mDataManager, sessionId.getUserId(),
+ getContext()));
}
@Override
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java
index 28612f1..d256d9c 100644
--- a/services/people/java/com/android/server/people/SessionInfo.java
+++ b/services/people/java/com/android/server/people/SessionInfo.java
@@ -20,6 +20,7 @@
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppTarget;
import android.app.prediction.IPredictionCallback;
+import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -40,9 +41,9 @@
new RemoteCallbackList<>();
SessionInfo(AppPredictionContext predictionContext, DataManager dataManager,
- @UserIdInt int callingUserId) {
+ @UserIdInt int callingUserId, Context context) {
mAppTargetPredictor = AppTargetPredictor.create(predictionContext,
- this::updatePredictions, dataManager, callingUserId);
+ this::updatePredictions, dataManager, callingUserId, context);
}
void addCallback(IPredictionCallback callback) {
diff --git a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
index c89dadc..e191081 100644
--- a/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/AppTargetPredictor.java
@@ -24,6 +24,7 @@
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.Context;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.people.data.DataManager;
@@ -43,10 +44,10 @@
/** Creates a {@link AppTargetPredictor} instance based on the prediction context. */
public static AppTargetPredictor create(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager, @UserIdInt int callingUserId, Context context) {
if (UI_SURFACE_SHARE.equals(predictionContext.getUiSurface())) {
- return new ShareTargetPredictor(
- predictionContext, updatePredictionsMethod, dataManager, callingUserId);
+ return new ShareTargetPredictor(predictionContext, updatePredictionsMethod, dataManager,
+ callingUserId, context);
}
return new AppTargetPredictor(
predictionContext, updatePredictionsMethod, dataManager, callingUserId);
@@ -124,6 +125,11 @@
callback.accept(targets);
}
+ /** To be overridden by the subclass to recycle resources. */
+ @WorkerThread
+ void destroy() {
+ }
+
AppPredictionContext getPredictionContext() {
return mPredictionContext;
}
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index 236ac84..368b737 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -16,6 +16,8 @@
package com.android.server.people.prediction;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
import static java.util.Collections.reverseOrder;
import android.annotation.NonNull;
@@ -23,17 +25,23 @@
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
+import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
@@ -52,14 +60,27 @@
private static final String TAG = "ShareTargetPredictor";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
private final IntentFilter mIntentFilter;
+ private final AppPredictor mRemoteAppPredictor;
ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
@NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
- @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+ @NonNull DataManager dataManager,
+ @UserIdInt int callingUserId, @NonNull Context context) {
super(predictionContext, updatePredictionsMethod, dataManager, callingUserId);
mIntentFilter = predictionContext.getExtras().getParcelable(
ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
+ if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ false)) {
+ predictionContext.getExtras().putBoolean(REMOTE_APP_PREDICTOR_KEY, true);
+ mRemoteAppPredictor = context.createContextAsUser(UserHandle.of(callingUserId), 0)
+ .getSystemService(AppPredictionManager.class)
+ .createAppPredictionSession(predictionContext);
+ } else {
+ mRemoteAppPredictor = null;
+ }
}
/** Reports chosen history of direct/app share targets. */
@@ -72,6 +93,9 @@
if (mIntentFilter != null) {
getDataManager().reportShareTargetEvent(event, mIntentFilter);
}
+ if (mRemoteAppPredictor != null) {
+ mRemoteAppPredictor.notifyAppTargetEvent(event);
+ }
}
/** Provides prediction on direct share targets */
@@ -129,6 +153,15 @@
callback.accept(appTargetList);
}
+ /** Recycles resources. */
+ @WorkerThread
+ @Override
+ void destroy() {
+ if (mRemoteAppPredictor != null) {
+ mRemoteAppPredictor.destroy();
+ }
+ }
+
private List<ShareTarget> getDirectShareTargets() {
List<ShareTarget> shareTargets = new ArrayList<>();
List<ShareShortcutInfo> shareShortcuts =
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index a262939..29aedce 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 @@
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/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index d5eda20..dce853a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -41,7 +41,12 @@
}
private val platformCompat: PlatformCompat = mockThrowOnUnmocked {
- whenever(isChangeEnabled(eq(DomainVerificationCollector.RESTRICT_DOMAINS), any())) {
+ whenever(
+ isChangeEnabledInternalNoLogging(
+ eq(DomainVerificationCollector.RESTRICT_DOMAINS),
+ any()
+ )
+ ) {
(arguments[1] as ApplicationInfo).targetSdkVersion >= Build.VERSION_CODES.S
}
}
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 7e25901..537a49e 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
@@ -114,12 +114,7 @@
it,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
- whenever(
- isChangeEnabled(
- anyLong(),
- any()
- )
- ) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(connection)
}
@@ -422,7 +417,7 @@
allowQueryAll.set(true)
- runMethod(target, NON_VERIFIER_UID)
+ assertFails { runMethod(target, NON_VERIFIER_UID) }
}
private fun approvedVerifier() {
@@ -821,7 +816,7 @@
// 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 8ae4c5a..14c02d5 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.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 @@
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 0e74b65..881604f 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.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 @@
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 @@
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 @@
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 @@
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() } }
@@ -310,7 +361,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index fe3672d..0ce16e6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -43,6 +43,7 @@
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import java.util.UUID
@@ -366,7 +367,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 377bae1..b7c6922 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -98,7 +98,7 @@
context,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(),any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(connection)
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index 44c1b8f..54648ab 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -71,7 +71,7 @@
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
- whenever(isChangeEnabled(anyLong(), any())) { true }
+ whenever(isChangeEnabledInternalNoLogging(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
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 22b2f7e..d220444 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 @@
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/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 7925b69..0fcda81 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -660,15 +660,19 @@
new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
markImplicitConstraintsSatisfied(job, false);
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), false, false);
assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
markImplicitConstraintsSatisfied(job, true);
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), false, false);
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
- job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ job.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
}
@@ -677,7 +681,7 @@
job.setDeviceNotDozingConstraintSatisfied(
sElapsedRealtimeClock.millis(), isSatisfied, false);
job.setBackgroundNotRestrictedConstraintSatisfied(
- sElapsedRealtimeClock.millis(), isSatisfied);
+ sElapsedRealtimeClock.millis(), isSatisfied, false);
}
private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
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 f73af53..4bab8e5 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
@@ -384,7 +384,8 @@
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
/* state */ true, /* allowlisted */false);
- js.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ js.setBackgroundNotRestrictedConstraintSatisfied(
+ sElapsedRealtimeClock.millis(), true, false);
return js;
}
@@ -5415,9 +5416,8 @@
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));
@@ -5466,7 +5466,7 @@
// 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/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
index 0597443..ef48646 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
@@ -19,8 +19,6 @@
import android.os.IPowerManager;
import android.os.PowerManager.LocationPowerSaveMode;
-import com.android.server.location.eventlog.LocationEventLog;
-
/**
* Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
* change".
@@ -30,8 +28,7 @@
@LocationPowerSaveMode
private int mLocationPowerSaveMode;
- public FakeLocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
- super(locationEventLog);
+ public FakeLocationPowerSaveModeHelper() {
mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
index 6156ba9..28da027 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
@@ -43,7 +43,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
import org.junit.After;
@@ -85,7 +84,7 @@
Context context = mock(Context.class);
doReturn(powerManager).when(context).getSystemService(PowerManager.class);
- mHelper = new SystemLocationPowerSaveModeHelper(context, new LocationEventLog());
+ mHelper = new SystemLocationPowerSaveModeHelper(context);
mHelper.onSystemReady();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 1f102ac..ae70dad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -16,8 +16,6 @@
package com.android.server.location.injector;
-import com.android.server.location.eventlog.LocationEventLog;
-
public class TestInjector implements Injector {
private final FakeUserInfoHelper mUserInfoHelper;
@@ -35,17 +33,13 @@
private final LocationUsageLogger mLocationUsageLogger;
public TestInjector() {
- this(new LocationEventLog());
- }
-
- public TestInjector(LocationEventLog eventLog) {
mUserInfoHelper = new FakeUserInfoHelper();
mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
mAppForegroundHelper = new FakeAppForegroundHelper();
- mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
+ mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper();
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
mDeviceIdleHelper = new FakeDeviceIdleHelper();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 1b58e92..24b85f0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -83,7 +83,6 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
@@ -161,19 +160,17 @@
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
- LocationEventLog eventLog = new LocationEventLog();
-
- mInjector = new TestInjector(eventLog);
+ mInjector = new TestInjector();
mInjector.getUserInfoHelper().startUser(OTHER_USER);
- mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog);
+ mPassive = new PassiveLocationProviderManager(mContext, mInjector);
mPassive.startManager();
mPassive.setRealProvider(new PassiveLocationProvider(mContext));
mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY);
mProvider.setProviderAllowed(true);
- mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive);
+ mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
mManager.startManager();
mManager.setRealProvider(mProvider);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index c3cca64..04e0151 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -36,7 +36,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.location.eventlog.LocationEventLog;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.test.FakeProvider;
@@ -77,7 +76,7 @@
mDelegateProvider = new FakeProvider(mDelegate);
mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
- mDelegateProvider, new LocationEventLog());
+ mDelegateProvider);
mProvider.getController().setListener(mListener);
mProvider.getController().start();
}
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 17c6b6f..db0c3ae 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.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 @@
.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 a0e208f..46487ea2 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 @@
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 @@
@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 @@
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 0000000..7a6110b
--- /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 4ef454c..9513c6e 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -75,6 +75,9 @@
<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 73a2feb..4a67ec7 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_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 @@
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 @@
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 9ffb5017..5c8a7d2 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 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 @@
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 @@
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 @@
/* 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 3d0895d..24a8b61 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 @@
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 @@
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 0000000..5edbc16
--- /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 1c96838..0dbf3fe 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.returnsArgAt;
import static org.mockito.ArgumentMatchers.any;
@@ -27,6 +28,7 @@
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;
@@ -35,6 +37,7 @@
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;
@@ -52,11 +55,11 @@
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}
@@ -67,6 +70,7 @@
private static final String PACKAGE_SCHEME = "package";
private static final String PACKAGE_NAME_1 = "package1";
private static final String PACKAGE_NAME_2 = "package2";
+ private static final String PACKAGE_NAME_3 = "package3";
private static final int USER_ID_1 = 1;
private static final int USER_ID_2 = 2;
@@ -79,6 +83,8 @@
@Mock
private IPackageManager mIPackageManager;
@Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
private IActivityManager mIActivityManager;
@Mock
private UserManager mUserManager;
@@ -107,13 +113,15 @@
List<PackageInfo> packages = new ArrayList<>();
packages.add(makePackageInfo(PACKAGE_NAME_1));
+ packages.add(makePackageInfo(PACKAGE_NAME_2));
+ packages.add(makePackageInfo(PACKAGE_NAME_3));
doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt());
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;
}
@@ -146,8 +154,8 @@
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
@@ -179,6 +187,26 @@
assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
}
+ @Test
+ public void testGetHibernatingPackagesForUser_returnsCorrectPackages() throws RemoteException {
+ // 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});
+ 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);
+ mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true);
+
+ // THEN the hibernating packages returned matches
+ List<String> hibernatingPackages =
+ mAppHibernationService.getHibernatingPackagesForUser(USER_ID_2);
+ assertEquals(2, hibernatingPackages.size());
+ assertTrue(hibernatingPackages.contains(PACKAGE_NAME_1));
+ assertTrue(hibernatingPackages.contains(PACKAGE_NAME_2));
+ }
+
/**
* Add a mock user with one package.
*/
@@ -230,18 +258,29 @@
}
@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 ad22cba..4240581 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 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*schemaVersion=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -98,16 +99,17 @@
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 @@
// 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 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*schemaVersion=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -150,16 +153,17 @@
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 @@
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 @@
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 @@
/*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 @@
/*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 @@
/*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 @@
/*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 @@
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 @@
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 @@
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 @@
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 @@
/*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 e0cdedd..c34c00d 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.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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert enough documents.
for (int i = 0;
@@ -471,7 +473,8 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert document
GenericDocument document =
@@ -504,14 +507,16 @@
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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert document
GenericDocument document =
@@ -597,7 +603,8 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -608,7 +615,8 @@
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package1 document
GenericDocument document =
@@ -647,7 +655,8 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -658,7 +667,8 @@
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package1 document
GenericDocument document =
@@ -735,7 +745,8 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Create expected schemaType proto.
SchemaProto expectedProto =
@@ -781,7 +792,8 @@
oldSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Create incompatible schema
List<AppSearchSchema> newSchemas =
@@ -795,7 +807,8 @@
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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Create expected schemaType proto.
SchemaProto expectedProto =
@@ -847,7 +861,8 @@
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 @@
finalSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ true);
+ /*forceOverride=*/ true,
+ /*version=*/ 0);
// Check Document schema is removed.
expectedProto =
@@ -895,14 +911,16 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
appSearchImpl.close();
@@ -1109,7 +1458,8 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
});
expectThrows(
@@ -1179,7 +1529,8 @@
"database",
"namespace",
"uri",
- /*usageTimestampMillis=*/ 1000L);
+ /*usageTimestampMillis=*/ 1000L,
+ /*systemUsage=*/ false);
});
expectThrows(
@@ -1203,6 +1554,18 @@
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 467ede4..673b7ee 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 @@
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 dc225f1..0fe3903 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 void testGetProto_Email() {
AppSearchSchema emailSchema =
new AppSearchSchema.Builder("Email")
- .setVersion(12345)
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("subject")
.setCardinality(
@@ -89,7 +88,7 @@
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 @@
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
new file mode 100644
index 0000000..557c14a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class UserAwareBiometricSchedulerTest {
+
+ private static final String TAG = "BiometricSchedulerTest";
+ private static final int TEST_SENSOR_ID = 0;
+
+ private UserAwareBiometricScheduler mScheduler;
+ private IBinder mToken;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private IBiometricService mBiometricService;
+
+ private TestUserStartedCallback mUserStartedCallback;
+ private TestUserStoppedCallback mUserStoppedCallback;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mToken = new Binder();
+ mUserStartedCallback = new TestUserStartedCallback();
+ mUserStoppedCallback = new TestUserStoppedCallback();
+
+ mScheduler = new UserAwareBiometricScheduler(TAG,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ () -> mCurrentUserId,
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new TestStopUserClient(mContext, Object::new, mToken, userId,
+ TEST_SENSOR_ID, mUserStoppedCallback);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
+ TEST_SENSOR_ID, mUserStartedCallback);
+ }
+ });
+ }
+
+ @Test
+ public void testScheduleOperation_whenNoUser() {
+ mCurrentUserId = UserHandle.USER_NULL;
+
+ final int nextUserId = 0;
+
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+ verify(nextClient, never()).start(any());
+ assertEquals(0, mUserStoppedCallback.numInvocations);
+ assertEquals(1, mUserStartedCallback.numInvocations);
+
+ waitForIdle();
+ verify(nextClient).start(any());
+ }
+
+ @Test
+ public void testScheduleOperation_whenSameUser() {
+ mCurrentUserId = 10;
+
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+
+ waitForIdle();
+
+ verify(nextClient).start(any());
+ assertEquals(0, mUserStoppedCallback.numInvocations);
+ assertEquals(0, mUserStartedCallback.numInvocations);
+ }
+
+ @Test
+ public void testScheduleOperation_whenDifferentUser() {
+ mCurrentUserId = 10;
+
+ final int nextUserId = 11;
+ BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
+ when(nextClient.getTargetUserId()).thenReturn(nextUserId);
+
+ mScheduler.scheduleClientMonitor(nextClient);
+
+ waitForIdle();
+ assertEquals(1, mUserStoppedCallback.numInvocations);
+
+ waitForIdle();
+ assertEquals(1, mUserStartedCallback.numInvocations);
+
+ waitForIdle();
+ verify(nextClient).start(any());
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
+
+ int numInvocations;
+
+ @Override
+ public void onUserStopped() {
+ numInvocations++;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ }
+
+ private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> {
+
+ int numInvocations;
+
+ @Override
+ public void onUserStarted(int newUserId, Object newObject) {
+ numInvocations++;
+ mCurrentUserId = newUserId;
+ }
+ }
+
+ private static class TestStopUserClient extends StopUserClient<Object> {
+ public TestStopUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ protected void startHalOperation() {
+
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ onUserStopped();
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+
+ 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<Object> callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ protected void startHalOperation() {
+
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), new Object());
+ callback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+ }
+}
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 04a7122..0cd6d86 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 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 @@
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
- private FaceProvider mFaceProvider;
+ private TestableFaceProvider mFaceProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -80,7 +82,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFaceProvider = new FaceProvider(mContext, mSensorProps, TAG,
+ mFaceProvider = new TestableFaceProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher);
}
@@ -123,4 +125,19 @@
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 d149880e..94cc666 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 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 @@
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
- private FingerprintProvider mFingerprintProvider;
+ private TestableFingerprintProvider mFingerprintProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -83,7 +85,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new FingerprintProvider(mContext, mSensorProps, TAG,
+ mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@@ -126,4 +128,20 @@
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 0000000..6264a14
--- /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 d093e79..c2a81d9 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 @@
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 576f9c2..cc206a1 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.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.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 @@
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 @@
@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)
- //);
+ 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.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)
- //);
+ 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 testGetSetNetworkSlicing() throws Exception {
+ 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)
- //);
+ final int managedProfileUserId = 15;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+ addManagedProfile(admin1, managedProfileAdminUid, admin1);
+ mContext.binder.callingUid = managedProfileAdminUid;
- 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)
- //);
+ 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/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index dbb415c..e7ffea0 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.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
@Presubmit
@@ -117,10 +118,13 @@
}
}
+ 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 @@
@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 @@
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 @@
+ " <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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -485,7 +492,7 @@
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 @@
try {
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- readonlyFile);
+ readonlyFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -551,7 +558,7 @@
public long getRevision(File file) throws IOException {
return 0;
}
- }, fakeFsverityUtil, mConfigFile);
+ }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -580,7 +587,7 @@
public long getRevision(File file) throws IOException {
return 0;
}
- }, fakeFsverityUtil, mConfigFile);
+ }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -617,7 +624,7 @@
FakeFontFileParser parser = new FakeFontFileParser();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -637,7 +644,7 @@
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 @@
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 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -703,7 +710,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -723,7 +730,7 @@
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 @@
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 50ba761..ee9de07 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 @@
}
@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 aa5bc93..d5df071 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 @@
}
@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 41f4a1e..8b23be5 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 @@
}
@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 b3ee18d..ee1a857 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_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 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.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 @@
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 @@
@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 @@
@Test
public void testAllocateLogicalAddress_TvDeviceNonPreferredNotOccupied() {
-
mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback);
mTestLooper.dispatchAll();
assertEquals(ADDR_TV, mLogicalAddress);
@@ -308,4 +330,90 @@
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 6bb148d..38a44c6 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_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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 1a6bad8..80da696 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 @@
}
@Override
- boolean isStandbyMessageReceived() {
+ protected boolean isStandbyMessageReceived() {
return mStandby;
}
@@ -184,7 +184,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 b3f0085..6880302 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 @@
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 @@
(byte) (DEVICE_TV & 0xFF)
};
callbackResult = -1;
- boolean handleResult =
+ @Constants.HandleMessageResult int handleResult =
mHdmiLocalDevice.handleGivePhysicalAddress(
(int finalResult) -> callbackResult = finalResult);
mTestLooper.dispatchAll();
@@ -221,7 +221,7 @@
* 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 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 4b3ef2f..39e06a3 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 @@
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 @@
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 @@
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 @@
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 70718f7..0000000
--- 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 b5336e3..68aa96a 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.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 @@
@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 @@
mCanGoToStandby = canGoToStandby;
}
}
- private class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem {
+ protected static class MockAudioSystemDevice extends HdmiCecLocalDeviceAudioSystem {
private boolean mCanGoToStandby;
private boolean mIsStandby;
@@ -171,15 +174,14 @@
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 @@
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));
+ mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter());
+ mHdmiControlServiceSpy.setCecController(mHdmiCecController);
+ mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create(
+ mHdmiControlServiceSpy));
+ mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator(
+ mHdmiControlServiceSpy));
- mLocalDevices.add(mAudioSystemDevice);
- mLocalDevices.add(mPlaybackDevice);
+ 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 @@
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();
- mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
- assertTrue(mPlaybackDevice.isStandby());
- assertTrue(mAudioSystemDevice.isStandby());
- assertFalse(mPlaybackDevice.isDisabled());
- assertFalse(mAudioSystemDevice.isDisabled());
+ mPlaybackDeviceSpy.setCanGoToStandby(false);
+
+ 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 @@
Binder.setCallingWorkSourceUid(callerUid);
WorkSourceUidReadingRunnable uidReadingRunnable = new WorkSourceUidReadingRunnable();
- mHdmiControlService.runOnServiceThread(uidReadingRunnable);
+ mHdmiControlServiceSpy.runOnServiceThread(uidReadingRunnable);
Binder.setCallingWorkSourceUid(runnerUid);
@@ -666,36 +667,36 @@
@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 @@
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 c61635c..d74bff2 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.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 @@
@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 @@
}
@Test
- public void succeedWithUnknownTvDevice() {
+ public void succeedWithUnknownTvDevice() throws Exception {
+ setUp(true);
+
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
playbackDevice.init();
@@ -185,7 +193,9 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 @@
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 eebc25a..6f1268e 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.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.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.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 @@
}
}
+ @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 @@
}
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/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index a7b32ac..68a6e60 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -96,7 +96,7 @@
case ACTION_JOB_STOPPED:
mTestJobStatus.running = false;
mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = params.getStopReason();
+ mTestJobStatus.stopReason = params.getLegacyStopReason();
break;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
index 3ea86f2..87881bf 100644
--- a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
+++ b/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
@@ -47,7 +47,7 @@
int reason = params.getStopReason();
int event = TestEnvironment.EVENT_STOP_JOB;
Log.d(TAG, "stop reason: " + String.valueOf(reason));
- if (reason == JobParameters.REASON_PREEMPT) {
+ if (reason == JobParameters.STOP_REASON_PREEMPT) {
event = TestEnvironment.EVENT_PREEMPT_JOB;
Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
}
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 91342ce..8c08226 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_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 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 @@
@Override
public boolean serverBasedResumeOnReboot() {
+ if (mInjected.forceServerBased()) {
+ return true;
+ }
return mServerBased;
}
@@ -205,9 +213,20 @@
}
@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 @@
// 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 @@
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 @@
}
@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 @@
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 @@
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 fb01ff6..100d3ea 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 @@
true);
}
- /**
- * Test that when StatsProvider triggers limit reached, new limit will be calculated and
- * re-armed.
- */
- @Test
- public void testStatsProviderLimitReached() throws Exception {
- final int CYCLE_DAY = 15;
-
- final NetworkStats stats = new NetworkStats(0L, 1);
+ private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) {
stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
- 2999, 1, 2000, 1, 0);
+ 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 warning and limit reached, new quotas will be
+ * calculated and re-armed.
+ */
+ @Test
+ public void testStatsProviderWarningAndLimitReached() throws Exception {
+ final int CYCLE_DAY = 15;
+
+ final NetworkStats stats = new NetworkStats(0L, 1);
+ 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/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index ecff409..5066240 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -47,12 +47,14 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.LocalServices;
import org.junit.After;
@@ -126,6 +128,10 @@
.setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
.setExtras(new Bundle())
.build();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ Boolean.toString(false),
+ true /* makeDefault*/);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
index b09a3c3..fac5c1f 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java
@@ -22,13 +22,17 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.Context;
@@ -38,9 +42,11 @@
import android.content.pm.ShortcutManager.ShareShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.Range;
import com.android.internal.app.ChooserActivity;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
@@ -71,6 +77,14 @@
private static final String PACKAGE_3 = "pkg3";
private static final String CLASS_1 = "cls1";
private static final String CLASS_2 = "cls2";
+ private static final AppTargetEvent APP_TARGET_EVENT =
+ new AppTargetEvent.Builder(
+ new AppTarget.Builder(
+ new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)).build(),
+ AppTargetEvent.ACTION_LAUNCH)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
+ .build();
+ private static final IntentFilter INTENT_FILTER = IntentFilter.create("SEND", "text/plain");
@Mock private Context mContext;
@Mock private DataManager mDataManager;
@@ -102,17 +116,33 @@
when(mDataManager.getShareShortcuts(any(), anyInt())).thenReturn(mShareShortcuts);
when(mDataManager.getPackage(PACKAGE_1, USER_ID)).thenReturn(mPackageData1);
when(mDataManager.getPackage(PACKAGE_2, USER_ID)).thenReturn(mPackageData2);
+ when(mContext.createContextAsUser(any(), any())).thenReturn(mContext);
+ when(mContext.getSystemServiceName(AppPredictionManager.class)).thenReturn(
+ Context.APP_PREDICTION_SERVICE);
+ when(mContext.getSystemService(AppPredictionManager.class))
+ .thenReturn(new AppPredictionManager(mContext));
Bundle bundle = new Bundle();
- bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY,
- IntentFilter.create("SEND", "text/plain"));
+ bundle.putObject(ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY, INTENT_FILTER);
AppPredictionContext predictionContext = new AppPredictionContext.Builder(mContext)
.setUiSurface(UI_SURFACE_SHARE)
.setPredictedTargetCount(NUM_PREDICTED_TARGETS)
.setExtras(bundle)
.build();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.DARK_LAUNCH_REMOTE_PREDICTION_SERVICE_ENABLED,
+ Boolean.toString(true),
+ true /* makeDefault*/);
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
+ }
+
+ @Test
+ public void testReportAppTargetEvent() {
+ mPredictor.reportAppTargetEvent(APP_TARGET_EVENT);
+
+ verify(mDataManager, times(1))
+ .reportShareTargetEvent(eq(APP_TARGET_EVENT), eq(INTENT_FILTER));
}
@Test
@@ -240,7 +270,7 @@
.setExtras(new Bundle())
.build();
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
mPredictor.predictTargets();
@@ -349,7 +379,7 @@
.setExtras(new Bundle())
.build();
mPredictor = new ShareTargetPredictor(
- predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID);
+ predictionContext, mUpdatePredictionsMethod, mDataManager, USER_ID, mContext);
AppTarget appTarget1 = new AppTarget.Builder(
new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID))
.build();
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 029e9a3..8e2b207 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -48,10 +48,12 @@
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
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.role.OnRoleHoldersChangedListener;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -87,6 +89,7 @@
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;
@@ -159,7 +162,7 @@
case Context.DEVICE_POLICY_SERVICE:
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
- return new AppSearchManager(getTestContext(), mMockAppSearchManager);
+ return new AppSearchManager(this, mMockAppSearchManager);
case Context.ROLE_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
@@ -189,6 +192,12 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
+ return this;
+ }
+
+ @Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
// ignore.
@@ -196,12 +205,6 @@
}
@Override
- public Context createContextAsUser(UserHandle user, int flags) {
- when(mMockPackageManager.getUserId()).thenReturn(user.getIdentifier());
- return this;
- }
-
- @Override
public void unregisterReceiver(BroadcastReceiver receiver) {
// ignore.
}
@@ -238,6 +241,15 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ super.createContextAsUser(user, flags);
+ final ServiceContext ctx = spy(new ServiceContext());
+ when(ctx.getUser()).thenReturn(user);
+ when(ctx.getUserId()).thenReturn(user.getIdentifier());
+ return ctx;
+ }
+
+ @Override
public int getUserId() {
return UserHandle.USER_SYSTEM;
}
@@ -620,12 +632,17 @@
protected Map<String, List<PackageIdentifier>> mSchemasPackageAccessible =
new ArrayMap<>(1);
+ private Map<String, Map<String, GenericDocument>> mDocumentMap = new ArrayMap<>(1);
+
+ private String getKey(int userId, String databaseName) {
+ return new StringBuilder().append(userId).append("@").append(databaseName).toString();
+ }
@Override
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();
@@ -650,24 +667,86 @@
}
@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 {
- ignore(callback);
+ final List<GenericDocument> docs = new ArrayList<>(documentBundles.size());
+ for (Bundle bundle : documentBundles) {
+ docs.add(new GenericDocument(bundle));
+ }
+ final AppSearchBatchResult.Builder<String, Void> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ Map<String, GenericDocument> docMap = mDocumentMap.get(key);
+ for (GenericDocument doc : docs) {
+ builder.setSuccess(doc.getUri(), null);
+ if (docMap == null) {
+ docMap = new ArrayMap<>(1);
+ mDocumentMap.put(key, docMap);
+ }
+ docMap.put(doc.getUri(), doc);
+ }
+ callback.onResult(builder.build());
}
@Override
public void getDocuments(String packageName, String databaseName, String namespace,
List<String> uris, Map<String, List<String>> typePropertyPaths, int userId,
IAppSearchBatchResultCallback callback) throws RemoteException {
- ignore(callback);
+ final AppSearchBatchResult.Builder<String, Bundle> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ for (String uri : uris) {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ key + " not found when getting: " + uri);
+ }
+ } else {
+ final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+ for (String uri : uris) {
+ if (docs.containsKey(uri)) {
+ builder.setSuccess(uri, docs.get(uri).getBundle());
+ } else {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "shortcut not found: " + uri);
+ }
+ }
+ }
+ callback.onResult(builder.build());
}
@Override
public void query(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
+ return;
+ }
+ final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values());
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 0);
+ final ArrayList<Bundle> resultBundles = new ArrayList<>();
+ for (GenericDocument document : documents) {
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putBundle("document", document.getBundle());
+ resultBundle.putString("packageName", packageName);
+ resultBundle.putString("databaseName", databaseName);
+ resultBundle.putParcelableArrayList("matches", new ArrayList<>());
+ resultBundles.add(resultBundle);
+ }
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
}
@Override
@@ -679,7 +758,10 @@
@Override
public void getNextPage(long nextPageToken, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final Bundle page = new Bundle();
+ page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1);
+ page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>());
+ callback.onResult(AppSearchResult.newSuccessfulResult(page));
}
@Override
@@ -688,8 +770,24 @@
}
@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);
}
@@ -698,14 +796,40 @@
public void removeByUri(String packageName, String databaseName, String namespace,
List<String> uris, int userId, IAppSearchBatchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final AppSearchBatchResult.Builder<String, Void> builder =
+ new AppSearchBatchResult.Builder<>();
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ for (String uri : uris) {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "package " + key + " not found when removing " + uri);
+ }
+ } else {
+ final Map<String, GenericDocument> docs = mDocumentMap.get(key);
+ for (String uri : uris) {
+ if (docs.containsKey(uri)) {
+ docs.remove(uri);
+ builder.setSuccess(uri, null);
+ } else {
+ builder.setFailure(uri, AppSearchResult.RESULT_NOT_FOUND,
+ "shortcut not found when removing " + uri);
+ }
+ }
+ }
+ callback.onResult(builder.build());
}
@Override
public void removeByQuery(String packageName, String databaseName, String queryExpression,
Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
throws RemoteException {
- ignore(callback);
+ final String key = getKey(userId, databaseName);
+ if (!mDocumentMap.containsKey(key)) {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ return;
+ }
+ mDocumentMap.get(key).clear();
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
}
@Override
@@ -724,12 +848,12 @@
return null;
}
- private void ignore(IAppSearchResultCallback callback) throws RemoteException {
- callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ private void removeShortcuts() {
+ mDocumentMap.clear();
}
- private void ignore(IAppSearchBatchResultCallback callback) throws RemoteException {
- callback.onResult(new AppSearchBatchResult.Builder().build());
+ private void ignore(IAppSearchResultCallback callback) throws RemoteException {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
}
}
@@ -1146,6 +1270,9 @@
shutdownServices();
+ mMockAppSearchManager.removeShortcuts();
+ mMockAppSearchManager = null;
+
super.tearDown();
}
@@ -1891,6 +2018,11 @@
return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
}
+ protected void updatePackageShortcut(String packageName, String shortcutId, int userId,
+ Consumer<ShortcutInfo> cb) {
+ mService.updatePackageShortcutForTest(packageName, shortcutId, userId, cb);
+ }
+
protected void assertShortcutExists(String packageName, String shortcutId, int userId) {
assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
}
@@ -2086,6 +2218,10 @@
return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
}
+ protected void updateCallerShortcut(String shortcutId, Consumer<ShortcutInfo> cb) {
+ updatePackageShortcut(getCallingPackage(), shortcutId, getCallingUserId(), cb);
+ }
+
protected List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
final List<ShortcutInfo>[] ret = new List[1];
runWithCaller(launcher, userId, () -> {
@@ -2245,6 +2381,8 @@
deleteAllSavedFiles();
+ mMockAppSearchManager.removeShortcuts();
+
initService();
mService.applyRestore(payload, USER_0);
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 d63a467..a231169 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 @@
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 @@
.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/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 4d0beef..3f680e6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1385,6 +1385,7 @@
mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
+ Log.d("ShortcutManagerTest1", si.toString());
assertTrue(si.hasIconFile());
});
@@ -1702,8 +1703,8 @@
// Because setDynamicShortcuts will update the timestamps when ranks are changing,
// we explicitly set timestamps here.
- getCallerShortcut("s1").setTimestamp(5000);
- getCallerShortcut("s2").setTimestamp(1000);
+ updateCallerShortcut("s1", si -> si.setTimestamp(5000));
+ updateCallerShortcut("s2", si -> si.setTimestamp(1000));
setCaller(CALLING_PACKAGE_2);
final ShortcutInfo s2_2 = makeShortcut("s2");
@@ -1713,9 +1714,9 @@
makeComponent(ShortcutActivity.class));
assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
- getCallerShortcut("s2").setTimestamp(1500);
- getCallerShortcut("s3").setTimestamp(3000);
- getCallerShortcut("s4").setTimestamp(500);
+ updateCallerShortcut("s2", si -> si.setTimestamp(1500));
+ updateCallerShortcut("s3", si -> si.setTimestamp(3000));
+ updateCallerShortcut("s4", si -> si.setTimestamp(500));
setCaller(CALLING_PACKAGE_3);
final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
@@ -1723,7 +1724,7 @@
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
- getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
+ updateCallerShortcut("s3", si -> si.setTimestamp(START_TIME + 5000));
setCaller(LAUNCHER_1);
@@ -7686,7 +7687,7 @@
assertEquals("http://www/", si.getIntent().getData().toString());
assertEquals("foo/bar", si.getIntent().getType());
assertEquals(
- new ComponentName("abc", ".xyz"), si.getIntent().getComponent());
+ new ComponentName("abc", "abc.xyz"), si.getIntent().getComponent());
assertEquals(set("cat1", "cat2"), si.getIntent().getCategories());
assertEquals("value1", si.getIntent().getStringExtra("key1"));
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 322e448..826a8d4 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 @@
}
@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 @@
}
@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 @@
}
@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 395b643..fc26611 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.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 @@
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 6e5fbd0..2df6c5a 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.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 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 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 @@
@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 324e592..7903a90 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.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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 a894178..27e953f 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 @@
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 @@
mLockSettingsInternal = lockSettingsInternal;
mIBootControl = bootControl;
mIMetricsReporter = metricsReporter;
+ mSharedPreferences = preferences;
}
@Override
@@ -114,12 +117,14 @@
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 @@
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 @@
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 1068270..742f503 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 @@
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/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index 5a100a2..a0e9d977 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -72,7 +72,6 @@
private TestCallback mTestCallback;
private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider;
- private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
@Before
public void setUp() {
@@ -84,13 +83,10 @@
};
mTestThreadingDomain = new TestThreadingDomain();
mTestCallback = new TestCallback(mTestThreadingDomain);
- mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
- stubbedProviderMetricsLogger, mTestThreadingDomain, "primary",
- mTimeZoneAvailabilityChecker);
+ stubbedProviderMetricsLogger, mTestThreadingDomain, "primary");
mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider(
- stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary",
- mTimeZoneAvailabilityChecker);
+ stubbedProviderMetricsLogger, mTestThreadingDomain, "secondary");
}
@Test
@@ -1185,10 +1181,9 @@
* Creates the instance.
*/
TestLocationTimeZoneProvider(ProviderMetricsLogger providerMetricsLogger,
- ThreadingDomain threadingDomain, String providerName,
- TimeZoneIdValidator timeZoneIdValidator) {
+ ThreadingDomain threadingDomain, String providerName) {
super(providerMetricsLogger, threadingDomain, providerName,
- timeZoneIdValidator);
+ new FakeTimeZoneProviderEventPreProcessor());
}
public void setFailDuringInitialization(boolean failInitialization) {
@@ -1321,14 +1316,4 @@
mTestProviderState.commitLatest();
}
}
-
- private static final class FakeTimeZoneIdValidator
- implements LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return true;
- }
-
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
new file mode 100644
index 0000000..e75d05c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.timezonedetector.location;
+
+/**
+ * Fake implementation of {@link TimeZoneProviderEventPreProcessor} which assumes that all events
+ * are valid or always uncertain if {@link #enterUncertainMode()} was called.
+ */
+public final class FakeTimeZoneProviderEventPreProcessor
+ implements TimeZoneProviderEventPreProcessor {
+
+ private boolean mIsUncertain = false;
+
+ @Override
+ public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
+ if (mIsUncertain) {
+ return TimeZoneProviderEvent.createUncertainEvent();
+ }
+ return timeZoneProviderEvent;
+ }
+
+ public void enterUncertainMode() {
+ mIsUncertain = true;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index d13a04e..0edb559 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -52,10 +52,8 @@
import java.time.Duration;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -68,13 +66,13 @@
private TestThreadingDomain mTestThreadingDomain;
private TestProviderListener mProviderListener;
- private FakeTimeZoneIdValidator mTimeZoneAvailabilityChecker;
+ private FakeTimeZoneProviderEventPreProcessor mTimeZoneProviderEventPreProcessor;
@Before
public void setUp() {
mTestThreadingDomain = new TestThreadingDomain();
mProviderListener = new TestProviderListener();
- mTimeZoneAvailabilityChecker = new FakeTimeZoneIdValidator();
+ mTimeZoneProviderEventPreProcessor = new FakeTimeZoneProviderEventPreProcessor();
}
@Test
@@ -82,9 +80,10 @@
String providerName = "arbitrary";
RecordingProviderMetricsLogger providerMetricsLogger = new RecordingProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
- mTimeZoneAvailabilityChecker.validIds("Europe/London");
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
// initialize()
provider.initialize(mProviderListener);
@@ -174,8 +173,10 @@
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
TestCommand testCommand = TestCommand.createForTests("test", new Bundle());
AtomicReference<Bundle> resultReference = new AtomicReference<>();
@@ -193,10 +194,11 @@
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
- mTimeZoneAvailabilityChecker.validIds("Europe/London");
// initialize()
provider.initialize(mProviderListener);
@@ -234,14 +236,17 @@
}
@Test
- public void considerSuggestionWithInvalidTimeZoneIdsAsUncertain() {
+ public void entersUncertainState_whenEventHasUnsupportedZones() {
String providerName = "primary";
StubbedProviderMetricsLogger providerMetricsLogger = new StubbedProviderMetricsLogger();
TestLocationTimeZoneProvider provider = new TestLocationTimeZoneProvider(
- providerMetricsLogger, mTestThreadingDomain, providerName,
- mTimeZoneAvailabilityChecker);
+ providerMetricsLogger,
+ mTestThreadingDomain,
+ providerName,
+ mTimeZoneProviderEventPreProcessor);
provider.setStateChangeRecordingEnabled(true);
provider.initialize(mProviderListener);
+ mTimeZoneProviderEventPreProcessor.enterUncertainMode();
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
@@ -309,8 +314,9 @@
TestLocationTimeZoneProvider(@NonNull ProviderMetricsLogger providerMetricsLogger,
@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName,
- @NonNull TimeZoneIdValidator timeZoneIdValidator) {
- super(providerMetricsLogger, threadingDomain, providerName, timeZoneIdValidator);
+ @NonNull TimeZoneProviderEventPreProcessor timeZoneProviderEventPreProcessor) {
+ super(providerMetricsLogger,
+ threadingDomain, providerName, timeZoneProviderEventPreProcessor);
}
@Override
@@ -367,20 +373,6 @@
}
}
- private static final class FakeTimeZoneIdValidator
- implements LocationTimeZoneProvider.TimeZoneIdValidator {
- private final Set<String> mValidTimeZoneIds = new HashSet<>();
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return mValidTimeZoneIds.contains(timeZoneId);
- }
-
- public void validIds(String... timeZoneIdss) {
- mValidTimeZoneIds.addAll(asList(timeZoneIdss));
- }
- }
-
private static class StubbedProviderMetricsLogger implements
LocationTimeZoneProvider.ProviderMetricsLogger {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
deleted file mode 100644
index 5561b2c..0000000
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidatorTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.timezonedetector.location;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.TimeZone;
-
-@Presubmit
-public class ZoneInfoDbTimeZoneIdValidatorTest {
- private final LocationTimeZoneProvider.TimeZoneIdValidator mTzChecker =
- new ZoneInfoDbTimeZoneIdValidator();
-
- @Test
- public void timeZoneIdsFromZoneInfoDbAreValid() {
- for (String timeZone : TimeZone.getAvailableIDs()) {
- assertWithMessage("Time zone %s should be supported", timeZone)
- .that(mTzChecker.isValid(timeZone)).isTrue();
- }
- }
-
- @Test
- public void nonExistingZones_areNotSupported() {
- List<String> nonExistingTimeZones = Arrays.asList(
- "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30"
- );
-
- for (String timeZone : nonExistingTimeZones) {
- assertWithMessage(timeZone + " is not a valid time zone")
- .that(mTzChecker.isValid(timeZone))
- .isFalse();
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
new file mode 100644
index 0000000..173705b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector.location;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Presubmit;
+import android.service.timezone.TimeZoneProviderSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.TimeZone;
+
+/** Tests for {@link ZoneInfoDbTimeZoneProviderEventPreProcessor}. */
+@Presubmit
+public class ZoneInfoDbTimeZoneProviderEventPreProcessorTest {
+
+ private static final long ARBITRARY_TIME_MILLIS = 11223344;
+
+ private final ZoneInfoDbTimeZoneProviderEventPreProcessor mPreProcessor =
+ new ZoneInfoDbTimeZoneProviderEventPreProcessor();
+
+ @Test
+ public void timeZoneIdsFromZoneInfoDbAreValid() {
+ for (String timeZone : TimeZone.getAvailableIDs()) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ assertWithMessage("Time zone %s should be supported", timeZone)
+ .that(mPreProcessor.preProcess(event)).isEqualTo(event);
+ }
+ }
+
+ @Test
+ public void eventWithNonExistingZones_areMappedToUncertainEvent() {
+ List<String> nonExistingTimeZones = Arrays.asList(
+ "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
+
+ for (String timeZone : nonExistingTimeZones) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+
+ assertWithMessage(timeZone + " is not a valid time zone")
+ .that(mPreProcessor.preProcess(event))
+ .isEqualTo(TimeZoneProviderEvent.createUncertainEvent());
+ }
+ }
+
+ private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
+ return TimeZoneProviderEvent.createSuggestionEvent(
+ new TimeZoneProviderSuggestion.Builder()
+ .setTimeZoneIds(Arrays.asList(timeZoneIds))
+ .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
+ .build());
+ }
+
+}
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 86b1620..8991e9f 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 @@
.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 0000000..dcff479
--- /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 b54b696..1e3c344 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.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 @@
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 @@
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 @@
* 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 b6c11fe..59c0b0e 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 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.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 @@
}
@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);
+ PrebakedSegment effect = new PrebakedSegment(VibrationEffect.EFFECT_CLICK,
+ /* shouldFallback= */ false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
- 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);
-
- VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
+ PrebakedSegment scaled = mVibrationScaler.scale(
effect, VibrationAttributes.USAGE_NOTIFICATION);
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
@@ -196,25 +161,33 @@
}
@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 effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
- VibrationEffect.Prebaked scaled = mVibrationScaler.scale(
- effect, VibrationAttributes.USAGE_NOTIFICATION);
- VibrationEffect.Prebaked scaledFallback1 =
- (VibrationEffect.Prebaked) scaled.getFallbackEffect();
- VibrationEffect.OneShot scaledFallback2 =
- (VibrationEffect.OneShot) scaledFallback1.getFallbackEffect();
+ PrebakedSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ effect, VibrationAttributes.USAGE_NOTIFICATION));
assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertEquals(scaledFallback1.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
- assertTrue(scaledFallback2.getAmplitude() > 0);
+
+ 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);
}
@Test
@@ -224,20 +197,20 @@
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 @@
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 @@
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 7d5eec0..37e0ec2 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.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.List;
import java.util.Map;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Tests for {@link VibrationThread}.
@@ -142,8 +147,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 VibrationEffect expectedPrebaked(int effectId) {
- return new VibrationEffect.Prebaked(effectId, false,
- VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ 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 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 bad3e4c..70ea219 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.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 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 @@
@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 @@
@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 @@
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 @@
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));
+ verify(mNativeWrapperMock).compose(eq(primitives), 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);
+ @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 @@
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 ce6639c..12ced38 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.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 @@
.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 @@
.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 @@
.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 @@
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 c6e35cf..e932905 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 @@
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 0000000..d825dfd
--- /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 5462f47..ff88174 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 @@
@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/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 07475e9..be6e801 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1111,7 +1111,7 @@
when(service.asBinder()).thenReturn(mock(IBinder.class));
ManagedServices services = new TestManagedServices(getContext(), mLock, mUserProfiles,
mIpm, APPROVAL_BY_PACKAGE);
- services.registerSystemService(service, null, 10);
+ services.registerSystemService(service, null, 10, 1000);
ManagedServices.ManagedServiceInfo info = services.checkServiceTokenLocked(service);
info.isSystem = true;
@@ -1163,10 +1163,10 @@
ManagedServices.ManagedServiceInfo service0 = service.new ManagedServiceInfo(
iInterface, ComponentName.unflattenFromString("a/a"), 0, false,
- mock(ServiceConnection.class), 26);
+ mock(ServiceConnection.class), 26, 34);
ManagedServices.ManagedServiceInfo service10 = service.new ManagedServiceInfo(
iInterface, ComponentName.unflattenFromString("b/b"), 10, false,
- mock(ServiceConnection.class), 26);
+ mock(ServiceConnection.class), 26, 345);
Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>();
removableBoundServices.add(service0);
removableBoundServices.add(service10);
@@ -1199,13 +1199,13 @@
ManagedServices.ManagedServiceInfo service0 = service.new ManagedServiceInfo(
iInterface, ComponentName.unflattenFromString("a/a"), 0, false,
- mock(ServiceConnection.class), 26);
+ mock(ServiceConnection.class), 26, 345);
ManagedServices.ManagedServiceInfo service0a = service.new ManagedServiceInfo(
iInterface, ComponentName.unflattenFromString("c/c"), 0, false,
- mock(ServiceConnection.class), 26);
+ mock(ServiceConnection.class), 26, 3456);
ManagedServices.ManagedServiceInfo service10 = service.new ManagedServiceInfo(
iInterface, ComponentName.unflattenFromString("b/b"), 10, false,
- mock(ServiceConnection.class), 26);
+ mock(ServiceConnection.class), 26, 34567);
Set<ManagedServices.ManagedServiceInfo> removableBoundServices = new ArraySet<>();
removableBoundServices.add(service0);
removableBoundServices.add(service0a);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index b9ffd65..a37d5c8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -31,9 +31,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.media.session.MediaSession;
import android.os.Build;
-import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
@@ -73,7 +71,6 @@
private NotificationRecord mRecordMinCallNonInterruptive;
private NotificationRecord mRecordMinCall;
private NotificationRecord mRecordHighCall;
- private NotificationRecord mRecordDefaultMedia;
private NotificationRecord mRecordEmail;
private NotificationRecord mRecordInlineReply;
private NotificationRecord mRecordSms;
@@ -139,15 +136,6 @@
new UserHandle(userId), "", 1999), getDefaultChannel());
mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
- Notification n3 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
- .setStyle(new Notification.MediaStyle()
- .setMediaSession(new MediaSession.Token(Process.myUid(), null)))
- .build();
- mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
- "", 1499), getDefaultChannel());
- mRecordDefaultMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
-
Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setStyle(new Notification.MessagingStyle("sender!")).build();
mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
@@ -218,7 +206,7 @@
.setStyle(new Notification.MediaStyle())
.build();
mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification(
- pkg2, pkg2, 1, "cheater", uid2, uid2, n12, new UserHandle(userId),
+ pkg2, pkg2, 1, "media", uid2, uid2, n12, new UserHandle(userId),
"", 9258), getDefaultChannel());
mNoMediaSessionMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
@@ -247,7 +235,6 @@
final List<NotificationRecord> expected = new ArrayList<>();
expected.add(mRecordColorizedCall);
expected.add(mRecordColorized);
- expected.add(mRecordDefaultMedia);
expected.add(mRecordHighCall);
expected.add(mRecordInlineReply);
if (mRecordSms != null) {
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 ec3a1af..55ebe11 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -469,7 +469,7 @@
when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
mListener = mListeners.new ManagedServiceInfo(
null, new ComponentName(PKG, "test_class"),
- UserHandle.getUserId(mUid), true, null, 0);
+ UserHandle.getUserId(mUid), true, null, 0, 123);
ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
ArraySet<ComponentName> components = new ArraySet<>();
components.add(defaultComponent);
@@ -900,10 +900,10 @@
@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 @@
.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));
@@ -2869,7 +2869,7 @@
snoozeNotificationRunnable.run();
ManagedServices.ManagedServiceInfo listener = mListeners.new ManagedServiceInfo(
- null, new ComponentName(PKG, "test_class"), mUid, true, null, 0);
+ null, new ComponentName(PKG, "test_class"), mUid, true, null, 0, 234);
listener.isSystem = true;
when(mListeners.checkServiceTokenLocked(any())).thenReturn(listener);
@@ -2882,7 +2882,7 @@
@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 @@
@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 @@
@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 @@
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 @@
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 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 @@
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 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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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());
}
@@ -4910,6 +4911,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4933,6 +4935,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4952,6 +4955,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4974,11 +4978,12 @@
}
@Test
- public void testToastRateLimiterCanPreventsShowCallForCustomToast() throws Exception {
+ public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -4995,12 +5000,36 @@
}
@Test
+ public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(false); // rate limit reached
+ // Avoids rate limiting.
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ .thenReturn(false);
+
+ setAppInForegroundForToasts(mUid, true);
+
+ Binder token = new Binder();
+ ITransientNotification callback = mock(ITransientNotification.class);
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ nmService.enqueueToast(testPackage, token, callback, 2000, 0);
+ verify(callback).show(any());
+ }
+
+ @Test
public void testCustomToastPostedWhileInForeground_blockedIfAppGoesToBackground()
throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5034,6 +5063,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5053,6 +5083,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5072,6 +5103,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5095,11 +5127,12 @@
}
@Test
- public void testToastRateLimiterCanPreventsShowCallForTextToast() throws Exception {
+ public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5114,12 +5147,32 @@
}
@Test
+ public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ .thenReturn(false);
+
+ Binder token = new Binder();
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+ verify(mStatusBar).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+ }
+
+ @Test
public void backgroundSystemCustomToast_callsSetProcessImportantAsForegroundForToast() throws
Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = true;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5145,6 +5198,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5166,6 +5220,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5186,6 +5241,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5203,6 +5259,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5224,6 +5281,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5247,6 +5305,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = true;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5270,6 +5329,7 @@
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5305,6 +5365,13 @@
.thenReturn(isWithinQuota);
}
+ private void setIfPackageHasPermissionToAvoidToastRateLimiting(
+ String pkg, boolean hasPermission) throws Exception {
+ when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
+ pkg, UserHandle.getUserId(mUid)))
+ .thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
+ }
+
@Test
public void testOnPanelRevealedAndHidden() {
int items = 5;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
index 3b15376..1116204 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java
@@ -316,9 +316,10 @@
mScheduleCalendar.setSchedule(mScheduleInfo);
mScheduleInfo.nextAlarm = 2000;
+ // next alarm updated to 3000 (alarm for 2000 was changed to 3000)
mScheduleCalendar.maybeSetNextAlarm(1000, 3000);
- assertEquals(2000, mScheduleInfo.nextAlarm);
+ assertEquals(3000, mScheduleInfo.nextAlarm);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 5a527a2..7446e9e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -279,17 +279,17 @@
conditionId, cal, now.getTimeInMillis(), now.getTimeInMillis() + 500);
assertEquals(Condition.STATE_TRUE, condition.state);
- // in schedule, update with later alarm time, should be in dnd
+ // in schedule, update with nextAlarm = later alarm time (1000), should be in dnd
condition = mService.evaluateSubscriptionLocked(
conditionId, cal, now.getTimeInMillis() + 250, now.getTimeInMillis() + 1000);
assertEquals(Condition.STATE_TRUE, condition.state);
- // at earliest alarm fire time, should exit dnd
- assertTrue(cal.isInSchedule(now.getTimeInMillis() + 500));
+ // at next alarm fire time (1000), should exit dnd
+ assertTrue(cal.isInSchedule(now.getTimeInMillis() + 1000));
assertTrue("" + info.nextAlarm + " " + now.getTimeInMillis(),
- cal.shouldExitForAlarm(now.getTimeInMillis() + 500));
+ cal.shouldExitForAlarm(now.getTimeInMillis() + 1000));
condition = mService.evaluateSubscriptionLocked(
- conditionId, cal, now.getTimeInMillis() + 500, 0);
+ conditionId, cal, now.getTimeInMillis() + 1000, 0);
assertEquals(Condition.STATE_FALSE, condition.state);
}
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 1700707..0000000
--- 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/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 002859e..7c9ff79 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -38,6 +38,7 @@
import android.app.ActivityOptions.SourceInfo;
import android.app.WaitResult;
import android.content.Intent;
+import android.os.IBinder;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
@@ -402,6 +403,26 @@
}
@Test
+ public void testConsecutiveLaunchNewTask() {
+ final IBinder launchCookie = mock(IBinder.class);
+ mTrampolineActivity.noDisplay = true;
+ mTrampolineActivity.mLaunchCookie = launchCookie;
+ onActivityLaunched(mTrampolineActivity);
+ final ActivityRecord activityOnNewTask = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+ mActivityMetricsLogger.notifyActivityLaunching(activityOnNewTask.intent,
+ mTrampolineActivity /* caller */);
+ notifyActivityLaunched(START_SUCCESS, activityOnNewTask);
+
+ transitToDrawnAndVerifyOnLaunchFinished(activityOnNewTask);
+ assertWithMessage("Trampoline's cookie must be transferred").that(
+ mTrampolineActivity.mLaunchCookie).isNull();
+ assertWithMessage("The last launch task has the transferred cookie").that(
+ activityOnNewTask.mLaunchCookie).isEqualTo(launchCookie);
+ }
+
+ @Test
public void testConsecutiveLaunchOnDifferentDisplay() {
onActivityLaunched(mTopActivity);
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 ce5fc40..678defe3 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 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 83aca5e..c3279bf 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 @@
}
@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 @@
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 32e5200..0afd39f 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;
@@ -46,6 +48,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -64,6 +67,7 @@
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;
@@ -329,21 +333,6 @@
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();
@@ -364,29 +353,29 @@
}
/**
- * 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());
}
@@ -438,7 +427,7 @@
}
/**
- * 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() {
@@ -446,16 +435,16 @@
// 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();
@@ -903,13 +892,13 @@
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);
@@ -948,28 +937,28 @@
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());
}
@@ -1267,6 +1256,42 @@
is(Configuration.ORIENTATION_PORTRAIT));
}
+ @Test
+ public void testHybridRotationAnimation() {
+ final DisplayContent displayContent = mDefaultDisplay;
+ final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
+ final WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ final WindowState[] windows = { statusBar, navBar, app };
+ makeWindowVisible(windows);
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
+ displayPolicy.addWindowLw(navBar, navBar.mAttrs);
+ final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
+ displayContent.getRotation());
+ spyOn(rotationAnim);
+ // Assume that the display rotation is changed so it is frozen in preparation for animation.
+ doReturn(true).when(rotationAnim).hasScreenshot();
+ mWm.mDisplayFrozen = true;
+ displayContent.setRotationAnimation(rotationAnim);
+ // The fade rotation animation also starts to hide some non-app windows.
+ assertNotNull(displayContent.getFadeRotationAnimationController());
+ assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
+
+ for (WindowState w : windows) {
+ w.setOrientationChanging(true);
+ }
+ // The display only waits for the app window to unfreeze.
+ assertFalse(displayContent.waitForUnfreeze(statusBar));
+ assertFalse(displayContent.waitForUnfreeze(navBar));
+ assertTrue(displayContent.waitForUnfreeze(app));
+ // If all windows animated by fade rotation animation have done the orientation change,
+ // the animation controller should be cleared.
+ statusBar.setOrientationChanging(false);
+ navBar.setOrientationChanging(false);
+ assertNull(displayContent.getFadeRotationAnimationController());
+ }
+
@UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR })
@Test
public void testApplyTopFixedRotationTransform() {
@@ -1275,6 +1300,7 @@
doReturn(false).when(displayPolicy).navigationBarCanMove();
displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+ makeWindowVisible(mStatusBarWindow, mNavBarWindow);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
@@ -1297,7 +1323,7 @@
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
- assertNotNull(mDisplayContent.getFixedRotationAnimationController());
+ assertNotNull(mDisplayContent.getFadeRotationAnimationController());
assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
ANIMATION_TYPE_FIXED_TRANSFORM));
assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
@@ -1381,7 +1407,7 @@
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
- assertNull(mDisplayContent.getFixedRotationAnimationController());
+ assertNull(mDisplayContent.getFadeRotationAnimationController());
}
@Test
@@ -1660,66 +1686,6 @@
}
@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);
@@ -1738,8 +1704,8 @@
@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");
@@ -1751,8 +1717,8 @@
@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
@@ -1765,8 +1731,8 @@
@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");
@@ -1777,8 +1743,8 @@
@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");
@@ -1805,7 +1771,7 @@
@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 -> {
@@ -1814,7 +1780,7 @@
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);
}
@@ -2049,6 +2015,130 @@
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 4e2697a..1bddd7b 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 @@
*/
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 e9c356d..e9907c1 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 @@
}
@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 925b6f9..6ffdb09 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 @@
}
@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 @@
}
@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,9 +278,33 @@
}
@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)
+ .build();
+ Task task2 = createTaskBuilder(".Task1")
+ .setFlags(FLAG_ACTIVITY_NEW_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).hasSize(1);
+ assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+ }
+
+ @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();
@@ -297,8 +321,7 @@
assertThat(mCallbacksRecorder.mAdded).hasSize(1);
assertThat(mCallbacksRecorder.mAdded).contains(task2);
assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
- assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
- assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+ assertThat(mCallbacksRecorder.mRemoved).isEmpty();
}
@Test
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 7a4ad74..7d137bc 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 @@
@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();
@@ -556,11 +556,11 @@
}
@Test
- public void testNotAttachNavigationBar_controlledByFixedRotationAnimation() {
+ public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
setupForShouldAttachNavBarDuringTransition();
- FixedRotationAnimationController mockController =
- mock(FixedRotationAnimationController.class);
- doReturn(mockController).when(mDefaultDisplay).getFixedRotationAnimationController();
+ FadeRotationAnimationController mockController =
+ mock(FadeRotationAnimationController.class);
+ doReturn(mockController).when(mDefaultDisplay).getFadeRotationAnimationController();
final ActivityRecord homeActivity = createHomeActivity();
initializeRecentsAnimationController(mController, homeActivity);
assertFalse(mController.isNavigationBarAttachedToApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 956c277..37da529 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -578,10 +578,10 @@
}
@Test
- public void testNonAppTarget_notSendNavBar_controlledByFixedRotation() throws Exception {
- final FixedRotationAnimationController mockController =
- mock(FixedRotationAnimationController.class);
- doReturn(mockController).when(mDisplayContent).getFixedRotationAnimationController();
+ public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
+ final FadeRotationAnimationController mockController =
+ mock(FadeRotationAnimationController.class);
+ doReturn(mockController).when(mDisplayContent).getFadeRotationAnimationController();
final int transit = TRANSIT_OLD_TASK_OPEN;
setupForNonAppTargetNavBar(transit, true);
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 8388f2a..0000000
--- 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
similarity index 60%
rename from services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
rename to services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 3a7954b..748622b 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_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_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 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.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 @@
}
@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 @@
@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 @@
// 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 @@
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 @@
@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 @@
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 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 @@
}
@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 @@
}
@Test
- public void testStackOverridesDisplayWindowingMode() {
+ public void testRootTaskOverridesDisplayWindowingMode() {
final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -358,95 +533,92 @@
}
@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 */));
+ assertTrue(homeRootTask.shouldBeVisible(null /* starting */));
+ assertTrue(pinnedRootTask.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 */));
+ 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 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 */));
+ // 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 @@
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 @@
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 @@
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 @@
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 @@
@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 */);
+ 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(homeStack).isTranslucent(any());
+ 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 @@
// 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 */);
-
- doReturn(false).when(homeStack).isTranslucent(any());
- doReturn(false).when(fullscreenStack).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));
- }
-
- @Test
- public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() {
- final Task homeStack = createStackForShouldBeVisibleTest(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(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_NoMoveHomeOnTop() {
- final Task fullscreenStack = createStackForShouldBeVisibleTest(
- mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
- true /* onTop */);
- final Task homeStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ public void testMoveHomeRootTaskBehindBottomMostVisible_NoMoveHomeBehindTranslucent() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, 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(true).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 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_MoveHomeBehindFullscreen() {
- 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 */);
- 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,
+
+ doReturn(false).when(homeRootTask).isTranslucent(any());
+ doReturn(false).when(fullscreenRootTask).isTranslucent(any());
+
+ // 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 testMoveHomeRootTaskBehindBottomMostVisible_MoveHomeBehindFullscreen() {
+ final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, 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 */);
+ 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));
+ // Ensure non always on top root task is put below always on top root tasks.
+ assertEquals(alwaysOnTopRootTask, getRootTaskAbove(nonAlwaysOnTopRootTask));
- 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));
+ 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));
- 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);
+ 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 @@
}
@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 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 @@
// 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 @@
}
@Test
- public void testStackOrderChangedOnAddPositionStack() {
+ public void testRootTaskOrderChangedOnAddPositionRootTask() {
final Task task = new TaskBuilder(mSupervisor).build();
mDefaultTaskDisplayArea.removeRootTask(task);
@@ -1260,14 +1412,14 @@
}
@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 @@
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 @@
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 1b114c1..20bced2 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 @@
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 eba5634..92d4ede 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_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 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 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 @@
@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 @@
@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 @@
@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 @@
}
@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);
+ 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());
- final WindowContainer taskStackContainer = stack1.getParent();
+ // 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 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);
+ final WindowContainer taskContainer = rootTask1.getParent();
- 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);
+ 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);
- taskStackContainer.positionChildAt(1, mPinnedTask, false);
- assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
- assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
+ 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());
- final WindowContainer taskStackContainer = stack1.getParent();
+ // Test that no root task can be above pinned root task.
+ final Task rootTask1 = createTask(mDisplayContent);
- final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
- final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedTask);
- assertThat(pinnedStackPos).isGreaterThan(stackPos);
+ final WindowContainer taskContainer = rootTask1.getParent();
- taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
- assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
- assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedTask);
+ final int rootTaskPos = taskContainer.mChildren.indexOf(rootTask1);
+ final int pinnedTaskPos = taskContainer.mChildren.indexOf(pinnedTask);
+ assertThat(pinnedTaskPos).isGreaterThan(rootTaskPos);
- taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, 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);
+
+ 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 @@
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 @@
@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 @@
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 @@
@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 @@
}
@Test
- public void testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange() {
+ public void testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange() {
final RootWindowContainer rootWindowContainer = mWm.mAtmService.mRootWindowContainer;
final TaskDisplayArea defaultTaskDisplayArea =
rootWindowContainer.getDefaultTaskDisplayArea();
@@ -330,7 +328,7 @@
ActivityRecord primarySplitActivity = primarySplitTask.getTopNonFinishingActivity();
assertNotNull(primarySplitActivity);
primarySplitActivity.setState(RESUMED,
- "testGetOrientation_nonResizableHomeStackWithHomeActivityPendingVisibilityChange");
+ "testGetOrientation_nonResizableHomeTaskWithHomeActivityPendingVisibilityChange");
ActivityRecord homeActivity = rootHomeTask.getTopNonFinishingActivity();
if (homeActivity == null) {
@@ -350,14 +348,14 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
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 ed57294..de4c40d 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 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 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 @@
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 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 @@
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 @@
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 @@
}
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 d853b93..0000000
--- 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 e58c162..0000000
--- 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 dca6b08..2389d2d 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.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 @@
* 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 @@
@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 @@
@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 @@
} 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 @@
@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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 79ef868..2dfb3a1 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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 b88173d..6919c4c 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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 6d0e510..baf072d 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 @@
}
@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 @@
}
@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 @@
}
@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 01c503e..e2b58bc 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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
@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 @@
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 @@
@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 @@
@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 @@
// 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 @@
// 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 @@
@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 @@
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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 5b5b1da..bfbe203 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 @@
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 b210dfb..e9e0c99 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 @@
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)
+ /** 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 */);
+ }
+
+ /** 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();
+ }
}
- Task createTaskStackOnTaskDisplayArea(int windowingMode, int activityType,
- TaskDisplayArea tda) {
- return new TaskBuilder(tda.mDisplayContent.mAtmService.mTaskSupervisor)
- .setTaskDisplayArea(tda)
- .setWindowingMode(windowingMode)
- .setActivityType(activityType)
- .setIntent(new Intent())
- .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 @@
*/
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 @@
*/
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 @@
.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 @@
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 @@
.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 e2585e5..ed5f1d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_IME;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -37,6 +38,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.window.WindowContext;
import androidx.test.filters.SmallTest;
@@ -207,7 +209,7 @@
/**
* 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
@@ -250,4 +252,29 @@
verify(selectFunc).apply(token2.windowType, options);
}
+
+ /**
+ * Test that {@link WindowToken#setInsetsFrozen(boolean)} will set the frozen insets
+ * states for its children windows and by default it shouldn't let IME window setting
+ * the frozen insets state even the window of the window token is the IME layering target.
+ */
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testSetInsetsFrozen_notAffectImeWindowState() {
+ // Pre-condition: make the IME window be controlled by IME insets provider.
+ mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow(
+ mDisplayContent.mInputMethodWindow, null, null);
+
+ // Simulate an app window to be the IME layering target, assume the app window has no
+ // frozen insets state by default.
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ mDisplayContent.setImeLayeringTarget(app);
+ assertNull(app.getFrozenInsetsState());
+ assertTrue(app.isImeLayeringTarget());
+
+ // Verify invoking setInsetsFrozen shouldn't let IME window setting the frozen insets state.
+ app.mToken.setInsetsFrozen(true);
+ assertNotNull(app.getFrozenInsetsState());
+ assertNull(mDisplayContent.mInputMethodWindow.getFrozenInsetsState());
+ }
}
diff --git a/services/translation/java/com/android/server/translation/RemoteTranslationService.java b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
index 0c7e617..82cb728 100644
--- a/services/translation/java/com/android/server/translation/RemoteTranslationService.java
+++ b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
@@ -20,9 +20,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.os.ResultReceiver;
import android.service.translation.ITranslationService;
import android.service.translation.TranslationService;
import android.util.Slog;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import com.android.internal.infra.AbstractRemoteService;
@@ -80,8 +82,14 @@
return mIdleUnbindTimeoutMs;
}
- public void onSessionCreated(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
- run((s) -> s.onCreateTranslationSession(sourceSpec, destSpec, sessionId, resultReceiver));
+ public void onSessionCreated(@NonNull TranslationContext translationContext, int sessionId,
+ IResultReceiver resultReceiver) {
+ run((s) -> s.onCreateTranslationSession(translationContext, sessionId, resultReceiver));
+ }
+
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ run((s) -> s.onTranslationCapabilitiesRequest(sourceFormat, targetFormat, resultReceiver));
}
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 72e1e33..f132b49 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,9 +37,11 @@
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;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
@@ -142,29 +150,33 @@
}
final class TranslationManagerServiceStub extends ITranslationManager.Stub {
+
@Override
- public void getSupportedLocales(IResultReceiver receiver, int userId)
+ public void onTranslationCapabilitiesRequest(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int targetFormat,
+ ResultReceiver receiver, int userId)
throws RemoteException {
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
- || isCalledByServiceAppLocked(userId, "getSupportedLocales"))) {
- service.getSupportedLocalesLocked(receiver);
+ || isCalledByServiceAppLocked(userId, "getTranslationCapabilities"))) {
+ service.onTranslationCapabilitiesRequestLocked(sourceFormat, targetFormat,
+ receiver);
} else {
- Slog.v(TAG, "getSupportedLocales(): no service for " + userId);
+ Slog.v(TAG, "onGetTranslationCapabilitiesLocked(): no service for " + userId);
receiver.send(STATUS_SYNC_CALL_FAIL, null);
}
}
}
@Override
- public void onSessionCreated(TranslationSpec sourceSpec, TranslationSpec destSpec,
+ public void onSessionCreated(TranslationContext translationContext,
int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId, "onSessionCreated"))) {
- service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver);
+ service.onSessionCreatedLocked(translationContext, sessionId, receiver);
} else {
Slog.v(TAG, "onSessionCreated(): no service for " + userId);
receiver.send(STATUS_SYNC_CALL_FAIL, null);
@@ -174,7 +186,7 @@
@Override
public void updateUiTranslationStateByTaskId(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
int taskId, int userId) {
// deprecated
enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
@@ -183,7 +195,7 @@
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId,
"updateUiTranslationStateByTaskId"))) {
- service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+ service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
taskId);
}
}
@@ -191,14 +203,14 @@
@Override
public void updateUiTranslationState(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
IBinder token, int taskId, int userId) {
enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
synchronized (mLock) {
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null && (isDefaultServiceLocked(userId)
|| isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
- service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds,
+ service.updateUiTranslationStateLocked(state, sourceSpec, targetSpec, viewIds,
token, taskId);
}
}
@@ -226,6 +238,46 @@
}
}
+ @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 1ca07cb..2cd41ba 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.translation;
-import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE;
import static android.view.translation.UiTranslationManager.EXTRA_STATE;
import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE;
@@ -31,23 +30,23 @@
import android.os.IRemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.translation.TranslationServiceInfo;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.inputmethod.InputMethodInfo;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
-import com.android.internal.util.SyncResultReceiver;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
-import java.util.ArrayList;
import java.util.List;
final class TranslationManagerServiceImpl extends
@@ -63,6 +62,9 @@
@Nullable
private ServiceInfo mRemoteTranslationServiceInfo;
+ @GuardedBy("mLock")
+ private TranslationServiceInfo mTranslationServiceInfo;
+
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
protected TranslationManagerServiceImpl(
@@ -77,10 +79,10 @@
@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")
@@ -122,28 +124,28 @@
}
@GuardedBy("mLock")
- void getSupportedLocalesLocked(@NonNull IResultReceiver resultReceiver) {
- // TODO: implement this
- try {
- resultReceiver.send(STATUS_SYNC_CALL_SUCCESS,
- SyncResultReceiver.bundleFor(new ArrayList<>()));
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException returning supported locales: " + e);
+ void onTranslationCapabilitiesRequestLocked(@TranslationSpec.DataFormat int sourceFormat,
+ @TranslationSpec.DataFormat int destFormat,
+ @NonNull ResultReceiver resultReceiver) {
+ final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
+ if (remoteService != null) {
+ remoteService.onTranslationCapabilitiesRequest(sourceFormat, destFormat,
+ resultReceiver);
}
}
@GuardedBy("mLock")
- void onSessionCreatedLocked(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+ void onSessionCreatedLocked(@NonNull TranslationContext translationContext, int sessionId,
+ IResultReceiver resultReceiver) {
final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
if (remoteService != null) {
- remoteService.onSessionCreated(sourceSpec, destSpec, sessionId, resultReceiver);
+ remoteService.onSessionCreated(translationContext, sessionId, resultReceiver);
}
}
@GuardedBy("mLock")
public void updateUiTranslationStateLocked(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
int taskId) {
// deprecated
final ActivityTokens taskTopActivityTokens =
@@ -152,13 +154,13 @@
Slog.w(TAG, "Unknown activity to query for update translation state.");
return;
}
- updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
- viewIds);
+ updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+ targetSpec, viewIds);
}
@GuardedBy("mLock")
public void updateUiTranslationStateLocked(@UiTranslationState int state,
- TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds,
+ TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
IBinder token, int taskId) {
// Get top activity for a given task id
final ActivityTokens taskTopActivityTokens =
@@ -169,20 +171,20 @@
+ "translation state for token=" + token + " taskId=" + taskId);
return;
}
- updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec,
- viewIds);
+ updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec,
+ targetSpec, viewIds);
}
private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens,
- @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec,
+ @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec targetSpec,
List<AutofillId> viewIds) {
try {
tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state,
- sourceSpec, destSpec, viewIds);
+ sourceSpec, targetSpec, viewIds);
} catch (RemoteException e) {
Slog.w(TAG, "Update UiTranslationState fail: " + e);
}
- invokeCallbacks(state, sourceSpec, destSpec);
+ invokeCallbacks(state, sourceSpec, targetSpec);
}
private void invokeCallbacks(
@@ -228,4 +230,16 @@
}
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 afa35fe..2e692e6 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 @@
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 748e5df..22a6445 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 e089995a..11ccfd8 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.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 @@
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 @@
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 @@
}
}
- 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 @@
}
@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 80d4f8f..c110b23 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.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 @@
}
@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 05573f1..0742cb4 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -27,6 +27,7 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
+import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.content.BroadcastReceiver;
@@ -35,11 +36,14 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
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;
@@ -50,7 +54,6 @@
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;
@@ -60,6 +63,7 @@
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;
@@ -167,12 +171,8 @@
mSessionComponentName = new ComponentName(service.getPackageName(),
mInfo.getSessionService());
final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService();
- if (hotwordDetectionServiceName != null) {
- mHotwordDetectionComponentName = new ComponentName(service.getPackageName(),
- hotwordDetectionServiceName);
- } else {
- mHotwordDetectionComponentName = null;
- }
+ mHotwordDetectionComponentName = hotwordDetectionServiceName != null
+ ? new ComponentName(service.getPackageName(), hotwordDetectionServiceName) : null;
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
IntentFilter filter = new IntentFilter();
@@ -187,24 +187,23 @@
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);
@@ -402,10 +401,10 @@
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");
@@ -416,7 +415,6 @@
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");
@@ -428,7 +426,7 @@
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
options, sharedMemory);
} else {
- mHotwordDetectionConnection.setConfigLocked(options, sharedMemory);
+ mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
}
}
@@ -456,8 +454,22 @@
}
boolean isIsolatedProcessLocked(ComponentName componentName) {
- // TODO : Need to make sure this component exists and is :isolated.
- return true;
+ IPackageManager pm = AppGlobals.getPackageManager();
+ try {
+ ServiceInfo serviceInfo = pm.getServiceInfo(componentName,
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, mUser);
+ if (serviceInfo != null) {
+ return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+ && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
+ }
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "isIsolatedProcess RemoteException : " + e);
+ }
+ }
+ return false;
}
public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -476,6 +488,7 @@
pw.println(" Service info:");
mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), " ");
pw.print(" Recognition service="); pw.println(mInfo.getRecognitionService());
+ pw.print(" Hotword detection service="); pw.println(mInfo.getHotwordDetectionService());
pw.print(" Settings activity="); pw.println(mInfo.getSettingsActivity());
pw.print(" Supports assist="); pw.println(mInfo.getSupportsAssist());
pw.print(" Supports launch from keyguard=");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 428d342..cc021a9 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.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.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 @@
IVoiceInteractionSession mSession;
IVoiceInteractor mInteractor;
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
+ private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
AssistDataRequester mAssistDataRequester;
IVoiceInteractionSessionShowCallback mShowCallback =
@@ -192,7 +193,7 @@
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 @@
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 @@
} 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 @@
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 @@
} 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 2a5ddfd..d77ab2b 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -138,6 +138,27 @@
public static final int STATE_SIMULATED_RINGING = 13;
/**
+ * @hide
+ */
+ @IntDef(prefix = { "STATE_" },
+ value = {
+ STATE_NEW,
+ STATE_DIALING,
+ STATE_RINGING,
+ STATE_HOLDING,
+ STATE_ACTIVE,
+ STATE_DISCONNECTED,
+ STATE_SELECT_PHONE_ACCOUNT,
+ STATE_CONNECTING,
+ STATE_DISCONNECTING,
+ STATE_PULLING_CALL,
+ STATE_AUDIO_PROCESSING,
+ STATE_SIMULATED_RINGING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CallState {};
+
+ /**
* The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call
* extras. Used to pass the phone accounts to display on the front end to the user in order to
* select phone accounts to (for example) place a call.
@@ -666,7 +687,11 @@
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;
@@ -674,6 +699,7 @@
// Next PROPERTY value: 0x00004000
//******************************************************************************************
+ private final @CallState int mState;
private final String mTelecomCallId;
private final Uri mHandle;
private final int mHandlePresentation;
@@ -868,6 +894,13 @@
return builder.toString();
}
+ /**
+ * @return the state of the {@link Call} represented by this {@link Call.Details}.
+ */
+ public final @CallState int getState() {
+ return mState;
+ }
+
/** {@hide} */
@TestApi
public String getTelecomCallId() {
@@ -1069,6 +1102,7 @@
if (o instanceof Details) {
Details d = (Details) o;
return
+ Objects.equals(mState, d.mState) &&
Objects.equals(mHandle, d.mHandle) &&
Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
@@ -1095,7 +1129,8 @@
@Override
public int hashCode() {
- return Objects.hash(mHandle,
+ return Objects.hash(mState,
+ mHandle,
mHandlePresentation,
mCallerDisplayName,
mCallerDisplayNamePresentation,
@@ -1117,6 +1152,7 @@
/** {@hide} */
public Details(
+ @CallState int state,
String telecomCallId,
Uri handle,
int handlePresentation,
@@ -1136,6 +1172,7 @@
String contactDisplayName,
int callDirection,
int callerNumberVerificationStatus) {
+ mState = state;
mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -1160,6 +1197,7 @@
/** {@hide} */
public static Details createFromParcelableCall(ParcelableCall parcelableCall) {
return new Details(
+ parcelableCall.getState(),
parcelableCall.getId(),
parcelableCall.getHandle(),
parcelableCall.getHandlePresentation(),
@@ -1186,6 +1224,8 @@
StringBuilder sb = new StringBuilder();
sb.append("[id: ");
sb.append(mTelecomCallId);
+ sb.append(", state: ");
+ sb.append(Call.stateToString(mState));
sb.append(", pa: ");
sb.append(mAccountHandle);
sb.append(", hdl: ");
@@ -1302,7 +1342,7 @@
* @param call The {@code Call} invoking this method.
* @param state The new state of the {@code Call}.
*/
- public void onStateChanged(Call call, int state) {}
+ public void onStateChanged(Call call, @CallState int state) {}
/**
* Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}.
@@ -2171,9 +2211,11 @@
/**
* Obtains the state of this {@code Call}.
*
- * @return A state value, chosen from the {@code STATE_*} constants.
+ * @return The call state.
+ * @deprecated The call state is available via {@link Call.Details#getState()}.
*/
- public int getState() {
+ @Deprecated
+ public @CallState int getState() {
return mState;
}
@@ -2551,6 +2593,30 @@
final void internalSetDisconnected() {
if (mState != Call.STATE_DISCONNECTED) {
mState = Call.STATE_DISCONNECTED;
+ if (mDetails != null) {
+ mDetails = new Details(mState,
+ mDetails.getTelecomCallId(),
+ mDetails.getHandle(),
+ mDetails.getHandlePresentation(),
+ mDetails.getCallerDisplayName(),
+ mDetails.getCallerDisplayNamePresentation(),
+ mDetails.getAccountHandle(),
+ mDetails.getCallCapabilities(),
+ mDetails.getCallProperties(),
+ mDetails.getDisconnectCause(),
+ mDetails.getConnectTimeMillis(),
+ mDetails.getGatewayInfo(),
+ mDetails.getVideoState(),
+ mDetails.getStatusHints(),
+ mDetails.getExtras(),
+ mDetails.getIntentExtras(),
+ mDetails.getCreationTimeMillis(),
+ mDetails.getContactDisplayName(),
+ mDetails.getCallDirection(),
+ mDetails.getCallerNumberVerificationStatus()
+ );
+ fireDetailsChanged(mDetails);
+ }
fireStateChanged(mState);
fireCallDestroyed();
}
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 182dc8b..320308c 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -399,7 +399,7 @@
}
/** The current state of the call. */
- public int getState() {
+ public @Call.CallState int getState() {
return mState;
}
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 4ae11b8..23cf511 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -447,6 +447,9 @@
DataFailCause.VSNCP_RECONNECT_NOT_ALLOWED,
DataFailCause.IPV6_PREFIX_UNAVAILABLE,
DataFailCause.HANDOFF_PREFERENCE_CHANGED,
+ DataFailCause.SLICE_REJECTED,
+ DataFailCause.MATCH_ALL_RULE_NOT_ALLOWED,
+ DataFailCause.ALL_MATCHING_RULES_FAILED,
DataFailCause.OEM_DCFAILCAUSE_1,
DataFailCause.OEM_DCFAILCAUSE_2,
DataFailCause.OEM_DCFAILCAUSE_3,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 04a0aba..3db31ec 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4254,6 +4254,14 @@
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 @@
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 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 @@
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/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 597fe8f..957f683 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -68,22 +68,22 @@
public final boolean isEnDcAvailable;
/**
- * Provides network support info for LTE VoPS and LTE Emergency bearer support
+ * Provides network support info for VoPS and Emergency bearer support
*/
@Nullable
- private final LteVopsSupportInfo mLteVopsSupportInfo;
+ private final VopsSupportInfo mVopsSupportInfo;
/**
* @hide
*/
DataSpecificRegistrationInfo(
int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable,
- boolean isEnDcAvailable, @Nullable LteVopsSupportInfo lteVops) {
+ boolean isEnDcAvailable, @Nullable VopsSupportInfo vops) {
this.maxDataCalls = maxDataCalls;
this.isDcNrRestricted = isDcNrRestricted;
this.isNrAvailable = isNrAvailable;
this.isEnDcAvailable = isEnDcAvailable;
- this.mLteVopsSupportInfo = lteVops;
+ this.mVopsSupportInfo = vops;
}
/**
@@ -97,7 +97,7 @@
isDcNrRestricted = dsri.isDcNrRestricted;
isNrAvailable = dsri.isNrAvailable;
isEnDcAvailable = dsri.isEnDcAvailable;
- mLteVopsSupportInfo = dsri.mLteVopsSupportInfo;
+ mVopsSupportInfo = dsri.mVopsSupportInfo;
}
private DataSpecificRegistrationInfo(/* @NonNull */ Parcel source) {
@@ -105,7 +105,7 @@
isDcNrRestricted = source.readBoolean();
isNrAvailable = source.readBoolean();
isEnDcAvailable = source.readBoolean();
- mLteVopsSupportInfo = LteVopsSupportInfo.CREATOR.createFromParcel(source);
+ mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader());
}
@Override
@@ -114,7 +114,7 @@
dest.writeBoolean(isDcNrRestricted);
dest.writeBoolean(isNrAvailable);
dest.writeBoolean(isEnDcAvailable);
- mLteVopsSupportInfo.writeToParcel(dest, flags);
+ dest.writeParcelable(mVopsSupportInfo, flags);
}
@Override
@@ -131,15 +131,15 @@
.append(" isDcNrRestricted = " + isDcNrRestricted)
.append(" isNrAvailable = " + isNrAvailable)
.append(" isEnDcAvailable = " + isEnDcAvailable)
- .append(" " + mLteVopsSupportInfo)
+ .append(" " + mVopsSupportInfo)
.append(" }")
.toString();
}
@Override
public int hashCode() {
- return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable,
- mLteVopsSupportInfo);
+ return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable,
+ isEnDcAvailable, mVopsSupportInfo);
}
@Override
@@ -153,7 +153,7 @@
&& this.isDcNrRestricted == other.isDcNrRestricted
&& this.isNrAvailable == other.isNrAvailable
&& this.isEnDcAvailable == other.isEnDcAvailable
- && Objects.equals(mLteVopsSupportInfo, other.mLteVopsSupportInfo);
+ && Objects.equals(mVopsSupportInfo, other.mVopsSupportInfo);
}
public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR =
@@ -171,10 +171,26 @@
/**
* @return The LTE VOPS (Voice over Packet Switched) support information
+ *
+ * @deprecated use {@link #getVopsSupportInfo()}
*/
+ @Deprecated
@NonNull
public LteVopsSupportInfo getLteVopsSupportInfo() {
- return mLteVopsSupportInfo;
+ return mVopsSupportInfo instanceof LteVopsSupportInfo
+ ? (LteVopsSupportInfo) mVopsSupportInfo
+ : new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+ LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
}
+ /**
+ * @return The VOPS (Voice over Packet Switched) support information.
+ *
+ * The instance of {@link LTEVopsSupportInfo}, or {@link NrVopsSupportInfo},
+ * null if there is there is no VOPS support information available.
+ */
+ @Nullable
+ public VopsSupportInfo getVopsSupportInfo() {
+ return mVopsSupportInfo;
+ }
}
diff --git a/telephony/java/android/telephony/DataThrottlingRequest.java b/telephony/java/android/telephony/DataThrottlingRequest.java
index f50bb58..2827e8d 100644
--- a/telephony/java/android/telephony/DataThrottlingRequest.java
+++ b/telephony/java/android/telephony/DataThrottlingRequest.java
@@ -17,6 +17,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -52,6 +53,9 @@
* @hide
*/
@SystemApi
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
public static final int DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER = 1;
/**
@@ -63,6 +67,9 @@
* @hide
*/
@SystemApi
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
public static final int DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER = 2;
/**
@@ -76,6 +83,9 @@
* @hide
*/
@SystemApi
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING)
public static final int DATA_THROTTLING_ACTION_HOLD = 3;
/**
diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.java b/telephony/java/android/telephony/LteVopsSupportInfo.java
index 83e41bf..87761e2 100644
--- a/telephony/java/android/telephony/LteVopsSupportInfo.java
+++ b/telephony/java/android/telephony/LteVopsSupportInfo.java
@@ -21,7 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
-import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -32,7 +32,7 @@
* @hide
*/
@SystemApi
-public final class LteVopsSupportInfo implements Parcelable {
+public final class LteVopsSupportInfo extends VopsSupportInfo {
/**@hide*/
@Retention(RetentionPolicy.SOURCE)
@@ -42,7 +42,10 @@
public @interface LteVopsStatus {}
/**
* Indicates information not available from modem.
+ *
+ * @deprecated as no instance will be created in this case
*/
+ @Deprecated
public static final int LTE_STATUS_NOT_AVAILABLE = 1;
/**
@@ -82,13 +85,38 @@
return mEmcBearerSupport;
}
+ /**
+ * Returns whether VoPS is supported by the network
+ */
+ @Override
+ public boolean isVopsSupported() {
+ return mVopsSupport == LTE_STATUS_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service is supported by the network
+ */
+ @Override
+ public boolean isEmergencyServiceSupported() {
+ return mEmcBearerSupport == LTE_STATUS_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service fallback is supported by the network
+ */
+ @Override
+ public boolean isEmergencyServiceFallbackSupported() {
+ return false;
+ }
+
@Override
public int describeContents() {
return 0;
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags, AccessNetworkType.EUTRAN);
out.writeInt(mVopsSupport);
out.writeInt(mEmcBearerSupport);
}
@@ -124,6 +152,8 @@
new Creator<LteVopsSupportInfo>() {
@Override
public LteVopsSupportInfo createFromParcel(Parcel in) {
+ // Skip the type info.
+ in.readInt();
return new LteVopsSupportInfo(in);
}
@@ -133,6 +163,11 @@
}
};
+ /** @hide */
+ protected static LteVopsSupportInfo createFromParcelBody(Parcel in) {
+ return new LteVopsSupportInfo(in);
+ }
+
private LteVopsSupportInfo(Parcel in) {
mVopsSupport = in.readInt();
mEmcBearerSupport = in.readInt();
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index a78f813..5fb60d7 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -293,11 +293,12 @@
@Nullable CellIdentity cellIdentity, @Nullable String rplmn,
int maxDataCalls, boolean isDcNrRestricted,
boolean isNrAvailable, boolean isEndcAvailable,
- LteVopsSupportInfo lteVopsSupportInfo) {
+ @Nullable VopsSupportInfo vopsSupportInfo) {
this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
emergencyOnly, availableServices, cellIdentity, rplmn);
mDataSpecificInfo = new DataSpecificRegistrationInfo(
- maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo);
+ maxDataCalls, isDcNrRestricted, isNrAvailable,
+ isEndcAvailable, vopsSupportInfo);
updateNrState();
}
diff --git a/telephony/java/android/telephony/NrVopsSupportInfo.aidl b/telephony/java/android/telephony/NrVopsSupportInfo.aidl
new file mode 100644
index 0000000..460a589
--- /dev/null
+++ b/telephony/java/android/telephony/NrVopsSupportInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.telephony;
+
+parcelable NrVopsSupportInfo;
diff --git a/telephony/java/android/telephony/NrVopsSupportInfo.java b/telephony/java/android/telephony/NrVopsSupportInfo.java
new file mode 100644
index 0000000..155ee38
--- /dev/null
+++ b/telephony/java/android/telephony/NrVopsSupportInfo.java
@@ -0,0 +1,260 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Class stores information related to NR network VoPS support
+ * @hide
+ */
+@SystemApi
+public final class NrVopsSupportInfo extends VopsSupportInfo {
+
+ /**
+ * Indicates network does not support vops
+ */
+ public static final int NR_STATUS_VOPS_NOT_SUPPORTED = 0;
+
+ /**
+ * Indicates network supports vops over 3gpp access.
+ */
+ public static final int NR_STATUS_VOPS_3GPP_SUPPORTED = 1;
+
+ /**
+ * Indicates network supports vops over non 3gpp access
+ */
+ public static final int NR_STATUS_VOPS_NON_3GPP_SUPPORTED = 2;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"NR_STATUS_VOPS_"},
+ value = {
+ NR_STATUS_VOPS_NOT_SUPPORTED,
+ NR_STATUS_VOPS_3GPP_SUPPORTED,
+ NR_STATUS_VOPS_NON_3GPP_SUPPORTED
+ })
+ public @interface NrVopsStatus {}
+
+ /**
+ * Indicates network does not support emergency service
+ */
+ public static final int NR_STATUS_EMC_NOT_SUPPORTED = 0;
+
+ /**
+ * Indicates network supports emergency service in NR connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMC_5GCN_ONLY = 1;
+
+ /**
+ * Indicates network supports emergency service in E-UTRA connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMC_EUTRA_5GCN_ONLY = 2;
+
+ /**
+ * Indicates network supports emergency service in NR connected to 5GCN and
+ * E-UTRA connected to 5GCN
+ */
+ public static final int NR_STATUS_EMC_NR_EUTRA_5GCN = 3;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"NR_STATUS_EMC_"},
+ value = {
+ NR_STATUS_EMC_NOT_SUPPORTED,
+ NR_STATUS_EMC_5GCN_ONLY,
+ NR_STATUS_EMC_EUTRA_5GCN_ONLY,
+ NR_STATUS_EMC_NR_EUTRA_5GCN
+ })
+ public @interface NrEmcStatus {}
+
+ /**
+ * Indicates network does not support emergency service
+ */
+ public static final int NR_STATUS_EMF_NOT_SUPPORTED = 0;
+
+ /**
+ * Indicates network supports emergency service fallback in NR connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMF_5GCN_ONLY = 1;
+
+ /**
+ * Indicates network supports emergency service fallback in E-UTRA connected to 5GCN only
+ */
+ public static final int NR_STATUS_EMF_EUTRA_5GCN_ONLY = 2;
+
+ /**
+ * Indicates network supports emergency service fallback in NR connected to 5GCN
+ * and E-UTRA connected to 5GCN
+ */
+ public static final int NR_STATUS_EMF_NR_EUTRA_5GCN = 3;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"NR_STATUS_EMF_"},
+ value = {
+ NR_STATUS_EMF_NOT_SUPPORTED,
+ NR_STATUS_EMF_5GCN_ONLY,
+ NR_STATUS_EMF_EUTRA_5GCN_ONLY,
+ NR_STATUS_EMF_NR_EUTRA_5GCN
+ })
+ public @interface NrEmfStatus {}
+
+ @NrVopsStatus
+ private final int mVopsSupport;
+ @NrEmcStatus
+ private final int mEmcSupport;
+ @NrEmfStatus
+ private final int mEmfSupport;
+
+ public NrVopsSupportInfo(@NrVopsStatus int vops, @NrEmcStatus int emc, @NrEmcStatus int emf) {
+ mVopsSupport = vops;
+ mEmcSupport = emc;
+ mEmfSupport = emf;
+ }
+
+ /**
+ * Provides the NR VoPS support capability as described in:
+ * 3GPP 24.501 EPS network feature support -> IMS VoPS
+ */
+ public @NrVopsStatus int getVopsSupport() {
+ return mVopsSupport;
+ }
+
+ /**
+ * Provides the NR Emergency bearer support capability as described in:
+ * 3GPP 24.501 EPS network feature support -> EMC, and
+ * 38.331 SIB1 : ims-EmergencySupport
+ */
+ public @NrEmcStatus int getEmcSupport() {
+ return mEmcSupport;
+ }
+
+ /**
+ * Provides the NR emergency service fallback support capability as
+ * described in 3GPP 24.501 EPS network feature support -> EMF
+ */
+ public @NrEmfStatus int getEmfSupport() {
+ return mEmfSupport;
+ }
+
+ /**
+ * Returns whether VoPS is supported by the network
+ */
+ @Override
+ public boolean isVopsSupported() {
+ return mVopsSupport != NR_STATUS_VOPS_NOT_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service is supported by the network
+ */
+ @Override
+ public boolean isEmergencyServiceSupported() {
+ return mEmcSupport != NR_STATUS_EMC_NOT_SUPPORTED;
+ }
+
+ /**
+ * Returns whether emergency service fallback is supported by the network
+ */
+ public boolean isEmergencyServiceFallbackSupported() {
+ return mEmfSupport != NR_STATUS_EMF_NOT_SUPPORTED;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ super.writeToParcel(out, flags, AccessNetworkType.NGRAN);
+ out.writeInt(mVopsSupport);
+ out.writeInt(mEmcSupport);
+ out.writeInt(mEmfSupport);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null || !(o instanceof NrVopsSupportInfo)) {
+ return false;
+ }
+ if (this == o) return true;
+ NrVopsSupportInfo other = (NrVopsSupportInfo) o;
+ return mVopsSupport == other.mVopsSupport
+ && mEmcSupport == other.mEmcSupport
+ && mEmfSupport == other.mEmfSupport;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mVopsSupport, mEmcSupport, mEmfSupport);
+ }
+
+ /**
+ * @return string representation.
+ */
+ @NonNull
+ @Override
+ public String toString() {
+ return ("NrVopsSupportInfo : "
+ + " mVopsSupport = " + mVopsSupport
+ + " mEmcSupport = " + mEmcSupport
+ + " mEmfSupport = " + mEmfSupport);
+ }
+
+ public static final @android.annotation.NonNull Creator<NrVopsSupportInfo> CREATOR =
+ new Creator<NrVopsSupportInfo>() {
+ @Override
+ public NrVopsSupportInfo createFromParcel(Parcel in) {
+ // Skip the type info.
+ in.readInt();
+ return new NrVopsSupportInfo(in);
+ }
+
+ @Override
+ public NrVopsSupportInfo[] newArray(int size) {
+ return new NrVopsSupportInfo[size];
+ }
+ };
+
+ /** @hide */
+ protected static NrVopsSupportInfo createFromParcelBody(Parcel in) {
+ return new NrVopsSupportInfo(in);
+ }
+
+ private NrVopsSupportInfo(Parcel in) {
+ mVopsSupport = in.readInt();
+ mEmcSupport = in.readInt();
+ mEmfSupport = in.readInt();
+ }
+}
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index 30480d1..a3aaf61 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -26,6 +26,7 @@
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 @@
/** @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 @@
* @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 @@
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 @@
*
* @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 @@
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 @@
* {@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 @@
};
/**
- * @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 @@
}
/**
- * 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 a012498..3b28616 100644
--- a/telephony/java/android/telephony/PhysicalChannelConfig.java
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -293,6 +293,14 @@
}
/**
+ * 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 @@
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 f110dae..2d06062 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -564,6 +564,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public int getDataRegState() {
return mDataRegState;
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 17af463..a527e8d 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2219,7 +2219,7 @@
ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
}
} catch (RemoteException ex) {
- throw new RuntimeException(ex);
+ Log.e(TAG, "getSmsCapacityOnIcc() RemoteException", ex);
}
return ret;
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index cfb29f1..5a12865 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -300,9 +300,12 @@
* @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 @@
}
/**
- * 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 f7580d7..67fe783 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -56,6 +56,7 @@
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.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 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,10 +633,10 @@
value = {
D2D_SHARING_DISABLED,
D2D_SHARING_ALL_CONTACTS,
- D2D_SHARING_STARRED_CONTACTS,
+ D2D_SHARING_SELECTED_CONTACTS,
D2D_SHARING_ALL
})
- public @interface DeviceToDeviceStatusSharing {}
+ public @interface DeviceToDeviceStatusSharingPreference {}
/**
* TelephonyProvider column name for device to device sharing status.
@@ -639,6 +645,13 @@
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 @@
}
/**
+ * 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
@@ -3415,29 +3479,65 @@
* app uses this method to indicate with whom they wish to share device to device status
* information.
* @param sharing the status sharing preference
- * @param subId the unique Subscription ID in database
+ * @param subscriptionId the unique Subscription ID in database
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
- public void setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharing int sharing,
- int subId) {
+ public void setDeviceToDeviceStatusSharingPreference(
+ @DeviceToDeviceStatusSharingPreference int sharing, int subscriptionId) {
if (VDBG) {
- logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: " + subId);
+ logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: "
+ + subscriptionId);
}
- setSubscriptionPropertyHelper(subId, "setDeviceToDeviceSharingStatus",
- (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subId));
+ setSubscriptionPropertyHelper(subscriptionId, "setDeviceToDeviceSharingStatus",
+ (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subscriptionId));
}
/**
* Returns the user-chosen device to device status sharing preference
- * @param subId Subscription id of subscription
+ * @param subscriptionId Subscription id of subscription
* @return The device to device status sharing preference
*/
- public @DeviceToDeviceStatusSharing int getDeviceToDeviceStatusSharing(int subId) {
+ public @DeviceToDeviceStatusSharingPreference int getDeviceToDeviceStatusSharingPreference(
+ int subscriptionId) {
if (VDBG) {
- logd("[getDeviceToDeviceStatusSharing] + subId: " + subId);
+ logd("[getDeviceToDeviceStatusSharing] + subId: " + subscriptionId);
}
- return getIntegerSubscriptionProperty(subId, D2D_STATUS_SHARING, D2D_SHARING_DISABLED,
- mContext);
+ return getIntegerSubscriptionProperty(subscriptionId, D2D_STATUS_SHARING,
+ D2D_SHARING_DISABLED, mContext);
+ }
+
+ /**
+ * 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);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 2f89bfb..88c66ac 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -84,7 +84,7 @@
* </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 @@
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 f06b538..d2da51a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11037,6 +11037,25 @@
}
/**
+ * Determines the {@link PhoneAccountHandle} associated with this TelephonyManager.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+ *
+ * <p>Requires Permission android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE or that the
+ * calling app has carrier privileges (see {@link #hasCarrierPrivileges})
+ *
+ * @return The {@link PhoneAccountHandle} associated with the TelphonyManager, or {@code null}
+ * if there is no associated {@link PhoneAccountHandle}; this can happen if the subscription is
+ * data-only or an opportunistic subscription.
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @Nullable PhoneAccountHandle getPhoneAccountHandle() {
+ return getPhoneAccountHandleForSubscriptionId(getSubId());
+ }
+
+ /**
* Determines the {@link PhoneAccountHandle} associated with a subscription Id.
*
* @param subscriptionId The subscription Id to check.
@@ -12388,12 +12407,6 @@
* "Data capable" means that this device supports packet-switched
* data connections over the telephony network.
* <p>
- * Note: the meaning of this flag is subtly different from the
- * PackageManager.FEATURE_TELEPHONY system feature, which is available
- * on any device with a telephony radio, even if the device is
- * voice-only.
- *
- * @hide
*/
public boolean isDataCapable() {
if (mContext == null) return true;
@@ -14928,12 +14941,31 @@
public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE =
"CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
+ /**
+ * Indicates whether a data throttling request sent with {@link #sendThermalMitigationRequest}
+ * is supported. See comments on {@link #sendThermalMitigationRequest} for more information.
+ *
+ * @hide
+ */
+ @SystemApi
+ 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 = {
CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
- CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE
+ CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE,
+ CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING,
+ CAPABILITY_SLICING_CONFIG_SUPPORTED,
})
public @interface RadioInterfaceCapability {}
@@ -15043,11 +15075,29 @@
* and can be used at any time during data throttling to hold onto the current level of data
* throttling.
*
+ * <p> If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}({@link
+ * #CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING}) returns false, then sending a {@link
+ * DataThrottlingRequest#DATA_THROTTLING_ACTION_HOLD}, {@link
+ * DataThrottlingRequest#DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER}, or {@link
+ * DataThrottlingRequest#DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER} will result in {@link
+ * IllegalArgumentException} being thrown. However, on devices that do not
+ * support data throttling, {@link
+ * 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}). </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.
*
* @throws IllegalStateException if the Telephony process is not currently available.
- * @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters.
+ * @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters or
+ * if the device's modem does not support data throttling.
*
* @hide
*/
@@ -15059,7 +15109,8 @@
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) {
@@ -15566,9 +15617,15 @@
* <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/VopsSupportInfo.aidl b/telephony/java/android/telephony/VopsSupportInfo.aidl
new file mode 100644
index 0000000..31c608f
--- /dev/null
+++ b/telephony/java/android/telephony/VopsSupportInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.telephony;
+
+parcelable VopsSupportInfo;
diff --git a/telephony/java/android/telephony/VopsSupportInfo.java b/telephony/java/android/telephony/VopsSupportInfo.java
new file mode 100644
index 0000000..f89bfa9
--- /dev/null
+++ b/telephony/java/android/telephony/VopsSupportInfo.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 android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+
+/**
+ * Abstract base class for the information related to network VoPS support.
+ * This is the base class for XxVopsSupportInfo which represent VoPS support
+ * information for specific network access techonology.
+ * @hide
+ */
+@SuppressLint("ParcelNotFinal")
+@SystemApi
+public abstract class VopsSupportInfo implements Parcelable {
+
+ /**
+ * @hide
+ */
+ public VopsSupportInfo() {}
+
+ /**
+ * Returns whether VoPS is supported by the network
+ */
+ public abstract boolean isVopsSupported();
+
+ /**
+ * Returns whether emergency service is supported by the network
+ */
+ public abstract boolean isEmergencyServiceSupported();
+
+ /**
+ * Returns whether emergency service fallback is supported by the network
+ */
+ public abstract boolean isEmergencyServiceFallbackSupported();
+
+ /**
+ * Implement the Parcelable interface
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public abstract void writeToParcel(@NonNull Parcel dest, int flags);
+
+ /**
+ * Used by child classes for parceling.
+ *
+ * @hide
+ */
+ protected void writeToParcel(@NonNull Parcel dest, int flags, int type) {
+ dest.writeInt(type);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<VopsSupportInfo> CREATOR =
+ new Creator<VopsSupportInfo>() {
+ @Override
+ public VopsSupportInfo createFromParcel(Parcel in) {
+ int type = in.readInt();
+ switch (type) {
+ case AccessNetworkType.EUTRAN:
+ return LteVopsSupportInfo.createFromParcelBody(in);
+ case AccessNetworkType.NGRAN:
+ return NrVopsSupportInfo.createFromParcelBody(in);
+ default: throw new RuntimeException("Bad VopsSupportInfo Parcel");
+ }
+ }
+
+ @Override
+ public VopsSupportInfo[] newArray(int size) {
+ return new VopsSupportInfo[size];
+ }
+ };
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public abstract boolean equals(Object o);
+}
diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java
index 2011eed..fe124ac 100644
--- a/telephony/java/android/telephony/data/NrQos.java
+++ b/telephony/java/android/telephony/data/NrQos.java
@@ -50,6 +50,18 @@
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/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl
similarity index 61%
copy from services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
copy to telephony/java/android/telephony/data/NrQosSessionAttributes.aidl
index cab5ad2..fd3bbb0 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneIdValidator.java
+++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl
@@ -14,17 +14,6 @@
* limitations under the License.
*/
-package com.android.server.timezonedetector.location;
+ package android.telephony.data;
-import android.annotation.NonNull;
-
-import com.android.i18n.timezone.ZoneInfoDb;
-
-class ZoneInfoDbTimeZoneIdValidator implements
- LocationTimeZoneProvider.TimeZoneIdValidator {
-
- @Override
- public boolean isValid(@NonNull String timeZoneId) {
- return ZoneInfoDb.getInstance().hasTimeZone(timeZoneId);
- }
-}
+ 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 0000000..857ccb9
--- /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/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 52d0f03..a133ead 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -29,7 +29,9 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Contains the User Capability Exchange capabilities corresponding to a contact's URI.
@@ -110,7 +112,6 @@
/**
* Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
* queried through SIP OPTIONS.
- * @hide
*/
public static final class OptionsBuilder {
@@ -151,7 +152,7 @@
* @param tags the list of the supported feature tags
* @return this OptionBuilder
*/
- public @NonNull OptionsBuilder addFeatureTags(@NonNull List<String> tags) {
+ public @NonNull OptionsBuilder addFeatureTags(@NonNull Set<String> tags) {
mCapabilities.mFeatureTags.addAll(tags);
return this;
}
@@ -220,7 +221,7 @@
private @CapabilityMechanism int mCapabilityMechanism;
private @RequestResult int mRequestResult;
- private final List<String> mFeatureTags = new ArrayList<>();
+ private final Set<String> mFeatureTags = new HashSet<>();
private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();
private RcsContactUceCapability(@NonNull Uri contactUri, @CapabilityMechanism int mechanism,
@@ -235,7 +236,9 @@
mCapabilityMechanism = in.readInt();
mSourceType = in.readInt();
mRequestResult = in.readInt();
- in.readStringList(mFeatureTags);
+ List<String> featureTagList = new ArrayList<>();
+ in.readStringList(featureTagList);
+ mFeatureTags.addAll(featureTagList);
in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
}
@@ -245,7 +248,7 @@
out.writeInt(mCapabilityMechanism);
out.writeInt(mSourceType);
out.writeInt(mRequestResult);
- out.writeStringList(mFeatureTags);
+ out.writeStringList(new ArrayList<>(mFeatureTags));
out.writeParcelableList(mPresenceTuples, flags);
}
@@ -285,7 +288,20 @@
if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
return Collections.emptyList();
}
- return Collections.unmodifiableList(mFeatureTags);
+ return Collections.unmodifiableList(new ArrayList<>(mFeatureTags));
+ }
+
+ /**
+ * @return The feature tags present in the OPTIONS response from the network.
+ * <p>
+ * Note: this is only populated if {@link #getCapabilityMechanism} is
+ * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS}
+ */
+ public @NonNull Set<String> getFeatureTags() {
+ if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
+ return Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(mFeatureTags);
}
/**
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index a217d13..c3d7325 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -26,7 +26,8 @@
import android.telephony.ims.stub.CapabilityExchangeEventListener;
import android.util.Log;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
/**
* The ICapabilityExchangeEventListener wrapper class to store the listener which is registered by
@@ -84,7 +85,7 @@
* request to the framework.
*/
public void onRemoteCapabilityRequest(@NonNull Uri contactUri,
- @NonNull List<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
+ @NonNull Set<String> remoteCapabilities, @NonNull OptionsRequestCallback callback)
throws ImsException {
ICapabilityExchangeEventListener listener = mListenerBinder;
if (listener == null) {
@@ -114,7 +115,8 @@
};
try {
- listener.onRemoteCapabilityRequest(contactUri, remoteCapabilities, internalCallback);
+ listener.onRemoteCapabilityRequest(contactUri, new ArrayList<>(remoteCapabilities),
+ internalCallback);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote capability request exception: " + e);
throw new ImsException("Remote is not available",
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 85703f8..6315b24 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -47,6 +47,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
@@ -145,8 +146,8 @@
throws RemoteException {
OptionsResponseCallback callbackWrapper = new RcsOptionsResponseAidlWrapper(callback);
executeMethodAsync(() -> mReference.getCapabilityExchangeImplBaseInternal()
- .sendOptionsCapabilityRequest(contactUri, myCapabilities, callbackWrapper),
- "sendOptionsCapabilityRequest");
+ .sendOptionsCapabilityRequest(contactUri, new HashSet<>(myCapabilities),
+ callbackWrapper), "sendOptionsCapabilityRequest");
}
// Call the methods with a clean calling identity on the executor and wait indefinitely for
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index 6295548..a3be8da 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -26,7 +26,7 @@
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
-import java.util.List;
+import java.util.Set;
/**
* The interface that is used by the framework to listen to events from the vendor RCS stack
@@ -98,7 +98,8 @@
* {@link OptionsRequestCallback#onRespondToCapabilityRequestWithError}.
* @param contactUri The URI associated with the remote contact that is
* requesting capabilities.
- * @param remoteCapabilities The remote contact's capability information.
+ * @param remoteCapabilities The remote contact's capability information. The capability
+ * information is in the format defined in RCC.07 section 2.6.1.3.
* @param callback The callback of this request which is sent from the remote user.
* @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
* currently connected to the framework. This can happen if the {@link RcsFeature} is not
@@ -107,6 +108,6 @@
* cases when the Telephony stack has crashed.
*/
void onRemoteCapabilityRequest(@NonNull Uri contactUri,
- @NonNull List<String> remoteCapabilities,
+ @NonNull Set<String> remoteCapabilities,
@NonNull OptionsRequestCallback callback) throws ImsException;
}
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index 03e17fb..25b9446 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -33,6 +33,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -433,6 +434,7 @@
* @param contactUri The URI of the remote user that we wish to get the capabilities of.
* @param myCapabilities The capabilities of this device to send to the remote user.
* @param callback The callback of this request which is sent from the remote user.
+ * @hide
*/
// executor used is defined in the constructor.
@SuppressLint("ExecutorRegistration")
@@ -446,4 +448,27 @@
// Do not do anything, this is a stub implementation.
}
}
+
+ /**
+ * Push one's own capabilities to a remote user via the SIP OPTIONS presence exchange mechanism
+ * in order to receive the capabilities of the remote user in response.
+ * <p>
+ * The implementer must use {@link OptionsResponseCallback} to send the response of
+ * this query from the network back to the framework.
+ * @param contactUri The URI of the remote user that we wish to get the capabilities of.
+ * @param myCapabilities The capabilities of this device to send to the remote user.
+ * @param callback The callback of this request which is sent from the remote user.
+ */
+ // executor used is defined in the constructor.
+ @SuppressLint("ExecutorRegistration")
+ public void sendOptionsCapabilityRequest(@NonNull Uri contactUri,
+ @NonNull Set<String> myCapabilities, @NonNull OptionsResponseCallback callback) {
+ // Stub - to be implemented by service
+ Log.w(LOG_TAG, "sendOptionsCapabilityRequest called with no implementation.");
+ try {
+ callback.onCommandError(COMMAND_CODE_NOT_SUPPORTED);
+ } catch (ImsException e) {
+ // Do not do anything, this is a stub implementation.
+ }
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 9493c76..6493772 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -302,4 +302,6 @@
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 8ed9cff..46752b7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2250,10 +2250,12 @@
*
* @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 @@
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 f49d4fc..4259a86 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,7 +32,7 @@
- // 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 @@
};
@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]\[email protected](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 e8cce23..677094b 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,7 +46,7 @@
- // 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 @@
};
@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 []\[email protected](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 9de6552..eb260ab 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -54,7 +54,7 @@
- // 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 @@
}
@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]\[email protected](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 5a3e273..158e065 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.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 @@
/**
+ * 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 @@
- // 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 @@
*
* 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 @@
@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 @@
"from", 0f);
}
+ this.mToken = token;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ this.mIPCInterface = iPCInterface;
onConstructed();
}
@@ -797,6 +820,22 @@
}
/**
+ * 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 @@
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 @@
"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 @@
&& 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 @@
_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 @@
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 @@
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 @@
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 @@
dest.writeInt(mStringRes);
dest.writeInt(mDayOfWeek);
dest.writeFloatArray(mCoords);
+ dest.writeStrongBinder(mToken);
+ if (mIPCInterface != null) dest.writeStrongInterface(mIPCInterface);
}
@Override
@@ -1326,6 +1398,8 @@
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 @@
"from", 0f);
}
+ this.mToken = token;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ this.mIPCInterface = iPCInterface;
onConstructed();
}
@@ -1454,6 +1532,8 @@
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 @@
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 @@
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 @@
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 @@
}
@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]\[email protected](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]\[email protected](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 3ab3445..a535e22 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,7 +85,7 @@
- // 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 @@
}
@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 []\[email protected](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 8901cac..d409624 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
@@ -36,7 +36,7 @@
- // 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 @@
};
@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]\[email protected](genEqualsHashCode=true)")
@Deprecated
@@ -160,7 +160,7 @@
- // 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 @@
};
@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]\[email protected](genEqualsHashCode=true)")
@Deprecated
@@ -274,7 +274,7 @@
- // 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 @@
};
@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]\[email protected](genEqualsHashCode=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index ac776f3..3583b95 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -64,7 +64,7 @@
- // 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 @@
}
@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 []\[email protected](genConstructor=false, genBuilder=false)")
@Deprecated
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
index b9b347b..c1a86b3 100644
--- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -87,7 +87,7 @@
assertEquals(down.id, eventId)
// Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
- assigner.onChoreographerCallback()
+ assigner.notifyFrameProcessed()
eventId = assigner.processEvent(move3)
assertEquals(move3.id, eventId)
eventId = assigner.processEvent(move4)
@@ -122,7 +122,7 @@
eventId = assigner.processEvent(up)
// DOWN is only sticky for Motions, not for keys
assertEquals(up.id, eventId)
- assigner.onChoreographerCallback()
+ assigner.notifyFrameProcessed()
val down2 = createKeyEvent(KeyEvent.ACTION_DOWN, 22)
eventId = assigner.processEvent(down2)
assertEquals(down2.id, eventId)
diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
index 4f95ce5..b134fe7 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.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 @@
assertEquals(expected.displayId, received.displayId)
}
+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")
+ }
+}
+
class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
InputEventReceiver(channel, looper) {
- companion object {
- const val TAG = "TestInputEventReceiver"
- }
-
- var lastEvent: InputEvent? = null
+ 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 var mFinishedSignal = CountDownLatch(1)
+ private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>()
+
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 @@
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 2e57467..c679d04 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -25,17 +25,24 @@
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 ad8aac1..e633c87 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 @@
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 @@
}
@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 8dc53ac..9fd190c 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.runner.RunWith;
import java.io.File;
-import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -252,6 +251,28 @@
}
@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 @@
//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 @@
.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 8caf4a1..1f1a909 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 a4d8353..1e54093 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -19,6 +19,7 @@
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 @@
setPartialConnectivityAcceptable(false)
setUnvalidatedConnectivityAcceptable(true)
}.build()
- assertParcelSane(config, 10)
+ 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 a7ad695..e7718b5 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -35,14 +35,15 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
-import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+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.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;
@@ -51,7 +52,6 @@
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.ParcelUtils.assertParcelSane;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
-import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -62,7 +62,6 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-import android.net.wifi.WifiInfo;
import android.net.wifi.aware.DiscoverySession;
import android.net.wifi.aware.PeerHandle;
import android.net.wifi.aware.WifiAwareNetworkSpecifier;
@@ -102,20 +101,6 @@
@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);
@@ -328,7 +313,8 @@
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});
}
@@ -352,55 +338,6 @@
testParcelSane(netCap);
}
- private NetworkCapabilities createNetworkCapabilitiesWithWifiInfo() {
- // uses a real WifiInfo to test parceling of sensitive data.
- final WifiInfo wifiInfo = new WifiInfo.Builder()
- .setSsid("sssid1234".getBytes())
- .setBssid("00:11:22:33:44:55")
- .build();
- return new NetworkCapabilities()
- .addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_EIMS)
- .addCapability(NET_CAPABILITY_NOT_METERED)
- .setSSID(TEST_SSID)
- .setTransportInfo(wifiInfo)
- .setRequestorPackageName("com.android.test")
- .setRequestorUid(9304);
- }
-
- @Test
- public void testParcelNetworkCapabilitiesWithLocationSensitiveFields() {
- assumeTrue(isAtLeastS());
-
- final NetworkCapabilities netCap = createNetworkCapabilitiesWithWifiInfo();
- final NetworkCapabilities netCapWithLocationSensitiveFields =
- new NetworkCapabilities(netCap, true);
-
- assertParcelingIsLossless(netCapWithLocationSensitiveFields);
- testParcelSane(netCapWithLocationSensitiveFields);
-
- assertEquals(netCapWithLocationSensitiveFields,
- parcelingRoundTrip(netCapWithLocationSensitiveFields));
- }
-
- @Test
- public void testParcelNetworkCapabilitiesWithoutLocationSensitiveFields() {
- assumeTrue(isAtLeastS());
-
- final NetworkCapabilities netCap = createNetworkCapabilitiesWithWifiInfo();
- final NetworkCapabilities netCapWithoutLocationSensitiveFields =
- new NetworkCapabilities(netCap, false);
-
- final NetworkCapabilities sanitizedNetCap =
- new NetworkCapabilities(netCapWithoutLocationSensitiveFields);
- final WifiInfo sanitizedWifiInfo = new WifiInfo.Builder()
- .setSsid(new byte[0])
- .setBssid(WifiInfo.DEFAULT_MAC_ADDRESS)
- .build();
- sanitizedNetCap.setTransportInfo(sanitizedWifiInfo);
- assertEquals(sanitizedNetCap, parcelingRoundTrip(netCapWithoutLocationSensitiveFields));
- }
-
private void testParcelSane(NetworkCapabilities cap) {
if (isAtLeastS()) {
assertParcelSane(cap, 17);
@@ -411,6 +348,45 @@
}
}
+ private static NetworkCapabilities createNetworkCapabilitiesWithTransportInfo() {
+ return new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_EIMS)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .setSSID(TEST_SSID)
+ .setTransportInfo(new TestTransportInfo())
+ .setRequestorPackageName("com.android.test")
+ .setRequestorUid(9304);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesCopyWithNoRedactions() {
+ assumeTrue(isAtLeastS());
+
+ final NetworkCapabilities netCap = createNetworkCapabilitiesWithTransportInfo();
+ final NetworkCapabilities netCapWithNoRedactions =
+ new NetworkCapabilities(netCap, NetworkCapabilities.REDACT_NONE);
+ TestTransportInfo testTransportInfo =
+ (TestTransportInfo) netCapWithNoRedactions.getTransportInfo();
+ assertFalse(testTransportInfo.locationRedacted);
+ assertFalse(testTransportInfo.localMacAddressRedacted);
+ assertFalse(testTransportInfo.settingsRedacted);
+ }
+
+ @Test
+ public void testNetworkCapabilitiesCopyWithoutLocationSensitiveFields() {
+ assumeTrue(isAtLeastS());
+
+ final NetworkCapabilities netCap = createNetworkCapabilitiesWithTransportInfo();
+ final NetworkCapabilities netCapWithNoRedactions =
+ new NetworkCapabilities(netCap, REDACT_FOR_ACCESS_FINE_LOCATION);
+ TestTransportInfo testTransportInfo =
+ (TestTransportInfo) netCapWithNoRedactions.getTransportInfo();
+ assertTrue(testTransportInfo.locationRedacted);
+ assertFalse(testTransportInfo.localMacAddressRedacted);
+ assertFalse(testTransportInfo.settingsRedacted);
+ }
+
@Test
public void testOemPaid() {
NetworkCapabilities nc = new NetworkCapabilities();
@@ -540,11 +516,22 @@
assertFalse(nc1.equalsNetCapabilities(nc2));
nc2.addUnwantedCapability(NET_CAPABILITY_INTERNET);
assertTrue(nc1.equalsNetCapabilities(nc2));
+ if (isAtLeastS()) {
+ // Remove a required capability doesn't affect unwanted capabilities.
+ // This is a behaviour change from S.
+ nc1.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
- nc1.removeCapability(NET_CAPABILITY_INTERNET);
- assertFalse(nc1.equalsNetCapabilities(nc2));
- nc2.removeCapability(NET_CAPABILITY_INTERNET);
- assertTrue(nc1.equalsNetCapabilities(nc2));
+ nc1.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+ assertFalse(nc1.equalsNetCapabilities(nc2));
+ nc2.removeUnwantedCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
+ } else {
+ nc1.removeCapability(NET_CAPABILITY_INTERNET);
+ assertFalse(nc1.equalsNetCapabilities(nc2));
+ nc2.removeCapability(NET_CAPABILITY_INTERNET);
+ assertTrue(nc1.equalsNetCapabilities(nc2));
+ }
}
@Test
@@ -605,11 +592,21 @@
// This will effectively move NOT_ROAMING capability from required to unwanted for nc1.
nc1.addUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
- 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));
+ if (isAtLeastS()) {
+ // 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);
+ }
nc1.setSSID(TEST_SSID);
nc2.combineCapabilities(nc1);
@@ -972,26 +969,6 @@
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));
@@ -1062,18 +1039,42 @@
} catch (IllegalArgumentException e) { }
}
- private class TestTransportInfo implements TransportInfo {
+ /**
+ * Test TransportInfo to verify redaction mechanism.
+ */
+ private static class TestTransportInfo implements TransportInfo {
+ public final boolean locationRedacted;
+ public final boolean localMacAddressRedacted;
+ public final boolean settingsRedacted;
+
TestTransportInfo() {
+ locationRedacted = false;
+ localMacAddressRedacted = false;
+ settingsRedacted = false;
+ }
+
+ TestTransportInfo(boolean locationRedacted,
+ boolean localMacAddressRedacted,
+ boolean settingsRedacted) {
+ this.locationRedacted = locationRedacted;
+ this.localMacAddressRedacted =
+ localMacAddressRedacted;
+ this.settingsRedacted = settingsRedacted;
}
@Override
- public TransportInfo makeCopy(boolean parcelLocationSensitiveFields) {
- return this;
+ public TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) {
+ return new TestTransportInfo(
+ (redactions & NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION) != 0,
+ (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0,
+ (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0
+ );
}
@Override
- public boolean hasLocationSensitiveFields() {
- return false;
+ public @NetworkCapabilities.RedactionType long getApplicableRedactions() {
+ return REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS
+ | REDACT_FOR_NETWORK_SETTINGS;
}
}
@@ -1084,7 +1085,7 @@
final int requestUid = 10100;
final int[] administratorUids = {ownerUid, 10001};
final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
- final TestTransportInfo transportInfo = new TestTransportInfo();
+ final TransportInfo transportInfo = new TransportInfo() {};
final String ssid = "TEST_SSID";
final String packageName = "com.google.test.networkcapabilities";
final NetworkCapabilities nc = new NetworkCapabilities.Builder()
diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
index 11d44b8..cd9da8e 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.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 @@
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 @@
}
@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 01eb514..61ef5bd 100644
--- a/tests/net/integration/src/android/net/TestNetworkStackClient.kt
+++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
@@ -19,6 +19,7 @@
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 @@
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 db49e0b..e039ef0 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.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 @@
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 6fc605e..36f205b 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -64,6 +64,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0b31999..44298d4 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.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;
@@ -82,15 +85,15 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
+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.REDACT_NONE;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
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;
@@ -212,7 +215,6 @@
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;
@@ -232,12 +234,12 @@
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;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.wifi.WifiInfo;
import android.os.BadParcelableException;
import android.os.Binder;
import android.os.Build;
@@ -263,6 +265,7 @@
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;
@@ -443,7 +446,7 @@
@Mock NetworkStatsManager mStatsManager;
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
- @Mock NetworkStackClient mNetworkStack;
+ @Mock NetworkStackClientBase mNetworkStack;
@Mock PackageManager mPackageManager;
@Mock UserManager mUserManager;
@Mock NotificationManager mNotificationManager;
@@ -1175,11 +1178,6 @@
}
@Override
- public int getNetId() {
- return (mMockNetworkAgent == null) ? NETID_UNSET : mMockNetworkAgent.getNetwork().netId;
- }
-
- @Override
public int getActiveVpnType() {
return mVpnType;
}
@@ -1203,10 +1201,10 @@
mNetworkCapabilities);
mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
- verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()),
+ verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(uids)));
verify(mMockNetd, never())
- .networkRemoveUidRanges(eq(mMockVpn.getNetId()), any());
+ .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), any());
mAgentRegistered = true;
updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
@@ -1448,6 +1446,23 @@
});
}
+ 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 @@
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));
@@ -1564,25 +1579,26 @@
}).when(deps).makeMultinetworkPolicyTracker(any(), any(), any());
doReturn(true).when(deps).getCellular464XlatEnabled();
- doReturn(60000).when(mResources).getInteger(
- com.android.connectivity.resources.R.integer.config_networkTransitionTimeout);
- doReturn("").when(mResources).getString(
- com.android.connectivity.resources.R.string.config_networkCaptivePortalServerUrl);
+ doReturn(60000).when(mResources).getInteger(R.integer.config_networkTransitionTimeout);
+ doReturn("").when(mResources).getString(R.string.config_networkCaptivePortalServerUrl);
doReturn(new String[]{ WIFI_WOL_IFNAME }).when(mResources).getStringArray(
- com.android.connectivity.resources.R.array.config_wakeonlan_supported_interfaces);
+ R.array.config_wakeonlan_supported_interfaces);
doReturn(new String[] { "0,1", "1,3" }).when(mResources).getStringArray(
- com.android.connectivity.resources.R.array.config_networkSupportedKeepaliveCount);
- doReturn(com.android.connectivity.resources.R.array.config_networkSupportedKeepaliveCount)
- .when(mResources).getIdentifier(eq("config_networkSupportedKeepaliveCount"),
- eq("array"), any());
- doReturn(com.android.connectivity.resources.R.array.network_switch_type_name)
- .when(mResources).getIdentifier(eq("network_switch_type_name"),
- eq("array"), any());
-
+ R.array.config_networkSupportedKeepaliveCount);
+ doReturn(new String[0]).when(mResources).getStringArray(
+ R.array.config_networkNotifySwitches);
+ doReturn(new int[]{10, 11, 12, 14, 15}).when(mResources).getIntArray(
+ R.array.config_protectedNetworks);
// We don't test the actual notification value strings, so just return an empty array.
// It doesn't matter what the values are as long as it's not null.
doReturn(new String[0]).when(mResources).getStringArray(R.array.network_switch_type_name);
+ doReturn(R.array.config_networkSupportedKeepaliveCount).when(mResources)
+ .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any());
+ doReturn(R.array.network_switch_type_name).when(mResources)
+ .getIdentifier(eq("network_switch_type_name"), eq("array"), any());
+
+
final ConnectivityResources connRes = mock(ConnectivityResources.class);
doReturn(mResources).when(connRes).get();
doReturn(connRes).when(deps).getResources(any());
@@ -3810,8 +3826,9 @@
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());
});
@@ -4040,7 +4057,7 @@
}
@Test
- public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
+ public void testRegisterPrivilegedDefaultCallbacksRequireNetworkSettings() throws Exception {
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false /* validated */);
@@ -4049,12 +4066,19 @@
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) {
@@ -7484,6 +7508,13 @@
final NetworkRequest vpnUidRequest = new NetworkRequest.Builder().build();
registerNetworkCallbackAsUid(vpnUidRequest, vpnUidCallback, VPN_UID);
+ 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<>();
@@ -7502,6 +7533,8 @@
callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
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);
@@ -7514,6 +7547,8 @@
callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
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());
@@ -7528,6 +7563,8 @@
callback.assertNoCallback();
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
@@ -7548,6 +7585,8 @@
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
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);
@@ -7568,6 +7607,8 @@
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
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);
@@ -7579,6 +7620,8 @@
defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
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);
@@ -7593,6 +7636,8 @@
callback.assertNoCallback();
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);
@@ -7604,6 +7649,8 @@
callback.assertNoCallback();
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 +7663,8 @@
defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
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);
@@ -7626,6 +7675,8 @@
assertUidRangesUpdatedForMyUid(true);
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);
@@ -7636,11 +7687,16 @@
mMockVpn.disconnect();
defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
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() {
@@ -8840,29 +8896,34 @@
final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid);
return mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
- netCap, includeLocationSensitiveInfo, callerUid,
+ netCap, includeLocationSensitiveInfo, Process.myUid(), callerUid,
mContext.getPackageName(), getAttributionTag())
.getOwnerUid();
}
- private void verifyWifiInfoCopyNetCapsPermission(
+ private void verifyTransportInfoCopyNetCapsPermission(
int callerUid, boolean includeLocationSensitiveInfo,
boolean shouldMakeCopyWithLocationSensitiveFieldsParcelable) {
- final WifiInfo wifiInfo = mock(WifiInfo.class);
- when(wifiInfo.hasLocationSensitiveFields()).thenReturn(true);
- final NetworkCapabilities netCap = new NetworkCapabilities().setTransportInfo(wifiInfo);
+ final TransportInfo transportInfo = mock(TransportInfo.class);
+ when(transportInfo.getApplicableRedactions()).thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION);
+ final NetworkCapabilities netCap =
+ new NetworkCapabilities().setTransportInfo(transportInfo);
mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
- netCap, includeLocationSensitiveInfo, callerUid,
+ netCap, includeLocationSensitiveInfo, Process.myPid(), callerUid,
mContext.getPackageName(), getAttributionTag());
- verify(wifiInfo).makeCopy(eq(shouldMakeCopyWithLocationSensitiveFieldsParcelable));
+ if (shouldMakeCopyWithLocationSensitiveFieldsParcelable) {
+ verify(transportInfo).makeCopy(REDACT_NONE);
+ } else {
+ verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION);
+ }
}
- private void verifyOwnerUidAndWifiInfoNetCapsPermission(
+ private void verifyOwnerUidAndTransportInfoNetCapsPermission(
boolean shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag,
boolean shouldInclLocationSensitiveOwnerUidWithIncludeFlag,
- boolean shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag,
- boolean shouldInclLocationSensitiveWifiInfoWithIncludeFlag) {
+ boolean shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag,
+ boolean shouldInclLocationSensitiveTransportInfoWithIncludeFlag) {
final int myUid = Process.myUid();
final int expectedOwnerUidWithoutIncludeFlag =
@@ -8876,13 +8937,13 @@
assertEquals(expectedOwnerUidWithIncludeFlag, getOwnerUidNetCapsPermission(
myUid, myUid, true /* includeLocationSensitiveInfo */));
- verifyWifiInfoCopyNetCapsPermission(myUid,
+ verifyTransportInfoCopyNetCapsPermission(myUid,
false, /* includeLocationSensitiveInfo */
- shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag);
+ shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag);
- verifyWifiInfoCopyNetCapsPermission(myUid,
+ verifyTransportInfoCopyNetCapsPermission(myUid,
true, /* includeLocationSensitiveInfo */
- shouldInclLocationSensitiveWifiInfoWithIncludeFlag);
+ shouldInclLocationSensitiveTransportInfoWithIncludeFlag);
}
@@ -8892,15 +8953,15 @@
setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION);
- verifyOwnerUidAndWifiInfoNetCapsPermission(
+ verifyOwnerUidAndTransportInfoNetCapsPermission(
// Ensure that we include owner uid even if the request asks to remove it since the
// app has necessary permissions and targetSdk < S.
true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
- false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */
+ false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
// Ensure that we remove location info if the request asks to remove it even if the
// app has necessary permissions.
- true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */
+ true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
);
}
@@ -8910,15 +8971,15 @@
setupLocationPermissions(Build.VERSION_CODES.R, true, AppOpsManager.OPSTR_FINE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION);
- verifyOwnerUidAndWifiInfoNetCapsPermission(
+ verifyOwnerUidAndTransportInfoNetCapsPermission(
// Ensure that we include owner uid even if the request asks to remove it since the
// app has necessary permissions and targetSdk < S.
true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
- false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */
+ false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
// Ensure that we remove location info if the request asks to remove it even if the
// app has necessary permissions.
- true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */
+ true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
);
}
@@ -8929,15 +8990,15 @@
setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION);
- verifyOwnerUidAndWifiInfoNetCapsPermission(
+ verifyOwnerUidAndTransportInfoNetCapsPermission(
// Ensure that we owner UID if the request asks us to remove it even if the app
// has necessary permissions since targetSdk >= S.
false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
- false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */
+ false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
// Ensure that we remove location info if the request asks to remove it even if the
// app has necessary permissions.
- true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */
+ true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
);
}
@@ -8947,15 +9008,15 @@
setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION);
- verifyOwnerUidAndWifiInfoNetCapsPermission(
+ verifyOwnerUidAndTransportInfoNetCapsPermission(
// Ensure that we owner UID if the request asks us to remove it even if the app
// has necessary permissions since targetSdk >= S.
true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
- false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */
+ false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
// Ensure that we remove location info if the request asks to remove it even if the
// app has necessary permissions.
- true /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */
+ true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
);
}
@@ -8965,11 +9026,11 @@
setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION);
- verifyOwnerUidAndWifiInfoNetCapsPermission(
+ verifyOwnerUidAndTransportInfoNetCapsPermission(
false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
- false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */
- false /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */
+ false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
+ false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
);
}
@@ -8992,11 +9053,11 @@
setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION);
- verifyOwnerUidAndWifiInfoNetCapsPermission(
+ verifyOwnerUidAndTransportInfoNetCapsPermission(
false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
- false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */
- false /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */
+ false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
+ false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
);
}
@@ -9006,14 +9067,193 @@
// Test that not having fine location permission leads to sanitization.
setupLocationPermissions(Build.VERSION_CODES.Q, true, null /* op */, null /* perm */);
- verifyOwnerUidAndWifiInfoNetCapsPermission(
+ verifyOwnerUidAndTransportInfoNetCapsPermission(
false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */
false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */
- false, /* shouldInclLocationSensitiveWifiInfoWithoutIncludeFlag */
- false /* shouldInclLocationSensitiveWifiInfoWithIncludeFlag */
+ false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */
+ false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */
);
}
+ @Test
+ public void testCreateForCallerWithLocalMacAddressSanitizedWithLocalMacAddressPermission()
+ throws Exception {
+ mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_GRANTED);
+
+ final TransportInfo transportInfo = mock(TransportInfo.class);
+ when(transportInfo.getApplicableRedactions())
+ .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS);
+ final NetworkCapabilities netCap =
+ new NetworkCapabilities().setTransportInfo(transportInfo);
+
+ mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
+ netCap, false /* includeLocationSensitiveInfoInTransportInfo */,
+ Process.myPid(), Process.myUid(),
+ mContext.getPackageName(), getAttributionTag());
+ // don't redact MAC_ADDRESS fields, only location sensitive fields.
+ verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION);
+ }
+
+ @Test
+ public void testCreateForCallerWithLocalMacAddressSanitizedWithoutLocalMacAddressPermission()
+ throws Exception {
+ mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_DENIED);
+
+ final TransportInfo transportInfo = mock(TransportInfo.class);
+ when(transportInfo.getApplicableRedactions())
+ .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS);
+ final NetworkCapabilities netCap =
+ new NetworkCapabilities().setTransportInfo(transportInfo);
+
+ mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
+ netCap, false /* includeLocationSensitiveInfoInTransportInfo */,
+ Process.myPid(), Process.myUid(),
+ mContext.getPackageName(), getAttributionTag());
+ // redact both MAC_ADDRESS & location sensitive fields.
+ verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION
+ | REDACT_FOR_LOCAL_MAC_ADDRESS);
+ }
+
+ @Test
+ public void testCreateForCallerWithLocalMacAddressSanitizedWithSettingsPermission()
+ throws Exception {
+ mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+
+ final TransportInfo transportInfo = mock(TransportInfo.class);
+ when(transportInfo.getApplicableRedactions())
+ .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS);
+ final NetworkCapabilities netCap =
+ new NetworkCapabilities().setTransportInfo(transportInfo);
+
+ mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
+ netCap, false /* includeLocationSensitiveInfoInTransportInfo */,
+ Process.myPid(), Process.myUid(),
+ mContext.getPackageName(), getAttributionTag());
+ // don't redact NETWORK_SETTINGS fields, only location sensitive fields.
+ verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION);
+ }
+
+ @Test
+ public void testCreateForCallerWithLocalMacAddressSanitizedWithoutSettingsPermission()
+ throws Exception {
+ mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_DENIED);
+
+ final TransportInfo transportInfo = mock(TransportInfo.class);
+ when(transportInfo.getApplicableRedactions())
+ .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS);
+ final NetworkCapabilities netCap =
+ new NetworkCapabilities().setTransportInfo(transportInfo);
+
+ mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled(
+ netCap, false /* includeLocationSensitiveInfoInTransportInfo */,
+ Process.myPid(), Process.myUid(),
+ mContext.getPackageName(), getAttributionTag());
+ // redact both NETWORK_SETTINGS & location sensitive fields.
+ verify(transportInfo).makeCopy(
+ REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS);
+ }
+
+ /**
+ * Test TransportInfo to verify redaction mechanism.
+ */
+ private static class TestTransportInfo implements TransportInfo {
+ public final boolean locationRedacted;
+ public final boolean localMacAddressRedacted;
+ public final boolean settingsRedacted;
+
+ TestTransportInfo() {
+ locationRedacted = false;
+ localMacAddressRedacted = false;
+ settingsRedacted = false;
+ }
+
+ TestTransportInfo(boolean locationRedacted, boolean localMacAddressRedacted,
+ boolean settingsRedacted) {
+ this.locationRedacted = locationRedacted;
+ this.localMacAddressRedacted =
+ localMacAddressRedacted;
+ this.settingsRedacted = settingsRedacted;
+ }
+
+ @Override
+ public TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) {
+ return new TestTransportInfo(
+ (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0,
+ (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0,
+ (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0
+ );
+ }
+
+ @Override
+ public @NetworkCapabilities.RedactionType long getApplicableRedactions() {
+ return REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS
+ | REDACT_FOR_NETWORK_SETTINGS;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof TestTransportInfo)) return false;
+ TestTransportInfo that = (TestTransportInfo) other;
+ return that.locationRedacted == this.locationRedacted
+ && that.localMacAddressRedacted == this.localMacAddressRedacted
+ && that.settingsRedacted == this.settingsRedacted;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(locationRedacted, localMacAddressRedacted, settingsRedacted);
+ }
+ }
+
+ private void verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps(
+ @NonNull TestNetworkCallback wifiNetworkCallback, int actualOwnerUid,
+ @NonNull TransportInfo actualTransportInfo, int expectedOwnerUid,
+ @NonNull TransportInfo expectedTransportInfo) throws Exception {
+ when(mPackageManager.getTargetSdkVersion(anyString())).thenReturn(Build.VERSION_CODES.S);
+ final NetworkCapabilities ncTemplate =
+ new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .setOwnerUid(actualOwnerUid);
+
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(),
+ ncTemplate);
+ mWiFiNetworkAgent.connect(false);
+
+ wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+ // Send network capabilities update with TransportInfo to trigger capabilities changed
+ // callback.
+ mWiFiNetworkAgent.setNetworkCapabilities(
+ ncTemplate.setTransportInfo(actualTransportInfo), true);
+
+ wifiNetworkCallback.expectCapabilitiesThat(mWiFiNetworkAgent,
+ nc -> Objects.equals(expectedOwnerUid, nc.getOwnerUid())
+ && Objects.equals(expectedTransportInfo, nc.getTransportInfo()));
+
+ }
+
+ @Test
+ public void testVerifyLocationDataIsNotIncludedWhenInclFlagNotSet() throws Exception {
+ final TestNetworkCallback wifiNetworkCallack = new TestNetworkCallback();
+ final int ownerUid = Process.myUid();
+ final TransportInfo transportInfo = new TestTransportInfo();
+ // Even though the test uid holds privileged permissions, mask location fields since
+ // the callback did not explicitly opt-in to get location data.
+ final TransportInfo sanitizedTransportInfo = new TestTransportInfo(
+ true, /* locationRedacted */
+ true, /* localMacAddressRedacted */
+ true /* settingsRedacted */
+ );
+ // Should not expect location data since the callback does not set the flag for including
+ // location data.
+ verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps(
+ wifiNetworkCallack, ownerUid, transportInfo, INVALID_UID, sanitizedTransportInfo);
+ }
+
private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
throws Exception {
final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE);
@@ -9558,10 +9798,12 @@
assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
if (add) {
- inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetId()),
+ inOrder.verify(mMockNetd, times(1))
+ .networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(vpnRanges)));
} else {
- inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(mMockVpn.getNetId()),
+ inOrder.verify(mMockNetd, times(1))
+ .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(vpnRanges)));
}
@@ -9602,8 +9844,8 @@
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())
);
}
@@ -9722,7 +9964,7 @@
&& 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
@@ -9730,6 +9972,36 @@
}
@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();
@@ -10174,6 +10446,7 @@
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);
}
@@ -10193,7 +10466,7 @@
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);
@@ -10329,6 +10602,11 @@
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);
@@ -10339,19 +10617,22 @@
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
@@ -10369,6 +10650,11 @@
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);
@@ -10380,15 +10666,19 @@
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
@@ -10402,6 +10692,11 @@
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);
@@ -10417,6 +10712,9 @@
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,
@@ -10424,25 +10722,31 @@
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 8c5d1d6..97c65eb 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 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 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 @@
IpConnectivityMetrics mService;
NetdEventListenerService mNetdListener;
+ final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
@Before
public void setUp() {
@@ -263,14 +272,6 @@
// 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 @@
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 ? mNcWifi : mNcCell);
+ }
+
+ 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/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 9ab60a4..116d755e 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.ConnectivityResources;
import android.net.IDnsResolver;
import android.net.INetd;
import android.net.LinkProperties;
@@ -47,10 +48,11 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
+import com.android.connectivity.resources.R;
import com.android.server.ConnectivityService;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,10 +86,16 @@
MockitoAnnotations.initMocks(this);
when(mCtx.getResources()).thenReturn(mResources);
when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
+ ConnectivityResources.setResourcesContextForTest(mCtx);
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
}
+ @After
+ public void tearDown() {
+ ConnectivityResources.setResourcesContextForTest(null);
+ }
+
@Test
public void testTransitions() {
setNotificationSwitch(transition(WIFI, CELLULAR));
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 8ccea1a..52975e4 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 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 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 @@
NetdEventListenerService mService;
ConnectivityManager mCm;
+ final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ final NetworkCapabilities mNcCell = 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 @@
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 ? mNcWifi : mNcCell);
+ }
+
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 @@
}
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 dde77b0..2f3ee68 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.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 @@
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 8932892..bcd6ed7 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 @@
"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 7515971..516c206 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -204,7 +204,7 @@
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 814cad4..f15d420 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -19,6 +19,8 @@
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;
@@ -32,9 +34,8 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -48,7 +49,9 @@
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;
@@ -239,9 +242,14 @@
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
@@ -330,6 +338,13 @@
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);
@@ -392,7 +407,7 @@
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
@@ -406,6 +421,42 @@
}
@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();
@@ -493,7 +544,7 @@
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) {
}
@@ -506,7 +557,7 @@
.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) {
}
@@ -517,15 +568,24 @@
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));
}
@@ -536,7 +596,7 @@
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);
}
@@ -565,7 +625,7 @@
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();
}
@@ -656,7 +716,7 @@
.setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
} else if (transport == TRANSPORT_WIFI) {
WifiInfo wifiInfo = mock(WifiInfo.class);
- when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
+ when(wifiInfo.makeCopy(anyLong())).thenReturn(wifiInfo);
when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
ncBuilder
@@ -782,7 +842,7 @@
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 4fa63d4..c853fc5 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.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 @@
mTestLooper.dispatchAll();
}
- @Test
- public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
+ private void verifyUpdateSubscriptionSnapshotNotifiesConnectionGateways(boolean isActive) {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]);
@@ -150,14 +150,27 @@
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 @@
NetworkRequestListener requestListener,
Set<VcnGatewayConnection> expectedGatewaysTornDown) {
assertFalse(mVcn.isActive());
- assertTrue(mVcn.getVcnGatewayConnections().isEmpty());
for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) {
verify(gatewayConnection).teardownAsynchronously();
}
@@ -238,6 +250,51 @@
}
@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 @@
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 @@
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 02ebaef..74392dd 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -220,11 +220,12 @@
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 d9ad649..4da4019 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 c64f4bc..da0571ba 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 @@
// 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 @@
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 @@
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 @@
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 @@
}
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 @@
}
/**
+ * 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 4b03a49..98a0042 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.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 @@
@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 @@
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 @@
}
/**
+ * 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