Merge "Add the charging ripple effect, currently guarded by flag_charging_ripple." into sc-dev
diff --git a/Android.bp b/Android.bp
index 9690969..ee5e992 100644
--- a/Android.bp
+++ b/Android.bp
@@ -917,7 +917,6 @@
"core/java/com/android/internal/util/RingBufferIndices.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
- "core/java/com/android/internal/util/TrafficStatsConstants.java",
"core/java/com/android/internal/util/WakeupMessage.java",
"core/java/com/android/internal/util/TokenBucket.java",
],
@@ -944,7 +943,6 @@
"core/java/com/android/internal/util/MessageUtils.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
- "core/java/com/android/internal/util/TrafficStatsConstants.java",
"core/java/com/android/internal/util/WakeupMessage.java",
],
}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index bff222e..c61f7c6 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -437,10 +437,6 @@
name: "hwbinder-stubs-docs",
srcs: [
"core/java/android/os/HidlSupport.java",
- "core/java/android/annotation/IntDef.java",
- "core/java/android/annotation/IntRange.java",
- "core/java/android/annotation/NonNull.java",
- "core/java/android/annotation/SystemApi.java",
"core/java/android/os/HidlMemory.java",
"core/java/android/os/HwBinder.java",
"core/java/android/os/HwBlob.java",
@@ -466,6 +462,7 @@
java_library_static {
name: "hwbinder.stubs",
sdk_version: "core_current",
+ libs: ["stub-annotations"],
srcs: [
":hwbinder-stubs-docs",
],
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
index bccef53..3a11417 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java
@@ -53,7 +53,7 @@
"applyPostLayoutPolicy",
"applySurfaceChanges",
"AppTransitionReady",
- "closeSurfaceTransactiom",
+ "closeSurfaceTransaction",
"openSurfaceTransaction",
"performLayout",
"performSurfacePlacement",
@@ -98,6 +98,10 @@
}
mTraceMarkParser.forAllSlices((key, slices) -> {
+ if (slices.size() < 2) {
+ Log.w(TAG, "No sufficient samples: " + key);
+ return;
+ }
for (TraceMarkSlice slice : slices) {
state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
}
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index 5ded446..e08d22c 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -18,17 +18,10 @@
}
public static final class AppSearchManager.SearchContext.Builder {
- ctor public 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 @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
- }
-
- public interface AppSearchMigrationHelper {
- method public void queryAndTransform(@NonNull String, @NonNull android.app.appsearch.AppSearchMigrationHelper.Transformer) throws java.lang.Exception;
- }
-
- public static interface AppSearchMigrationHelper.Transformer {
- method @NonNull public android.app.appsearch.GenericDocument transform(int, int, @NonNull android.app.appsearch.GenericDocument) throws java.lang.Exception;
+ method @Deprecated @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
}
public final class AppSearchResult<ValueType> {
@@ -108,14 +101,8 @@
method @NonNull public android.app.appsearch.AppSearchSchema.Int64PropertyConfig.Builder setCardinality(int);
}
- public static interface AppSearchSchema.Migrator {
- method public default void onDowngrade(int, int, @NonNull android.app.appsearch.AppSearchMigrationHelper) throws java.lang.Exception;
- method public default void onUpgrade(int, int, @NonNull android.app.appsearch.AppSearchMigrationHelper) throws java.lang.Exception;
- }
-
public abstract static class AppSearchSchema.PropertyConfig {
method public int getCardinality();
- method public int getDataType();
method @NonNull public String getName();
field public static final int CARDINALITY_OPTIONAL = 2; // 0x2
field public static final int CARDINALITY_REPEATED = 1; // 0x1
@@ -180,14 +167,15 @@
method public int getScore();
method public long getTtlMillis();
method @NonNull public String getUri();
- field public static final String DEFAULT_NAMESPACE = "";
+ field @Deprecated public static final String DEFAULT_NAMESPACE = "";
}
public static class GenericDocument.Builder<BuilderType extends android.app.appsearch.GenericDocument.Builder> {
- ctor public GenericDocument.Builder(@NonNull String, @NonNull String);
+ 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 @NonNull public BuilderType setNamespace(@NonNull String);
+ 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...);
@@ -206,12 +194,13 @@
}
public static final class GetByUriRequest.Builder {
- ctor public 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 @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String);
+ method @Deprecated @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String);
}
public class GlobalSearchSession implements java.io.Closeable {
@@ -219,6 +208,13 @@
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);
+ }
+
public class PackageIdentifier {
ctor public PackageIdentifier(@NonNull String, @NonNull byte[]);
method @NonNull public String getPackageName();
@@ -242,11 +238,12 @@
}
public static final class RemoveByUriRequest.Builder {
- ctor public 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 @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String);
+ method @Deprecated @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String);
}
public final class ReportUsageRequest {
@@ -256,30 +253,50 @@
}
public static final class ReportUsageRequest.Builder {
- ctor public ReportUsageRequest.Builder();
+ ctor @Deprecated public ReportUsageRequest.Builder();
+ ctor public ReportUsageRequest.Builder(@NonNull String);
method @NonNull public android.app.appsearch.ReportUsageRequest build();
- method @NonNull public android.app.appsearch.ReportUsageRequest.Builder setNamespace(@NonNull String);
+ 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 @NonNull public android.app.appsearch.GenericDocument getDocument();
+ 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();
}
+ public static final class SearchResult.Builder {
+ ctor public SearchResult.Builder(@NonNull String, @NonNull String);
+ 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);
+ }
+
public static final class SearchResult.MatchInfo {
method @NonNull public CharSequence getExactMatch();
- method @NonNull public android.app.appsearch.SearchResult.MatchRange getExactMatchPosition();
+ 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 @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetPosition();
+ method @Deprecated @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetPosition();
+ method @NonNull public android.app.appsearch.SearchResult.MatchRange getSnippetRange();
+ }
+
+ public static final class SearchResult.MatchInfo.Builder {
+ ctor public SearchResult.MatchInfo.Builder();
+ method @NonNull public android.app.appsearch.SearchResult.MatchInfo build();
+ method @NonNull public android.app.appsearch.SearchResult.MatchInfo.Builder setExactMatchRange(@NonNull android.app.appsearch.SearchResult.MatchRange);
+ method @NonNull public android.app.appsearch.SearchResult.MatchInfo.Builder setPropertyPath(@NonNull String);
+ method @NonNull public android.app.appsearch.SearchResult.MatchInfo.Builder setSnippetRange(@NonNull android.app.appsearch.SearchResult.MatchRange);
}
public static final class SearchResult.MatchRange {
+ ctor public SearchResult.MatchRange(int, int);
method public int getEnd();
method public int getStart();
}
@@ -334,7 +351,7 @@
}
public final class SetSchemaRequest {
- method @NonNull public java.util.Map<java.lang.String,android.app.appsearch.AppSearchSchema.Migrator> getMigrators();
+ 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();
@@ -348,7 +365,7 @@
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
method @NonNull public android.app.appsearch.SetSchemaRequest build();
method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
- method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.Migrator);
+ method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull 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);
@@ -361,6 +378,19 @@
method @NonNull public java.util.List<android.app.appsearch.SetSchemaResponse.MigrationFailure> getMigrationFailures();
}
+ public static final class SetSchemaResponse.Builder {
+ ctor public SetSchemaResponse.Builder();
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addDeletedType(@NonNull String);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addDeletedTypes(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addIncompatibleType(@NonNull String);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addIncompatibleTypes(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigratedType(@NonNull String);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigratedTypes(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigrationFailure(@NonNull android.app.appsearch.SetSchemaResponse.MigrationFailure);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.Builder addMigrationFailures(@NonNull java.util.Collection<android.app.appsearch.SetSchemaResponse.MigrationFailure>);
+ method @NonNull public android.app.appsearch.SetSchemaResponse build();
+ }
+
public static class SetSchemaResponse.MigrationFailure {
method @NonNull public android.app.appsearch.AppSearchResult<java.lang.Void> getAppSearchResult();
method @NonNull public String getNamespace();
@@ -368,11 +398,23 @@
method @NonNull public String getUri();
}
+ public static final class SetSchemaResponse.MigrationFailure.Builder {
+ ctor public SetSchemaResponse.MigrationFailure.Builder();
+ method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure build();
+ method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure.Builder setAppSearchResult(@NonNull android.app.appsearch.AppSearchResult<java.lang.Void>);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure.Builder setNamespace(@NonNull String);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure.Builder setSchemaType(@NonNull String);
+ method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure.Builder setUri(@NonNull String);
+ }
+
}
package android.app.appsearch.exceptions {
public class AppSearchException extends java.lang.Exception {
+ ctor public AppSearchException(int);
+ ctor public AppSearchException(int, @Nullable String);
+ ctor public AppSearchException(int, @Nullable String, @Nullable Throwable);
method public int getResultCode();
method @NonNull public <T> android.app.appsearch.AppSearchResult<T> toAppSearchResult();
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index a62bb50..0c6b86b 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -30,17 +30,19 @@
* Provides access to the centralized AppSearch index maintained by the system.
*
* <p>AppSearch is a search library for managing structured data featuring:
+ *
* <ul>
- * <li>A fully offline on-device solution
- * <li>A set of APIs for applications to index documents and retrieve them via full-text search
- * <li>APIs for applications to allow the System to display their content on system UI surfaces
- * <li>Similarly, APIs for applications to allow the System to share their content with other
- * specified applications.
+ * <li>A fully offline on-device solution
+ * <li>A set of APIs for applications to index documents and retrieve them via full-text search
+ * <li>APIs for applications to allow the System to display their content on system UI surfaces
+ * <li>Similarly, APIs for applications to allow the System to share their content with other
+ * specified applications.
* </ul>
*
* <p>Applications create a database by opening an {@link AppSearchSession}.
*
* <p>Example:
+ *
* <pre>
* AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
*
@@ -51,11 +53,12 @@
* });</pre>
*
* <p>After opening the session, a schema must be set in order to define the organizational
- * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema
- * is composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique
- * type of data.
+ * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema is
+ * composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique type
+ * of data.
*
* <p>Example:
+ *
* <pre>
* AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email")
* .addProperty(new StringPropertyConfig.Builder("subject")
@@ -73,15 +76,16 @@
* });</pre>
*
* <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object,
- * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a
- * logical group of documents. For example, a namespace can be created to group documents on a
- * per-account basis. A URI identifies a single document within a namespace. The combination
- * of URI and namespace uniquely identifies a {@link GenericDocument} in the database.
+ * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a logical
+ * group of documents. For example, a namespace can be created to group documents on a per-account
+ * basis. A URI identifies a single document within a namespace. The combination of URI and
+ * namespace uniquely identifies a {@link GenericDocument} in the database.
*
- * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database
- * and indexed by calling {@link AppSearchSession#put}.
+ * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database and
+ * indexed by calling {@link AppSearchSession#put}.
*
* <p>Example:
+ *
* <pre>
* // Although for this example we use GenericDocument directly, we recommend extending
* // GenericDocument to create specific types (i.e. Email) with specific setters/getters.
@@ -106,18 +110,12 @@
* and namespace.
*
* <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove
- * operation. Remove operations can be done by URI and namespace via
- * {@link AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)},
- * or by query via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
+ * operation. Remove operations can be done by URI and namespace via {@link
+ * AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)}, or by query via
+ * {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
*/
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
- /**
- * The default empty database name.
- *
- * @hide
- */
- public static final String DEFAULT_DATABASE_NAME = "";
private final IAppSearchManager mService;
private final Context mContext;
@@ -149,10 +147,42 @@
/** Builder for {@link SearchContext} objects. */
public static final class Builder {
- private String mDatabaseName = DEFAULT_DATABASE_NAME;
+ private 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.
+ *
+ * <p>Databases with different names are fully separate with distinct types, namespaces,
+ * and data.
+ *
+ * <p>Database name cannot contain {@code '/'}.
+ *
+ * @param databaseName The name of the database.
+ * @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+ */
+ public Builder(@NonNull String databaseName) {
+ Objects.requireNonNull(databaseName);
+ Preconditions.checkArgument(
+ !databaseName.contains("/"), "Database name cannot contain '/'");
+ 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.
@@ -164,16 +194,21 @@
*
* <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);
- if (databaseName.contains("/")) {
- throw new IllegalArgumentException("Database name cannot contain '/'");
- }
+ Preconditions.checkArgument(
+ !databaseName.contains("/"), "Database name cannot contain '/'");
mDatabaseName = databaseName;
return this;
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index f379739..9ea73a9 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -114,8 +114,6 @@
* @param executor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from setting the schema. If the
* operation succeeds, the callback will be invoked with {@code null}.
- * @see android.app.appsearch.AppSearchSchema.Migrator
- * @see android.app.appsearch.AppSearchMigrationHelper.Transformer
*/
// TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
// exposed.
@@ -241,6 +239,7 @@
documentBundles.add(documents.get(i).getBundle());
}
try {
+ // TODO(b/173532925) a timestamp needs to be sent here to calculate binder latency
mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
new IAppSearchBatchResultCallback.Stub() {
public void onResult(AppSearchBatchResult result) {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java
index d394904..77740f8 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchEmail.java
@@ -152,14 +152,14 @@
/** The builder class for {@link AppSearchEmail}. */
public static class Builder extends GenericDocument.Builder<AppSearchEmail.Builder> {
-
/**
* Creates a new {@link AppSearchEmail.Builder}
*
+ * @param namespace The namespace of the Email.
* @param uri The Uri of the Email.
*/
- public Builder(@NonNull String uri) {
- super(uri, SCHEMA_TYPE);
+ public Builder(@NonNull String namespace, @NonNull String uri) {
+ super(namespace, uri, SCHEMA_TYPE);
}
/** Sets the from address of {@link AppSearchEmail} */
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java
deleted file mode 100644
index 37943fc..0000000
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchMigrationHelper.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.appsearch;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-
-/**
- * The helper class for {@link AppSearchSchema} migration.
- *
- * <p>It will query and migrate {@link GenericDocument} in given type to a new version.
- */
-public interface AppSearchMigrationHelper {
-
- /**
- * Queries all documents that need to be migrated to the different version, and transform
- * documents to that version by passing them to the provided {@link Transformer}.
- *
- * @param schemaType The schema that need be updated and migrated {@link GenericDocument} under
- * this type.
- * @param transformer The {@link Transformer} that will upgrade or downgrade a {@link
- * GenericDocument} to new version.
- * @see Transformer#transform
- */
- // Rethrow the Generic Exception thrown from the Transformer.
- @SuppressLint("GenericException")
- void queryAndTransform(@NonNull String schemaType, @NonNull Transformer transformer)
- throws Exception;
-
- /** The class to migrate {@link GenericDocument} between different version. */
- interface Transformer {
-
- /**
- * Translates a {@link GenericDocument} from a version to a different version.
- *
- * <p>If the uri, schema type or namespace is changed via the transform, it will apply to
- * the new {@link GenericDocument}.
- *
- * @param currentVersion The current version of the document's schema.
- * @param finalVersion The final version that documents need to be migrated to.
- * @param document The {@link GenericDocument} need to be translated to new version.
- * @return A {@link GenericDocument} in new version.
- */
- @NonNull
- // This method will be overridden by users, allow them to throw any customer Exceptions.
- @SuppressLint("GenericException")
- GenericDocument transform(
- int currentVersion, int finalVersion, @NonNull GenericDocument document)
- throws Exception;
- }
-}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 8bf438d..55f0c80 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -20,7 +20,6 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.app.appsearch.exceptions.IllegalSchemaException;
import android.app.appsearch.util.BundleUtil;
import android.os.Bundle;
@@ -179,7 +178,7 @@
* @throws IllegalStateException if the version is negative or the builder has already been
* used.
* @see AppSearchSession#setSchema
- * @see AppSearchSchema.Migrator
+ * @see Migrator
* @see SetSchemaRequest.Builder#setMigrator
*/
@NonNull
@@ -309,7 +308,11 @@
return mBundle.getString(NAME_FIELD, "");
}
- /** Returns the type of data the property contains (e.g. string, int, bytes, etc). */
+ /**
+ * Returns the type of data the property contains (e.g. string, int, bytes, etc).
+ *
+ * @hide
+ */
public @DataType int getDataType() {
return mBundle.getInt(DATA_TYPE_FIELD, -1);
}
@@ -857,43 +860,4 @@
}
}
}
-
- /**
- * A migrator class to translate {@link GenericDocument} from different version of {@link
- * AppSearchSchema}
- */
- public interface Migrator {
-
- /**
- * Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}.
- *
- * <p>This methods will be invoked only if the {@link SetSchemaRequest} is setting a higher
- * version number than the current {@link AppSearchSchema} saved in AppSearch.
- *
- * @param currentVersion The current version of the document's schema.
- * @param targetVersion The final version that documents need to be migrated to.
- * @param helper The helper class could help to query all documents need to be migrated.
- */
- // This method will be overridden by users, allow them to throw any customer Exceptions.
- @SuppressLint("GenericException")
- default void onUpgrade(
- int currentVersion, int targetVersion, @NonNull AppSearchMigrationHelper helper)
- throws Exception {}
-
- /**
- * Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}.
- *
- * <p>The methods will be invoked only if the {@link SetSchemaRequest} is setting a higher
- * version number than the current {@link AppSearchSchema} saved in AppSearch.
- *
- * @param currentVersion The current version of the document's schema.
- * @param targetVersion The final version that documents need to be migrated to.
- * @param helper The helper class could help to query all documents need to be migrated.
- */
- // This method will be overridden by users, allow them to throw any customer Exceptions.
- @SuppressLint("GenericException")
- default void onDowngrade(
- int currentVersion, int targetVersion, @NonNull AppSearchMigrationHelper helper)
- throws Exception {}
- }
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 72bb9f3..4ce95ea 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -46,8 +46,14 @@
public class GenericDocument {
private static final String TAG = "AppSearchGenericDocumen";
- /** The default empty namespace. */
- public static final String DEFAULT_NAMESPACE = "";
+ /**
+ * 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;
@@ -141,7 +147,7 @@
/** Returns the namespace of the {@link GenericDocument}. */
@NonNull
public String getNamespace() {
- return mBundle.getString(NAMESPACE_FIELD, DEFAULT_NAMESPACE);
+ return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
}
/** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */
@@ -579,6 +585,9 @@
*
* <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}
@@ -586,7 +595,10 @@
* 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);
@@ -604,6 +616,41 @@
}
/**
+ * 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.
+ *
+ * @param namespace the namespace to set for the {@link GenericDocument}.
+ * @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}.
+ */
+ @SuppressWarnings("unchecked")
+ public Builder(@NonNull String namespace, @NonNull String uri, @NonNull String schemaType) {
+ Preconditions.checkNotNull(namespace);
+ Preconditions.checkNotNull(uri);
+ Preconditions.checkNotNull(schemaType);
+ mBuilderTypeInstance = (BuilderType) this;
+ mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
+ mBundle.putString(GenericDocument.URI_FIELD, uri);
+ mBundle.putString(GenericDocument.SCHEMA_TYPE_FIELD, schemaType);
+ // 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);
+ }
+
+ /**
* Sets the app-defined namespace this document resides in. No special values are reserved
* or understood by the infrastructure.
*
@@ -611,8 +658,14 @@
*
* <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");
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 17266f8..6881a27 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -107,19 +107,40 @@
* <p>Once {@link #build} is called, the instance can no longer be used.
*/
public static final class Builder {
- private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
+ private 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 {@link
- * GenericDocument#DEFAULT_NAMESPACE}.
+ * <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");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java
new file mode 100644
index 0000000..5ae9a41
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A migrator class to translate {@link GenericDocument} from different version of {@link
+ * AppSearchSchema}
+ *
+ * <p>Make non-backwards-compatible changes will delete all stored documents in old schema. You can
+ * save your documents by setting {@link Migrator} via the {@link
+ * SetSchemaRequest.Builder#setMigrator} for each type and target version you want to save.
+ *
+ * <p>{@link #onDowngrade} or {@link #onUpgrade} will be triggered if the version number of the
+ * schema stored in AppSearch is different with the version in the request.
+ *
+ * <p>If any error or Exception occurred in the {@link #onDowngrade} or {@link #onUpgrade}, all the
+ * setSchema request will be rejected unless the schema changes are backwards-compatible, and stored
+ * documents won't have any observable changes.
+ */
+public abstract class Migrator {
+ private final int mStartVersion;
+
+ /**
+ * Creates a {@link Migrator} will trigger migration for any version less than the final version
+ * in the new schema.
+ */
+ public Migrator() {
+ this(/*startVersion=*/ 0);
+ }
+
+ /**
+ * Creates a {@link Migrator} with a non-negative start version.
+ *
+ * <p>Providing 0 will trigger migration for any version less than the final version in the new
+ * schema.
+ *
+ * @param startVersion The migration will be only triggered for those versions greater or equal
+ * to the given startVersion.
+ */
+ 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;
+ }
+
+ /**
+ * Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}.
+ *
+ * <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.
+ *
+ * @param currentVersion The current version of the document's schema.
+ * @param targetVersion 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);
+
+ /**
+ * Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}.
+ *
+ * <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>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 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);
+}
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 39b53b6..455cf3a 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -59,17 +59,39 @@
* <p>Once {@link #build} is called, the instance can no longer be used.
*/
public static final class Builder {
- private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
+ private 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 {@link GenericDocument#DEFAULT_NAMESPACE}.
+ * <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");
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 2bfcf28..2cd08c6 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
@@ -62,18 +62,40 @@
/** Builder for {@link ReportUsageRequest} objects. */
public static final class Builder {
- private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
+ private 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 {@link GenericDocument#DEFAULT_NAMESPACE}.
+ * <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");
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 f34034b..cb20849 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -32,7 +32,7 @@
* <p>This allows clients to obtain:
*
* <ul>
- * <li>The document which matched, using {@link #getDocument}
+ * <li>The document which matched, using {@link #getGenericDocument}
* <li>Information about which properties in the document matched, and "snippet" information
* containing textual summaries of the document's matches, using {@link #getMatches}
* </ul>
@@ -43,17 +43,10 @@
* @see SearchResults
*/
public final class SearchResult {
- /** @hide */
- public static final String DOCUMENT_FIELD = "document";
-
- /** @hide */
- public static final String MATCHES_FIELD = "matches";
-
- /** @hide */
- public static final String PACKAGE_NAME_FIELD = "packageName";
-
- /** @hide */
- public static final String DATABASE_NAME_FIELD = "databaseName";
+ static final String DOCUMENT_FIELD = "document";
+ static final String MATCHES_FIELD = "matches";
+ static final String PACKAGE_NAME_FIELD = "packageName";
+ static final String DATABASE_NAME_FIELD = "databaseName";
@NonNull private final Bundle mBundle;
@@ -74,13 +67,20 @@
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}.
*
* @return Document object which matched the query.
*/
@NonNull
- public GenericDocument getDocument() {
+ public GenericDocument getGenericDocument() {
if (mDocument == null) {
mDocument =
new GenericDocument(
@@ -104,7 +104,7 @@
Preconditions.checkNotNull(mBundle.getParcelableArrayList(MATCHES_FIELD));
mMatches = new ArrayList<>(matchBundles.size());
for (int i = 0; i < matchBundles.size(); i++) {
- MatchInfo matchInfo = new MatchInfo(getDocument(), matchBundles.get(i));
+ MatchInfo matchInfo = new MatchInfo(matchBundles.get(i), getGenericDocument());
mMatches.add(matchInfo);
}
}
@@ -124,13 +124,69 @@
/**
* Contains the database name that stored the {@link GenericDocument}.
*
- * @return Database name that stored the document
+ * @return Name of the database within which the document is stored
*/
@NonNull
public String getDatabaseName() {
return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD));
}
+ /** Builder for {@link SearchResult} objects. */
+ public static final class Builder {
+ private final Bundle mBundle = new Bundle();
+ private final ArrayList<Bundle> mMatchInfos = new ArrayList<>();
+
+ private boolean mBuilt;
+
+ /**
+ * Constructs a new builder for {@link SearchResult} objects.
+ *
+ * @param packageName the package name the matched document belongs to
+ * @param databaseName the database name the matched document belongs to.
+ */
+ public Builder(@NonNull String packageName, @NonNull String databaseName) {
+ mBundle.putString(PACKAGE_NAME_FIELD, Preconditions.checkNotNull(packageName));
+ mBundle.putString(DATABASE_NAME_FIELD, Preconditions.checkNotNull(databaseName));
+ }
+
+ /**
+ * Sets the document which matched.
+ *
+ * @throws IllegalStateException if the builder has already been used
+ */
+ @NonNull
+ public Builder setGenericDocument(@NonNull GenericDocument document) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putBundle(DOCUMENT_FIELD, document.getBundle());
+ return this;
+ }
+
+ /** Adds another match to this SearchResult. */
+ @NonNull
+ public Builder addMatch(@NonNull MatchInfo matchInfo) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkState(
+ matchInfo.mDocument == null,
+ "This MatchInfo is already associated with a SearchResult and can't be "
+ + "reassigned");
+ mMatchInfos.add(matchInfo.mBundle);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link SearchResult}.
+ *
+ * @throws IllegalStateException if the builder has already been used
+ */
+ @NonNull
+ public SearchResult build() {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putParcelableArrayList(MATCHES_FIELD, mMatchInfos);
+ mBuilt = true;
+ return new SearchResult(mBundle);
+ }
+ }
+
/**
* This class represents a match objects for any Snippets that might be present in {@link
* SearchResults} from query. Using this class user can get the full text, exact matches and
@@ -147,11 +203,11 @@
* <p>{@link MatchInfo#getFullText()} returns "A commonly used fake word is foo. Another
* nonsense word that’s used a lot is bar."
*
- * <p>{@link MatchInfo#getExactMatchPosition()} returns [29, 32]
+ * <p>{@link MatchInfo#getExactMatchRange()} returns [29, 32]
*
* <p>{@link MatchInfo#getExactMatch()} returns "foo"
*
- * <p>{@link MatchInfo#getSnippetPosition()} returns [26, 33]
+ * <p>{@link MatchInfo#getSnippetRange()} returns [26, 33]
*
* <p>{@link MatchInfo#getSnippet()} returns "is foo."
*
@@ -172,11 +228,11 @@
*
* <p>{@link MatchInfo#getFullText()} returns "Test Name Jr."
*
- * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 4]
+ * <p>{@link MatchInfo#getExactMatchRange()} returns [0, 4]
*
* <p>{@link MatchInfo#getExactMatch()} returns "Test"
*
- * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 9]
+ * <p>{@link MatchInfo#getSnippetRange()} returns [0, 9]
*
* <p>{@link MatchInfo#getSnippet()} returns "Test Name"
*
@@ -186,52 +242,54 @@
*
* <p>{@link MatchInfo#getFullText()} returns "TestNameJr@gmail.com"
*
- * <p>{@link MatchInfo#getExactMatchPosition()} returns [0, 20]
+ * <p>{@link MatchInfo#getExactMatchRange()} returns [0, 20]
*
* <p>{@link MatchInfo#getExactMatch()} returns "TestNameJr@gmail.com"
*
- * <p>{@link MatchInfo#getSnippetPosition()} returns [0, 20]
+ * <p>{@link MatchInfo#getSnippetRange()} returns [0, 20]
*
* <p>{@link MatchInfo#getSnippet()} returns "TestNameJr@gmail.com"
*/
public static final class MatchInfo {
- /**
- * The path of the matching snippet property.
- *
- * @hide
- */
- public static final String PROPERTY_PATH_FIELD = "propertyPath";
+ /** The path of the matching snippet property. */
+ private static final String PROPERTY_PATH_FIELD = "propertyPath";
- /** @hide */
- public static final String EXACT_MATCH_POSITION_LOWER_FIELD = "exactMatchPositionLower";
+ private static final String EXACT_MATCH_RANGE_LOWER_FIELD = "exactMatchRangeLower";
+ private static final String EXACT_MATCH_RANGE_UPPER_FIELD = "exactMatchRangeUpper";
+ private static final String SNIPPET_RANGE_LOWER_FIELD = "snippetRangeLower";
+ private static final String SNIPPET_RANGE_UPPER_FIELD = "snippetRangeUpper";
- /** @hide */
- public static final String EXACT_MATCH_POSITION_UPPER_FIELD = "exactMatchPositionUpper";
-
- /** @hide */
- public static final String WINDOW_POSITION_LOWER_FIELD = "windowPositionLower";
-
- /** @hide */
- public static final String WINDOW_POSITION_UPPER_FIELD = "windowPositionUpper";
-
- private final String mFullText;
private final String mPropertyPath;
- private final Bundle mBundle;
- private MatchRange mExactMatchRange;
- private MatchRange mWindowRange;
+ final Bundle mBundle;
- MatchInfo(@NonNull GenericDocument document, @NonNull Bundle bundle) {
+ /**
+ * Document which the match comes from.
+ *
+ * <p>If this is {@code null}, methods which require access to the document, like {@link
+ * #getExactMatch}, will throw {@link NullPointerException}.
+ */
+ @Nullable final GenericDocument mDocument;
+
+ /** Full text of the matched property. Populated on first use. */
+ @Nullable private String mFullText;
+
+ /** Range of property that exactly matched the query. Populated on first use. */
+ @Nullable private MatchRange mExactMatchRange;
+
+ /** Range of some reasonable amount of context around the query. Populated on first use. */
+ @Nullable private MatchRange mWindowRange;
+
+ MatchInfo(@NonNull Bundle bundle, @Nullable GenericDocument document) {
mBundle = Preconditions.checkNotNull(bundle);
- Preconditions.checkNotNull(document);
+ mDocument = document;
mPropertyPath = Preconditions.checkNotNull(bundle.getString(PROPERTY_PATH_FIELD));
- mFullText = getPropertyValues(document, mPropertyPath);
}
/**
* Gets the property path corresponding to the given entry.
*
- * <p>Property Path: '.' - delimited sequence of property names indicating which property in
- * the Document these snippets correspond to.
+ * <p>A property path is a '.' - delimited sequence of property names indicating which
+ * property in the document these snippets correspond to.
*
* <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. For class
* example 1 this returns "subject"
@@ -249,21 +307,34 @@
*/
@NonNull
public String getFullText() {
+ if (mFullText == null) {
+ Preconditions.checkState(
+ mDocument != null,
+ "Document has not been populated; this MatchInfo cannot be used yet");
+ mFullText = getPropertyValues(mDocument, mPropertyPath);
+ }
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.
*
* <p>For class example 1 this returns [29, 32]
*/
@NonNull
- public MatchRange getExactMatchPosition() {
+ public MatchRange getExactMatchRange() {
if (mExactMatchRange == null) {
mExactMatchRange =
new MatchRange(
- mBundle.getInt(EXACT_MATCH_POSITION_LOWER_FIELD),
- mBundle.getInt(EXACT_MATCH_POSITION_UPPER_FIELD));
+ mBundle.getInt(EXACT_MATCH_RANGE_LOWER_FIELD),
+ mBundle.getInt(EXACT_MATCH_RANGE_UPPER_FIELD));
}
return mExactMatchRange;
}
@@ -275,7 +346,14 @@
*/
@NonNull
public CharSequence getExactMatch() {
- return getSubstring(getExactMatchPosition());
+ return getSubstring(getExactMatchRange());
+ }
+
+ /** @deprecated TODO(b/181887768): This method exists only for dogfooder transition. */
+ @NonNull
+ @Deprecated
+ public MatchRange getSnippetPosition() {
+ return getSnippetRange();
}
/**
@@ -287,12 +365,12 @@
* <p>For class example 1 this returns [29, 41].
*/
@NonNull
- public MatchRange getSnippetPosition() {
+ public MatchRange getSnippetRange() {
if (mWindowRange == null) {
mWindowRange =
new MatchRange(
- mBundle.getInt(WINDOW_POSITION_LOWER_FIELD),
- mBundle.getInt(WINDOW_POSITION_UPPER_FIELD));
+ mBundle.getInt(SNIPPET_RANGE_LOWER_FIELD),
+ mBundle.getInt(SNIPPET_RANGE_UPPER_FIELD));
}
return mWindowRange;
}
@@ -309,7 +387,7 @@
*/
@NonNull
public CharSequence getSnippet() {
- return getSubstring(getSnippetPosition());
+ return getSubstring(getSnippetRange());
}
private CharSequence getSubstring(MatchRange range) {
@@ -331,6 +409,72 @@
// TODO(b/175146044): Return the proper match based on the index in the propertyName.
return values[0];
}
+
+ /** Builder for {@link MatchInfo} objects. */
+ public static final class Builder {
+ private final Bundle mBundle = new Bundle();
+ private boolean mBuilt = false;
+
+ /**
+ * Sets the property path corresponding to the given entry.
+ *
+ * <p>A property path is a '.' - delimited sequence of property names indicating which
+ * property in the document these snippets correspond to.
+ *
+ * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc. For class
+ * example 1 this returns "subject"
+ *
+ * @throws IllegalStateException if the builder has already been used
+ */
+ @NonNull
+ public Builder setPropertyPath(@NonNull String propertyPath) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putString(
+ SearchResult.MatchInfo.PROPERTY_PATH_FIELD,
+ Preconditions.checkNotNull(propertyPath));
+ return this;
+ }
+
+ /**
+ * Sets the exact {@link MatchRange} corresponding to the given entry.
+ *
+ * @throws IllegalStateException if the builder has already been used
+ */
+ @NonNull
+ public Builder setExactMatchRange(@NonNull MatchRange matchRange) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(matchRange);
+ mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_LOWER_FIELD, matchRange.getStart());
+ mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_UPPER_FIELD, matchRange.getEnd());
+ return this;
+ }
+
+ /**
+ * Sets the snippet {@link MatchRange} corresponding to the given entry.
+ *
+ * @throws IllegalStateException if the builder has already been used
+ */
+ @NonNull
+ public Builder setSnippetRange(@NonNull MatchRange matchRange) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(matchRange);
+ mBundle.putInt(MatchInfo.SNIPPET_RANGE_LOWER_FIELD, matchRange.getStart());
+ mBundle.putInt(MatchInfo.SNIPPET_RANGE_UPPER_FIELD, matchRange.getEnd());
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link MatchInfo}.
+ *
+ * @throws IllegalStateException if the builder has already been used
+ */
+ @NonNull
+ public MatchInfo build() {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBuilt = true;
+ return new MatchInfo(mBundle, /*document=*/ null);
+ }
+ }
}
/**
@@ -353,7 +497,6 @@
*
* @param start The start point (inclusive)
* @param end The end point (exclusive)
- * @hide
*/
public MatchRange(int start, int end) {
if (start > end) {
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 c054063..c1eedcd 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -63,24 +63,33 @@
* android.app.appsearch.exceptions.AppSearchException}, with a message describing the
* incompatibility. As a result, the previously set schema will remain unchanged.
*
- * <p>Backward incompatible changes can be made by setting {@link
- * SetSchemaRequest.Builder#setForceOverride} method to {@code true}. This deletes all documents
- * that are incompatible with the new schema. The new schema is then saved and persisted to disk.
+ * <p>Backward incompatible changes can be made by :
+ *
+ * <ul>
+ * <li>setting {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. This
+ * deletes all documents that are incompatible with the new schema. The new schema is then
+ * saved and persisted to disk.
+ * <li>Add a {@link Migrator} for each incompatible type and make no deletion. The migrator will
+ * migrate documents from it's old schema version to the new version. Migrated types will be
+ * set into both {@link SetSchemaResponse#getIncompatibleTypes()} and {@link
+ * SetSchemaResponse#getMigratedTypes()}. See the migration section below.
+ * </ul>
*
* @see AppSearchSession#setSchema
+ * @see Migrator
*/
public final class SetSchemaRequest {
private final Set<AppSearchSchema> mSchemas;
private final Set<String> mSchemasNotDisplayedBySystem;
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
- private final Map<String, AppSearchSchema.Migrator> mMigrators;
+ private final Map<String, Migrator> mMigrators;
private final boolean mForceOverride;
SetSchemaRequest(
@NonNull Set<AppSearchSchema> schemas,
@NonNull Set<String> schemasNotDisplayedBySystem,
@NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages,
- @NonNull Map<String, AppSearchSchema.Migrator> migrators,
+ @NonNull Map<String, Migrator> migrators,
boolean forceOverride) {
mSchemas = Preconditions.checkNotNull(schemas);
mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem);
@@ -129,9 +138,12 @@
return copy;
}
- /** Returns the map of {@link android.app.appsearch.AppSearchSchema.Migrator}. */
+ /**
+ * Returns the map of {@link Migrator}, the key will be the schema type of the {@link Migrator}
+ * associated with.
+ */
@NonNull
- public Map<String, AppSearchSchema.Migrator> getMigrators() {
+ public Map<String, Migrator> getMigrators() {
return Collections.unmodifiableMap(mMigrators);
}
@@ -164,7 +176,7 @@
private final Set<String> mSchemasNotDisplayedBySystem = new ArraySet<>();
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
new ArrayMap<>();
- private final Map<String, AppSearchSchema.Migrator> mMigrators = new ArrayMap<>();
+ private final Map<String, Migrator> mMigrators = new ArrayMap<>();
private boolean mForceOverride = false;
private boolean mBuilt = false;
@@ -295,7 +307,7 @@
}
/**
- * Sets the {@link android.app.appsearch.AppSearchSchema.Migrator}.
+ * Sets the {@link Migrator}.
*
* @param schemaType The schema type to set migrator on.
* @param migrator The migrator translate a document from it's old version to a new
@@ -303,8 +315,7 @@
*/
@NonNull
@SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects.
- public Builder setMigrator(
- @NonNull String schemaType, @NonNull AppSearchSchema.Migrator migrator) {
+ public Builder setMigrator(@NonNull String schemaType, @NonNull Migrator migrator) {
Preconditions.checkNotNull(schemaType);
Preconditions.checkNotNull(migrator);
mMigrators.put(schemaType, migrator);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index a146006..d63e437 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -128,8 +128,8 @@
* Returns a {@link Set} of schema type whose new definitions set in the {@link
* AppSearchSession#setSchema} call were incompatible with the pre-existing schema.
*
- * <p>If a {@link android.app.appsearch.AppSearchSchema.Migrator} is provided for this type and
- * the migration is success triggered. The type will also appear in {@link #getMigratedTypes()}.
+ * <p>If a {@link Migrator} is provided for this type and the migration is success triggered.
+ * The type will also appear in {@link #getMigratedTypes()}.
*
* @see AppSearchSession#setSchema
* @see SetSchemaRequest.Builder#setForceOverride
@@ -160,12 +160,8 @@
.addMigrationFailures(mMigrationFailures);
}
- /**
- * Builder for {@link SetSchemaResponse} objects.
- *
- * @hide
- */
- public static class Builder {
+ /** Builder for {@link SetSchemaResponse} objects. */
+ public static final class Builder {
private final ArrayList<MigrationFailure> mMigrationFailures = new ArrayList<>();
private final ArrayList<String> mDeletedTypes = new ArrayList<>();
private final ArrayList<String> mMigratedTypes = new ArrayList<>();
@@ -309,12 +305,8 @@
mBundle.getString(ERROR_MESSAGE_FIELD, /*defaultValue=*/ ""));
}
- /**
- * Builder for {@link MigrationFailure} objects.
- *
- * @hide
- */
- public static class Builder {
+ /** Builder for {@link MigrationFailure} objects. */
+ public static final class Builder {
private String mSchemaType;
private String mNamespace;
private String mUri;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java b/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java
index b1a33a4..ca4ea2b 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/exceptions/AppSearchException.java
@@ -32,19 +32,27 @@
/**
* Initializes an {@link AppSearchException} with no message.
*
- * @hide
+ * @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
*/
public AppSearchException(@AppSearchResult.ResultCode int resultCode) {
this(resultCode, /*message=*/ null);
}
- /** @hide */
+ /**
+ * Initializes an {@link AppSearchException} with a result code and message.
+ *
+ * @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
+ */
public AppSearchException(
@AppSearchResult.ResultCode int resultCode, @Nullable String message) {
this(resultCode, message, /*cause=*/ null);
}
- /** @hide */
+ /**
+ * Initializes an {@link AppSearchException} with a result code, message and cause.
+ *
+ * @param resultCode One of the constants documented in {@link AppSearchResult#getResultCode}.
+ */
public AppSearchException(
@AppSearchResult.ResultCode int resultCode,
@Nullable String message,
@@ -53,12 +61,16 @@
mResultCode = resultCode;
}
- /** Returns the result code this exception was constructed with. */
+ /**
+ * Returns the result code this exception was constructed with.
+ *
+ * @return One of the constants documented in {@link AppSearchResult#getResultCode}.
+ */
public @AppSearchResult.ResultCode int getResultCode() {
return mResultCode;
}
- /** Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult} */
+ /** Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult}. */
@NonNull
public <T> AppSearchResult<T> toAppSearchResult() {
return AppSearchResult.newFailedResult(mResultCode, getMessage());
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
new file mode 100644
index 0000000..fae8ad4
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
@@ -0,0 +1,118 @@
+/*
+ * 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.util;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.Migrator;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utilities for schema migration.
+ *
+ * @hide
+ */
+public final class SchemaMigrationUtil {
+ private static final String TAG = "AppSearchMigrateUtil";
+
+ private SchemaMigrationUtil() {}
+
+ /**
+ * Finds out which incompatible schema type won't be migrated by comparing its current and final
+ * version number.
+ */
+ @NonNull
+ public static Set<String> getUnmigratedIncompatibleTypes(
+ @NonNull Set<String> incompatibleSchemaTypes,
+ @NonNull Map<String, Migrator> migrators,
+ @NonNull Map<String, Integer> currentVersionMap,
+ @NonNull Map<String, Integer> finalVersionMap)
+ throws AppSearchException {
+ Set<String> unmigratedSchemaTypes = new ArraySet<>();
+ for (String unmigratedSchemaType : incompatibleSchemaTypes) {
+ Integer currentVersion = currentVersionMap.get(unmigratedSchemaType);
+ Integer finalVersion = finalVersionMap.get(unmigratedSchemaType);
+ if (currentVersion == null) {
+ // impossible, we have done something wrong.
+ throw new AppSearchException(
+ AppSearchResult.RESULT_UNKNOWN_ERROR,
+ "Cannot find the current version number for schema type: "
+ + unmigratedSchemaType);
+ }
+ if (finalVersion == null) {
+ // The schema doesn't exist in the SetSchemaRequest.
+ unmigratedSchemaTypes.add(unmigratedSchemaType);
+ continue;
+ }
+ // we don't have migrator or won't trigger migration for this schema type.
+ Migrator migrator = migrators.get(unmigratedSchemaType);
+ if (migrator == null
+ || !migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion)) {
+ unmigratedSchemaTypes.add(unmigratedSchemaType);
+ }
+ }
+ return Collections.unmodifiableSet(unmigratedSchemaTypes);
+ }
+
+ /**
+ * Triggers upgrade or downgrade migration for the given schema type if its version stored in
+ * AppSearch is different with the version in the request.
+ *
+ * @return {@code True} if we trigger the migration for the given type.
+ */
+ public static boolean shouldTriggerMigration(
+ @NonNull String schemaType,
+ @NonNull Migrator migrator,
+ @NonNull Map<String, Integer> currentVersionMap,
+ @NonNull Map<String, Integer> finalVersionMap)
+ throws AppSearchException {
+ Integer currentVersion = currentVersionMap.get(schemaType);
+ Integer finalVersion = finalVersionMap.get(schemaType);
+ if (currentVersion == null) {
+ Log.d(TAG, "The SchemaType: " + schemaType + " not present in AppSearch.");
+ return false;
+ }
+ if (finalVersion == null) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_INVALID_ARGUMENT,
+ "Receive a migrator for schema type : "
+ + schemaType
+ + ", but the schema doesn't exist in the request.");
+ }
+ return migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion);
+ }
+
+ /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */
+ @NonNull
+ public static Map<String, Integer> buildVersionMap(
+ @NonNull Collection<AppSearchSchema> schemas) {
+ Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size());
+ for (AppSearchSchema currentSchema : schemas) {
+ currentVersionMap.put(currentSchema.getSchemaType(), currentSchema.getVersion());
+ }
+ 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 27c9ccb..77aa14c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -193,7 +193,7 @@
try {
// TODO(b/173451571): reduce burden of binder thread by enqueue request onto
// a separate thread.
- impl.putDocument(packageName, databaseName, document);
+ impl.putDocument(packageName, databaseName, document, /*logger=*/ null);
resultBuilder.setSuccess(document.getUri(), /*result=*/ null);
} catch (Throwable t) {
resultBuilder.setResult(document.getUri(), throwableToFailedResult(t));
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
index 7c92456..ad94a0a 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -368,7 +368,8 @@
packageAccessibleDocuments.toArray(new GenericDocument[0]));
}
- mAppSearchImpl.putDocument(PACKAGE_NAME, DATABASE_NAME, visibilityDocument.build());
+ mAppSearchImpl.putDocument(
+ PACKAGE_NAME, DATABASE_NAME, visibilityDocument.build(), /*logger=*/ null);
// Update derived data structures.
mNotPlatformSurfaceableMap.put(prefix, schemasNotPlatformSurfaceable);
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 e2c211b..5e8760e 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
@@ -17,6 +17,7 @@
package com.android.server.appsearch.external.localstorage;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
@@ -29,6 +30,7 @@
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Bundle;
+import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -43,6 +45,7 @@
import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SetSchemaResponseToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.TypePropertyPathToProtoConverter;
+import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
import com.google.android.icing.IcingSearchEngine;
import com.google.android.icing.proto.DeleteByQueryResultProto;
@@ -449,23 +452,65 @@
public void putDocument(
@NonNull String packageName,
@NonNull String databaseName,
- @NonNull GenericDocument document)
+ @NonNull GenericDocument document,
+ @Nullable AppSearchLogger logger)
throws AppSearchException {
+ PutDocumentStats.Builder pStatsBuilder = null;
+ if (logger != null) {
+ pStatsBuilder = new PutDocumentStats.Builder(packageName, databaseName);
+ }
+ long totalStartTimeMillis = SystemClock.elapsedRealtime();
+
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
+ // Generate Document Proto
+ long generateDocumentProtoStartTimeMillis = SystemClock.elapsedRealtime();
DocumentProto.Builder documentBuilder =
GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
+ long generateDocumentProtoEndTimeMillis = SystemClock.elapsedRealtime();
+
+ // Rewrite Document Type
+ long rewriteDocumentTypeStartTimeMillis = SystemClock.elapsedRealtime();
String prefix = createPrefix(packageName, databaseName);
addPrefixToDocument(documentBuilder, prefix);
+ long rewriteDocumentTypeEndTimeMillis = SystemClock.elapsedRealtime();
PutResultProto putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
+ // Logging stats
+ if (logger != null) {
+ pStatsBuilder
+ .getGeneralStatsBuilder()
+ .setStatusCode(
+ statusProtoToAppSearchException(putResultProto.getStatus())
+ .getResultCode());
+ pStatsBuilder
+ .setGenerateDocumentProtoLatencyMillis(
+ (int)
+ (generateDocumentProtoEndTimeMillis
+ - generateDocumentProtoStartTimeMillis))
+ .setRewriteDocumentTypesLatencyMillis(
+ (int)
+ (rewriteDocumentTypeEndTimeMillis
+ - rewriteDocumentTypeStartTimeMillis));
+ AppSearchLoggerHelper.copyNativeStats(
+ putResultProto.getPutDocumentStats(), pStatsBuilder);
+ }
+
checkSuccess(putResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
+
+ if (logger != null) {
+ long totalEndTimeMillis = SystemClock.elapsedRealtime();
+ pStatsBuilder
+ .getGeneralStatsBuilder()
+ .setTotalLatencyMillis((int) (totalEndTimeMillis - totalStartTimeMillis));
+ logger.logStats(pStatsBuilder.build());
+ }
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
new file mode 100644
index 0000000..5680670
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.external.localstorage;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
+
+import com.google.android.icing.proto.PutDocumentStatsProto;
+
+/**
+ * Class contains helper functions for logging.
+ *
+ * <p>E.g. we need to have helper functions to copy numbers from IcingLib to stats classes.
+ *
+ * @hide
+ */
+public final class AppSearchLoggerHelper {
+ private AppSearchLoggerHelper() {}
+
+ /**
+ * Copies native stats to builder.
+ *
+ * @param fromNativeStats stats copied from
+ * @param toStatsBuilder stats copied to
+ */
+ static void copyNativeStats(
+ @NonNull PutDocumentStatsProto fromNativeStats,
+ @NonNull PutDocumentStats.Builder toStatsBuilder) {
+ Preconditions.checkNotNull(fromNativeStats);
+ Preconditions.checkNotNull(toStatsBuilder);
+ toStatsBuilder
+ .setNativeLatencyMillis(fromNativeStats.getLatencyMs())
+ .setNativeDocumentStoreLatencyMillis(fromNativeStats.getDocumentStoreLatencyMs())
+ .setNativeIndexLatencyMillis(fromNativeStats.getIndexLatencyMs())
+ .setNativeIndexMergeLatencyMillis(fromNativeStats.getIndexMergeLatencyMs())
+ .setNativeDocumentSizeBytes(fromNativeStats.getDocumentSize())
+ .setNativeNumTokensIndexed(
+ fromNativeStats.getTokenizationStats().getNumTokensIndexed())
+ .setNativeExceededMaxNumTokens(
+ fromNativeStats.getTokenizationStats().getExceededMaxTokenNum());
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
deleted file mode 100644
index a7f1cc4..0000000
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.appsearch.external.localstorage;
-
-import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
-
-import android.annotation.NonNull;
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchMigrationHelper;
-import android.app.appsearch.GenericDocument;
-import android.app.appsearch.SearchResultPage;
-import android.app.appsearch.SearchSpec;
-import android.app.appsearch.SetSchemaResponse;
-import android.app.appsearch.exceptions.AppSearchException;
-import android.os.Bundle;
-import android.os.Parcel;
-
-import com.android.internal.util.Preconditions;
-
-import com.google.protobuf.CodedInputStream;
-import com.google.protobuf.CodedOutputStream;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Map;
-
-/**
- * An implementation of {@link AppSearchMigrationHelper} which query document and save post-migrated
- * documents to locally in the app's storage space.
- */
-class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper {
- private final AppSearchImpl mAppSearchImpl;
- private final String mPackageName;
- private final String mDatabaseName;
- private final File mFile;
- private final Map<String, Integer> mCurrentVersionMap;
- private final Map<String, Integer> mFinalVersionMap;
-
- AppSearchMigrationHelperImpl(
- @NonNull AppSearchImpl appSearchImpl,
- @NonNull Map<String, Integer> currentVersionMap,
- @NonNull Map<String, Integer> finalVersionMap,
- @NonNull String packageName,
- @NonNull String databaseName)
- throws IOException {
- mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
- mCurrentVersionMap = Preconditions.checkNotNull(currentVersionMap);
- mFinalVersionMap = Preconditions.checkNotNull(finalVersionMap);
- mPackageName = Preconditions.checkNotNull(packageName);
- mDatabaseName = Preconditions.checkNotNull(databaseName);
- mFile = File.createTempFile(/*prefix=*/ "appsearch", /*suffix=*/ null);
- }
-
- @Override
- public void queryAndTransform(
- @NonNull String schemaType, @NonNull AppSearchMigrationHelper.Transformer migrator)
- throws Exception {
- Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
- int currentVersion = mCurrentVersionMap.get(schemaType);
- int finalVersion = mFinalVersionMap.get(schemaType);
- try (FileOutputStream outputStream = new FileOutputStream(mFile)) {
- // TODO(b/151178558) change the output stream so that we can use it in platform
- CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
- SearchResultPage searchResultPage =
- mAppSearchImpl.query(
- mPackageName,
- mDatabaseName,
- /*queryExpression=*/ "",
- new SearchSpec.Builder()
- .addFilterSchemas(schemaType)
- .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
- .build());
- while (!searchResultPage.getResults().isEmpty()) {
- for (int i = 0; i < searchResultPage.getResults().size(); i++) {
- GenericDocument newDocument =
- migrator.transform(
- currentVersion,
- finalVersion,
- searchResultPage.getResults().get(i).getDocument());
- Bundle bundle = newDocument.getBundle();
- Parcel parcel = Parcel.obtain();
- parcel.writeBundle(bundle);
- byte[] serializedMessage = parcel.marshall();
- parcel.recycle();
- codedOutputStream.writeByteArrayNoTag(serializedMessage);
- }
- codedOutputStream.flush();
- searchResultPage = mAppSearchImpl.getNextPage(searchResultPage.getNextPageToken());
- outputStream.flush();
- }
- }
- }
-
- /**
- * Reads {@link GenericDocument} from the temperate file and saves them to AppSearch.
- *
- * <p>This method should be only called once.
- *
- * @return the {@link AppSearchBatchResult} for migration documents.
- */
- @NonNull
- public SetSchemaResponse readAndPutDocuments(SetSchemaResponse.Builder responseBuilder)
- throws IOException, AppSearchException {
- Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
- try (InputStream inputStream = new FileInputStream(mFile)) {
- CodedInputStream codedInputStream = CodedInputStream.newInstance(inputStream);
- while (!codedInputStream.isAtEnd()) {
- GenericDocument document = readDocumentFromInputStream(codedInputStream);
- try {
- mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
- } catch (Throwable t) {
- responseBuilder.addMigrationFailure(
- new SetSchemaResponse.MigrationFailure.Builder()
- .setNamespace(document.getNamespace())
- .setSchemaType(document.getSchemaType())
- .setUri(document.getUri())
- .setAppSearchResult(throwableToFailedResult(t))
- .build());
- }
- }
- mAppSearchImpl.persistToDisk();
- return responseBuilder.build();
- } finally {
- mFile.delete();
- }
- }
-
- void deleteTempFile() {
- mFile.delete();
- }
-
- /**
- * Reads {@link GenericDocument} from given {@link CodedInputStream}.
- *
- * @param codedInputStream The codedInputStream to read from
- * @throws IOException on File operation error.
- */
- @NonNull
- private static GenericDocument readDocumentFromInputStream(
- @NonNull CodedInputStream codedInputStream) throws IOException {
- byte[] serializedMessage = codedInputStream.readByteArray();
-
- Parcel parcel = Parcel.obtain();
- parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
- parcel.setDataPosition(0);
- Bundle bundle = parcel.readBundle();
- parcel.recycle();
-
- return new GenericDocument(bundle);
- }
-}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
index a2386ec..d6b9da8 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -102,8 +102,8 @@
public static GenericDocument toGenericDocument(@NonNull DocumentProto proto) {
Preconditions.checkNotNull(proto);
GenericDocument.Builder<?> documentBuilder =
- new GenericDocument.Builder<>(proto.getUri(), proto.getSchema())
- .setNamespace(proto.getNamespace())
+ new GenericDocument.Builder<>(
+ proto.getNamespace(), proto.getUri(), proto.getSchema())
.setScore(proto.getScore())
.setTtlMillis(proto.getTtlMs())
.setCreationTimestampMillis(proto.getCreationTimestampMs());
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 e9852aa..1d8db72 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
@@ -58,14 +58,14 @@
@NonNull List<String> databaseNames) {
Preconditions.checkArgument(
proto.getResultsCount() == packageNames.size(),
- "Size of " + "results does not match the number of package names.");
+ "Size of results does not match the number of package names.");
Bundle bundle = new Bundle();
bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
for (int i = 0; i < proto.getResultsCount(); i++) {
- resultBundles.add(
- toSearchResultBundle(
- proto.getResults(i), packageNames.get(i), databaseNames.get(i)));
+ SearchResult result =
+ toSearchResult(proto.getResults(i), packageNames.get(i), databaseNames.get(i));
+ resultBundles.add(result.getBundle());
}
bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
return new SearchResultPage(bundle);
@@ -80,50 +80,41 @@
* @return A {@link SearchResult} bundle.
*/
@NonNull
- private static Bundle toSearchResultBundle(
+ private static SearchResult toSearchResult(
@NonNull SearchResultProto.ResultProtoOrBuilder proto,
@NonNull String packageName,
@NonNull String databaseName) {
- Bundle bundle = new Bundle();
GenericDocument document =
GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument());
- bundle.putBundle(SearchResult.DOCUMENT_FIELD, document.getBundle());
- bundle.putString(SearchResult.PACKAGE_NAME_FIELD, packageName);
- bundle.putString(SearchResult.DATABASE_NAME_FIELD, databaseName);
-
- ArrayList<Bundle> matchList = new ArrayList<>();
+ SearchResult.Builder builder =
+ new SearchResult.Builder(packageName, databaseName).setGenericDocument(document);
if (proto.hasSnippet()) {
for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) {
SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i);
for (int j = 0; j < entry.getSnippetMatchesCount(); j++) {
- Bundle matchInfoBundle =
- convertToMatchInfoBundle(
- entry.getSnippetMatches(j), entry.getPropertyName());
- matchList.add(matchInfoBundle);
+ SearchResult.MatchInfo matchInfo =
+ toMatchInfo(entry.getSnippetMatches(j), entry.getPropertyName());
+ builder.addMatch(matchInfo);
}
}
}
- bundle.putParcelableArrayList(SearchResult.MATCHES_FIELD, matchList);
-
- return bundle;
+ return builder.build();
}
- private static Bundle convertToMatchInfoBundle(
- SnippetMatchProto snippetMatchProto, String propertyPath) {
- Bundle bundle = new Bundle();
- bundle.putString(SearchResult.MatchInfo.PROPERTY_PATH_FIELD, propertyPath);
- bundle.putInt(
- SearchResult.MatchInfo.EXACT_MATCH_POSITION_LOWER_FIELD,
- snippetMatchProto.getExactMatchPosition());
- bundle.putInt(
- SearchResult.MatchInfo.EXACT_MATCH_POSITION_UPPER_FIELD,
- snippetMatchProto.getExactMatchPosition() + snippetMatchProto.getExactMatchBytes());
- bundle.putInt(
- SearchResult.MatchInfo.WINDOW_POSITION_LOWER_FIELD,
- snippetMatchProto.getWindowPosition());
- bundle.putInt(
- SearchResult.MatchInfo.WINDOW_POSITION_UPPER_FIELD,
- snippetMatchProto.getWindowPosition() + snippetMatchProto.getWindowBytes());
- return bundle;
+ private static SearchResult.MatchInfo toMatchInfo(
+ @NonNull SnippetMatchProto snippetMatchProto, @NonNull String propertyPath) {
+ return new SearchResult.MatchInfo.Builder()
+ .setPropertyPath(propertyPath)
+ .setExactMatchRange(
+ new SearchResult.MatchRange(
+ snippetMatchProto.getExactMatchPosition(),
+ snippetMatchProto.getExactMatchPosition()
+ + snippetMatchProto.getExactMatchBytes()))
+ .setSnippetRange(
+ new SearchResult.MatchRange(
+ snippetMatchProto.getWindowPosition(),
+ snippetMatchProto.getWindowPosition()
+ + snippetMatchProto.getWindowBytes()))
+ .build();
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
index 81a5067..a724f95 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
@@ -76,7 +76,7 @@
CallStats(@NonNull Builder builder) {
Preconditions.checkNotNull(builder);
- mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStats);
+ mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStatsBuilder).build();
mCallType = builder.mCallType;
mEstimatedBinderLatencyMillis = builder.mEstimatedBinderLatencyMillis;
mNumOperationsSucceeded = builder.mNumOperationsSucceeded;
@@ -132,15 +132,23 @@
/** Builder for {@link CallStats}. */
public static class Builder {
- @NonNull final GeneralStats mGeneralStats;
+ @NonNull final GeneralStats.Builder mGeneralStatsBuilder;
@CallType int mCallType;
int mEstimatedBinderLatencyMillis;
int mNumOperationsSucceeded;
int mNumOperationsFailed;
- /** Builder takes {@link GeneralStats} to hold general stats. */
- public Builder(@NonNull GeneralStats generalStats) {
- mGeneralStats = Preconditions.checkNotNull(generalStats);
+ /** Builder takes {@link GeneralStats.Builder}. */
+ public Builder(@NonNull String packageName, @NonNull String database) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(database);
+ mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
+ }
+
+ /** Returns {@link GeneralStats.Builder}. */
+ @NonNull
+ public GeneralStats.Builder getGeneralStatsBuilder() {
+ return mGeneralStatsBuilder;
}
/** Sets type of the call. */
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
index d2a45d5..8ce8eda 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
@@ -82,18 +82,18 @@
public static class Builder {
@NonNull final String mPackageName;
@NonNull final String mDatabase;
- @AppSearchResult.ResultCode int mStatusCode;
+ @AppSearchResult.ResultCode int mStatusCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
int mTotalLatencyMillis;
/**
* Constructor
*
* @param packageName name of the package logging stats
- * @param dataBase name of the database logging stats
+ * @param database name of the database logging stats
*/
- public Builder(@NonNull String packageName, @NonNull String dataBase) {
+ public Builder(@NonNull String packageName, @NonNull String database) {
mPackageName = Preconditions.checkNotNull(packageName);
- mDatabase = Preconditions.checkNotNull(dataBase);
+ mDatabase = Preconditions.checkNotNull(database);
}
/** Sets status code returned from {@link AppSearchResult#getResultCode()} */
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
index b1b643b..c1f6fb1 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
@@ -54,12 +54,14 @@
/** Number of tokens added to the index. */
private final int mNativeNumTokensIndexed;
- /** Number of tokens clipped for exceeding the max number. */
- private final int mNativeNumTokensClipped;
+ /**
+ * Whether the number of tokens to be indexed exceeded the max number of tokens per document.
+ */
+ private final boolean mNativeExceededMaxNumTokens;
PutDocumentStats(@NonNull Builder builder) {
Preconditions.checkNotNull(builder);
- mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStats);
+ mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStatsBuilder).build();
mGenerateDocumentProtoLatencyMillis = builder.mGenerateDocumentProtoLatencyMillis;
mRewriteDocumentTypesLatencyMillis = builder.mRewriteDocumentTypesLatencyMillis;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
@@ -68,7 +70,7 @@
mNativeIndexMergeLatencyMillis = builder.mNativeIndexMergeLatencyMillis;
mNativeDocumentSizeBytes = builder.mNativeDocumentSizeBytes;
mNativeNumTokensIndexed = builder.mNativeNumTokensIndexed;
- mNativeNumTokensClipped = builder.mNativeNumTokensClipped;
+ mNativeExceededMaxNumTokens = builder.mNativeExceededMaxNumTokens;
}
/** Returns the {@link GeneralStats} object attached to this instance. */
@@ -117,14 +119,17 @@
return mNativeNumTokensIndexed;
}
- /** Returns number of tokens clipped for exceeding the max number. */
- public int getNativeNumTokensClipped() {
- return mNativeNumTokensClipped;
+ /**
+ * Returns whether the number of tokens to be indexed exceeded the max number of tokens per
+ * document.
+ */
+ public boolean getNativeExceededMaxNumTokens() {
+ return mNativeExceededMaxNumTokens;
}
/** Builder for {@link PutDocumentStats}. */
public static class Builder {
- @NonNull final GeneralStats mGeneralStats;
+ @NonNull final GeneralStats.Builder mGeneralStatsBuilder;
int mGenerateDocumentProtoLatencyMillis;
int mRewriteDocumentTypesLatencyMillis;
int mNativeLatencyMillis;
@@ -133,11 +138,19 @@
int mNativeIndexMergeLatencyMillis;
int mNativeDocumentSizeBytes;
int mNativeNumTokensIndexed;
- int mNativeNumTokensClipped;
+ boolean mNativeExceededMaxNumTokens;
- /** Builder takes {@link GeneralStats} to hold general stats. */
- public Builder(@NonNull GeneralStats generalStats) {
- mGeneralStats = Preconditions.checkNotNull(generalStats);
+ /** Builder takes {@link GeneralStats.Builder}. */
+ public Builder(@NonNull String packageName, @NonNull String database) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(database);
+ mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
+ }
+
+ /** Returns {@link GeneralStats.Builder}. */
+ @NonNull
+ public GeneralStats.Builder getGeneralStatsBuilder() {
+ return mGeneralStatsBuilder;
}
/** Sets how much time we spend for generating document proto, in milliseconds. */
@@ -200,10 +213,13 @@
return this;
}
- /** Sets number of tokens clipped for exceeding the max number. */
+ /**
+ * Sets whether the number of tokens to be indexed exceeded the max number of tokens per
+ * document.
+ */
@NonNull
- public Builder setNativeNumTokensClipped(int nativeNumTokensClipped) {
- mNativeNumTokensClipped = nativeNumTokensClipped;
+ public Builder setNativeExceededMaxNumTokens(boolean nativeExceededMaxNumTokens) {
+ mNativeExceededMaxNumTokens = nativeExceededMaxNumTokens;
return this;
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
new file mode 100644
index 0000000..aeb66d9
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -0,0 +1,315 @@
+/*
+ * 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.appsearch.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.appsearch.external.localstorage.AppSearchLogger;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
+
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Logger Implementation using Westworld.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @hide
+ */
+public final class PlatformLogger implements AppSearchLogger {
+ private static final String TAG = "AppSearchPlatformLogger";
+
+ // Context of the system service.
+ private final Context mContext;
+
+ // User ID of the caller who we're logging for.
+ private final int mUserId;
+
+ // Configuration for the logger
+ private final Config mConfig;
+
+ private final Random mRng = new Random();
+ private final Object mLock = new Object();
+
+ /**
+ * SparseArray to track how many stats we skipped due to
+ * {@link Config#mMinTimeIntervalBetweenSamplesMillis}.
+ *
+ * <p> We can have correct extrapolated number by adding those counts back when we log
+ * the same type of stats next time. E.g. the true count of an event could be estimated as:
+ * SUM(sampling_ratio * (num_skipped_sample + 1)) as est_count
+ *
+ * <p>The key to the SparseArray is {@link CallStats.CallType}
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mSkippedSampleCountLocked =
+ new SparseIntArray();
+
+ /**
+ * Map to cache the packageUid for each package.
+ *
+ * <p>It maps packageName to packageUid.
+ *
+ * <p>The entry will be removed whenever the app gets uninstalled
+ */
+ @GuardedBy("mLock")
+ private final Map<String, Integer> mPackageUidCacheLocked =
+ new ArrayMap<>();
+
+ /**
+ * Elapsed time for last stats logged from boot in millis
+ */
+ @GuardedBy("mLock")
+ private long mLastPushTimeMillisLocked = 0;
+
+ /**
+ * Class to configure the {@link PlatformLogger}
+ */
+ public static final class Config {
+ // Minimum time interval (in millis) since last message logged to Westworld before
+ // logging again.
+ private final long mMinTimeIntervalBetweenSamplesMillis;
+
+ // Default sampling ratio for all types of stats
+ private final int mDefaultSamplingRatio;
+
+ /**
+ * Sampling ratios for different types of stats
+ *
+ * <p>This SparseArray is passed by client and is READ-ONLY. The key to that SparseArray is
+ * {@link CallStats.CallType}
+ *
+ * <p>If sampling ratio is missing for certain stats type,
+ * {@link Config#mDefaultSamplingRatio} will be used.
+ *
+ * <p>E.g. sampling ratio=10 means that one out of every 10 stats was logged. If sampling
+ * ratio is 1, we will log each sample and it acts as if the sampling is disabled.
+ */
+ @NonNull
+ private final SparseIntArray mSamplingRatios;
+
+ /**
+ * Configuration for {@link PlatformLogger}
+ *
+ * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
+ * required for two consecutive stats logged
+ * @param defaultSamplingRatio default sampling ratio
+ * @param samplingRatios SparseArray to customize sampling ratio for
+ * different stat types
+ */
+ public Config(long minTimeIntervalBetweenSamplesMillis,
+ int defaultSamplingRatio,
+ @Nullable SparseIntArray samplingRatios) {
+ mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
+ mDefaultSamplingRatio = defaultSamplingRatio;
+ if (samplingRatios != null) {
+ mSamplingRatios = samplingRatios;
+ } else {
+ mSamplingRatios = new SparseIntArray();
+ }
+ }
+ }
+
+ /**
+ * Helper class to hold platform specific stats for Westworld.
+ */
+ static final class ExtraStats {
+ // UID for the calling package of the stats.
+ final int mPackageUid;
+ // sampling ratio for the call type of the stats.
+ final int mSamplingRatio;
+ // number of samplings skipped before the current one for the same call type.
+ final int mSkippedSampleCount;
+
+ ExtraStats(int packageUid, int samplingRatio, int skippedSampleCount) {
+ mPackageUid = packageUid;
+ mSamplingRatio = samplingRatio;
+ mSkippedSampleCount = skippedSampleCount;
+ }
+ }
+
+ /**
+ * Westworld constructor
+ */
+ public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) {
+ mContext = Preconditions.checkNotNull(context);
+ mConfig = Preconditions.checkNotNull(config);
+ mUserId = userId;
+ }
+
+ /** Logs {@link CallStats}. */
+ @Override
+ public void logStats(@NonNull CallStats stats) {
+ Preconditions.checkNotNull(stats);
+ synchronized (mLock) {
+ if (shouldLogForTypeLocked(stats.getCallType())) {
+ logToWestworldLocked(stats);
+ }
+ }
+ }
+
+ /** Logs {@link PutDocumentStats}. */
+ @Override
+ public void logStats(@NonNull PutDocumentStats stats) {
+ Preconditions.checkNotNull(stats);
+ synchronized (mLock) {
+ if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
+ logToWestworldLocked(stats);
+ }
+ }
+ }
+
+ /**
+ * Removes cached UID for package.
+ *
+ * @return removed UID for the package, or {@code INVALID_UID} if package was not previously
+ * cached.
+ */
+ public int removeCachedUidForPackage(@NonNull String packageName) {
+ // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
+ Preconditions.checkNotNull(packageName);
+ synchronized (mLock) {
+ Integer uid = mPackageUidCacheLocked.remove(packageName);
+ return uid != null ? uid : Process.INVALID_UID;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void logToWestworldLocked(@NonNull CallStats stats) {
+ mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
+ ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
+ stats.getCallType());
+ /* TODO(b/173532925) Log the CallStats to Westworld
+ stats.log(..., samplingRatio, skippedSampleCount, ...)
+ */
+ }
+
+ @GuardedBy("mLock")
+ private void logToWestworldLocked(@NonNull PutDocumentStats stats) {
+ mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
+ ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+ /* TODO(b/173532925) Log the PutDocumentStats to Westworld
+ stats.log(..., samplingRatio, skippedSampleCount, ...)
+ */
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @NonNull
+ ExtraStats createExtraStatsLocked(@NonNull String packageName,
+ @CallStats.CallType int callType) {
+ int packageUid = getPackageUidAsUserLocked(packageName);
+ int samplingRatio = mConfig.mSamplingRatios.get(callType,
+ mConfig.mDefaultSamplingRatio);
+
+ int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
+ /*valueOfKeyIfNotFound=*/ 0);
+ mSkippedSampleCountLocked.put(callType, 0);
+
+ return new ExtraStats(packageUid, samplingRatio, skippedSampleCount);
+ }
+
+ /**
+ * Checks if this stats should be logged.
+ *
+ * <p>It won't be logged if it is "sampled" out, or it is too close to the previous logged
+ * stats.
+ */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
+ int samplingRatio = mConfig.mSamplingRatios.get(callType,
+ mConfig.mDefaultSamplingRatio);
+
+ // Sampling
+ if (!shouldSample(samplingRatio)) {
+ return false;
+ }
+
+ // Rate limiting
+ // Check the timestamp to see if it is too close to last logged sample
+ long currentTimeMillis = SystemClock.elapsedRealtime();
+ if (mLastPushTimeMillisLocked
+ > currentTimeMillis - mConfig.mMinTimeIntervalBetweenSamplesMillis) {
+ int count = mSkippedSampleCountLocked.get(callType, /*valueOfKeyIfNotFound=*/ 0);
+ ++count;
+ mSkippedSampleCountLocked.put(callType, count);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the stats should be "sampled"
+ *
+ * @param samplingRatio sampling ratio
+ * @return if the stats should be sampled
+ */
+ private boolean shouldSample(int samplingRatio) {
+ if (samplingRatio <= 0) {
+ return false;
+ }
+
+ return mRng.nextInt((int) samplingRatio) == 0;
+ }
+
+ /**
+ * Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to
+ * find the UID.
+ */
+ @GuardedBy("mLock")
+ private int getPackageUidAsUserLocked(@NonNull String packageName) {
+ Integer packageUid = mPackageUidCacheLocked.get(packageName);
+ if (packageUid != null) {
+ return packageUid;
+ }
+
+ // TODO(b/173532925) since VisibilityStore has the same method, we can make this a
+ // utility function
+ try {
+ packageUid = mContext.getPackageManager().getPackageUidAsUser(packageName, mUserId);
+ mPackageUidCacheLocked.put(packageName, packageUid);
+ return packageUid;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package doesn't exist, continue
+ }
+ return Process.INVALID_UID;
+ }
+
+ //
+ // Functions below are used for tests only
+ //
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void setLastPushTimeMillisLocked(long lastPushElapsedTimeMillis) {
+ mLastPushTimeMillisLocked = lastPushElapsedTimeMillis;
+ }
+}
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 41c70f0..0952215 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I42b89416968565ceb6483b400894f5b49524208c
+I723a9d7b5e64329ab25b6d7627f3b2d222c31ac7
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 37717d6..4a3c7a5 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
@@ -47,10 +47,7 @@
AppSearchBatchResult<String, GenericDocument> result =
checkIsBatchResultSuccess(
session.getByUri(
- new GetByUriRequest.Builder()
- .setNamespace(namespace)
- .addUris(uris)
- .build()));
+ new GetByUriRequest.Builder(namespace).addUris(uris).build()));
assertThat(result.getSuccesses()).hasSize(uris.length);
assertThat(result.getFailures()).isEmpty();
List<GenericDocument> list = new ArrayList<>(uris.length);
@@ -80,7 +77,7 @@
List<GenericDocument> documents = new ArrayList<>();
while (results.size() > 0) {
for (SearchResult result : results) {
- documents.add(result.getDocument());
+ documents.add(result.getGenericDocument());
}
results = searchResults.getNextPage().get();
}
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 77146e0..78c5b15 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -539,6 +539,11 @@
* scheduled as exact. Applications are strongly discouraged from using exact
* alarms unnecessarily as they reduce the OS's ability to minimize battery use.
*
+ * <p>
+ * Starting with {@link Build.VERSION_CODES#S}, apps require the
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use this
+ * API.
+ *
* @param type type of alarm.
* @param triggerAtMillis time in milliseconds that the alarm should go
* off, using the appropriate clock (depending on the alarm type).
@@ -558,6 +563,7 @@
* @see #RTC
* @see #RTC_WAKEUP
*/
+ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
null, null);
@@ -571,7 +577,13 @@
* The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
* invoked via the specified target Handler, or on the application's main looper
* if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * <p>
+ * Starting with {@link Build.VERSION_CODES#S}, apps require the
+ * {@link Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM} permission to use this
+ * API.
*/
+ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
public void setExact(@AlarmType int type, long triggerAtMillis, String tag,
OnAlarmListener listener, Handler targetHandler) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 6967d81..4c8ab93 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -59,15 +59,6 @@
* constraint on the JobInfo object that you are creating. Otherwise, the builder would throw an
* exception when building. From Android version {@link Build.VERSION_CODES#Q} and onwards, it is
* valid to schedule jobs with no constraints.
- * <p> Prior to Android version {@link Build.VERSION_CODES#S}, jobs could only have a maximum of 100
- * jobs scheduled at a time. Starting with Android version {@link Build.VERSION_CODES#S}, that limit
- * has been increased to 150. Expedited jobs also count towards the limit.
- * <p> In Android version {@link Build.VERSION_CODES#LOLLIPOP}, jobs had a maximum execution time
- * of one minute. Starting with Android version {@link Build.VERSION_CODES#M} and ending with
- * Android version {@link Build.VERSION_CODES#R}, jobs had a maximum execution time of 10 minutes.
- * Starting from Android version {@link Build.VERSION_CODES#S}, jobs will still be stopped after
- * 10 minutes if the system is busy or needs the resources, but if not, jobs may continue running
- * longer than 10 minutes.
*/
public class JobInfo implements Parcelable {
private static String TAG = "JobInfo";
@@ -1471,7 +1462,7 @@
* <ol>
* <li>Run as soon as possible</li>
* <li>Be less restricted during Doze and battery saver</li>
- * <li>Have network access</li>
+ * <li>Have the same network access as foreground services</li>
* <li>Be less likely to be killed than regular jobs</li>
* <li>Be subject to background location throttling</li>
* </ol>
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 361325d..1f4ef04 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -57,6 +57,19 @@
* {@link android.content.Context#getSystemService
* Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
*
+ * <p> Prior to Android version {@link android.os.Build.VERSION_CODES#S}, jobs could only have
+ * a maximum of 100 jobs scheduled at a time. Starting with Android version
+ * {@link android.os.Build.VERSION_CODES#S}, that limit has been increased to 150.
+ * Expedited jobs also count towards the limit.
+ *
+ * <p> In Android version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, jobs had a maximum
+ * execution time of one minute. Starting with Android version
+ * {@link android.os.Build.VERSION_CODES#M} and ending with Android version
+ * {@link android.os.Build.VERSION_CODES#R}, jobs had a maximum execution time of 10 minutes.
+ * Starting from Android version {@link android.os.Build.VERSION_CODES#S}, jobs will still be
+ * stopped after 10 minutes if the system is busy or needs the resources, but if not, jobs
+ * may continue running longer than 10 minutes.
+ *
* <p class="caution"><strong>Note:</strong> Beginning with API 30
* ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications.
* Calling {@link #schedule(JobInfo)} and other such methods with very high frequency can have a
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 61afada..0f3d299 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -74,6 +74,7 @@
/**
* Call this to inform the JobScheduler that the job has finished its work. When the
* system receives this message, it releases the wakelock being held for the job.
+ * This does not need to be called if {@link #onStopJob(JobParameters)} has been called.
* <p>
* You can request that the job be scheduled again by passing {@code true} as
* the <code>wantsReschedule</code> parameter. This will apply back-off policy
@@ -135,6 +136,8 @@
/**
* This method is called if the system has determined that you must stop execution of your job
* even before you've had a chance to call {@link #jobFinished(JobParameters, boolean)}.
+ * 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
* example you may have requested WiFi with
@@ -144,8 +147,8 @@
* 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.
* <p>
- * Once this method returns, the system releases the wakelock that it is holding on
- * behalf of the job.</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.
diff --git a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
index 752c36e..6cdf585 100644
--- a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
+++ b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
@@ -65,7 +65,7 @@
* @return package names the system has white-listed to opt out of power save restrictions,
* except for device idle mode.
*
- * @hide Should be migrated to PowerWhitelistManager
+ * @hide Should be migrated to PowerExemptionManager
*/
@TestApi
public @NonNull String[] getSystemPowerWhitelistExceptIdle() {
@@ -80,7 +80,7 @@
* @return package names the system has white-listed to opt out of power save restrictions for
* all modes.
*
- * @hide Should be migrated to PowerWhitelistManager
+ * @hide Should be migrated to PowerExemptionManager
*/
@TestApi
public @NonNull String[] getSystemPowerWhitelist() {
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index 43d4873..9d18dfe 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -42,7 +42,7 @@
boolean isPowerSaveWhitelistExceptIdleApp(String name);
boolean isPowerSaveWhitelistApp(String name);
@UnsupportedAppUsage(maxTargetSdk = 30,
- publicAlternatives = "Use SystemApi {@code PowerWhitelistManager#whitelistAppTemporarily(String, int, String)}.")
+ publicAlternatives = "Use SystemApi {@code PowerExemptionManager#addToTemporaryAllowList(String, int, int, String)}.")
void addPowerSaveTempWhitelistApp(String name, long duration, int userId, int reasonCode, String reason);
long addPowerSaveTempWhitelistAppForMms(String name, int userId, int reasonCode, String reason);
long addPowerSaveTempWhitelistAppForSms(String name, int userId, int reasonCode, String reason);
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 8445335..88f21a5 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -29,6 +29,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.content.Context;
import java.lang.annotation.Retention;
@@ -170,9 +171,11 @@
/** @hide */
public static final int REASON_EXEMPTED_PACKAGE = 64;
/** @hide */
- public static final int REASON_ALLOWLISTED_PACKAGE = 65;
+ public static final int REASON_ALLOWLISTED_PACKAGE = 65;
/** @hide */
public static final int REASON_APPOP = 66;
+ /** @hide */
+ public static final int REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD = 67;
/* BG-FGS-launch is allowed by temp-allow-list or system-allow-list.
Reason code for temp and system allow list starts here.
@@ -193,6 +196,10 @@
* Set temp-allow-list for activity recognition.
*/
public static final int REASON_ACTIVITY_RECOGNITION = 103;
+ /**
+ * Set temp-allow-list for transferring accounts between users.
+ */
+ public static final int REASON_ACCOUNT_TRANSFER = 104;
/* Reason code range 200-299 are reserved for broadcast actions */
/**
@@ -216,7 +223,7 @@
* Device idle system allow list, including EXCEPT-IDLE
* @hide
*/
- public static final int REASON_SYSTEM_ALLOW_LISTED = 300;
+ public static final int REASON_SYSTEM_ALLOW_LISTED = 300;
/** @hide */
public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301;
/**
@@ -291,6 +298,11 @@
* @hide
*/
public static final int REASON_SHELL = 316;
+ /**
+ * Media session callbacks.
+ * @hide
+ */
+ public static final int REASON_MEDIA_SESSION_CALLBACK = 317;
/**
* The list of BG-FGS-Launch and temp-allow-list reason code.
@@ -324,11 +336,13 @@
REASON_EXEMPTED_PACKAGE,
REASON_ALLOWLISTED_PACKAGE,
REASON_APPOP,
+ REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD,
// temp and system allow list reasons.
REASON_GEOFENCING,
REASON_PUSH_MESSAGING,
REASON_PUSH_MESSAGING_OVER_QUOTA,
REASON_ACTIVITY_RECOGNITION,
+ REASON_ACCOUNT_TRANSFER,
REASON_BOOT_COMPLETED,
REASON_PRE_BOOT_COMPLETED,
REASON_LOCKED_BOOT_COMPLETED,
@@ -349,6 +363,7 @@
REASON_EVENT_SMS,
REASON_EVENT_MMS,
REASON_SHELL,
+ REASON_MEDIA_SESSION_CALLBACK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReasonCode {}
@@ -446,6 +461,7 @@
* @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.
*/
+ @UserHandleAware
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public void addToTemporaryAllowList(@NonNull String packageName, long durationMs,
@ReasonCode int reasonCode, @Nullable String reason) {
@@ -469,6 +485,7 @@
* used for logging purposes. Could be null or empty string.
* @return The duration (in milliseconds) that the app is allow-listed for
*/
+ @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) {
@@ -571,6 +588,8 @@
return "ALLOWLISTED_PACKAGE";
case REASON_APPOP:
return "APPOP";
+ case REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD:
+ return "ACTIVITY_VISIBILITY_GRACE_PERIOD";
case REASON_GEOFENCING:
return "GEOFENCING";
case REASON_PUSH_MESSAGING:
@@ -579,6 +598,8 @@
return "PUSH_MESSAGING_OVER_QUOTA";
case REASON_ACTIVITY_RECOGNITION:
return "ACTIVITY_RECOGNITION";
+ case REASON_ACCOUNT_TRANSFER:
+ return "REASON_ACCOUNT_TRANSFER";
case REASON_BOOT_COMPLETED:
return "BOOT_COMPLETED";
case REASON_PRE_BOOT_COMPLETED:
@@ -619,6 +640,8 @@
return "EVENT_MMS";
case REASON_SHELL:
return "SHELL";
+ case REASON_MEDIA_SESSION_CALLBACK:
+ return "MEDIA_SESSION_CALLBACK";
default:
return "(unknown:" + reasonCode + ")";
}
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index b1b733a..eba39c7 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -16,13 +16,6 @@
package android.os;
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
-import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
-import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
-import static android.app.ActivityManager.PROCESS_STATE_TOP;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,7 +26,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
import java.util.List;
/**
@@ -43,9 +35,11 @@
* placed on the temporary whitelist are removed from that whitelist after a predetermined amount of
* time.
*
+ * @deprecated Use {@link PowerExemptionManager} instead
* @hide
*/
@SystemApi
+@Deprecated
@SystemService(Context.POWER_WHITELIST_MANAGER)
public class PowerWhitelistManager {
private final Context mContext;
@@ -53,21 +47,23 @@
// TODO: migrate to PowerWhitelistController
private final IDeviceIdleController mService;
+ private final PowerExemptionManager mPowerExemptionManager;
+
/**
* Indicates that an unforeseen event has occurred and the app should be whitelisted to handle
* it.
*/
- public static final int EVENT_UNSPECIFIED = 0;
+ public static final int EVENT_UNSPECIFIED = PowerExemptionManager.EVENT_UNSPECIFIED;
/**
* Indicates that an SMS event has occurred and the app should be whitelisted to handle it.
*/
- public static final int EVENT_SMS = 1;
+ public static final int EVENT_SMS = PowerExemptionManager.EVENT_SMS;
/**
* Indicates that an MMS event has occurred and the app should be whitelisted to handle it.
*/
- public static final int EVENT_MMS = 2;
+ public static final int EVENT_MMS = PowerExemptionManager.EVENT_MMS;
/**
* @hide
@@ -84,12 +80,14 @@
/**
* Allow the temp allowlist behavior, plus allow foreground service start from background.
*/
- public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0;
+ public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED =
+ PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
/**
* Only allow the temp allowlist behavior, not allow foreground service start from
* background.
*/
- public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1;
+ public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED =
+ PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
/**
* The list of temp allowlist types.
@@ -107,73 +105,83 @@
* BG-FGS-launch is denied.
* @hide
*/
- public static final int REASON_DENIED = -1;
+ public static final int REASON_DENIED = PowerExemptionManager.REASON_DENIED;
/* Reason code range 0-9 are reserved for default reasons */
/**
* The default reason code if reason is unknown.
*/
- public static final int REASON_UNKNOWN = 0;
+ public static final int REASON_UNKNOWN = PowerExemptionManager.REASON_UNKNOWN;
/**
* Use REASON_OTHER if there is no better choice.
*/
- public static final int REASON_OTHER = 1;
+ public static final int REASON_OTHER = PowerExemptionManager.REASON_OTHER;
/* Reason code range 10-49 are reserved for BG-FGS-launch allowed proc states */
/** @hide */
- public static final int REASON_PROC_STATE_PERSISTENT = 10;
+ public static final int REASON_PROC_STATE_PERSISTENT =
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
/** @hide */
- public static final int REASON_PROC_STATE_PERSISTENT_UI = 11;
+ public static final int REASON_PROC_STATE_PERSISTENT_UI =
+ PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
/** @hide */
- public static final int REASON_PROC_STATE_TOP = 12;
+ public static final int REASON_PROC_STATE_TOP = PowerExemptionManager.REASON_PROC_STATE_TOP;
/** @hide */
- public static final int REASON_PROC_STATE_BTOP = 13;
+ public static final int REASON_PROC_STATE_BTOP = PowerExemptionManager.REASON_PROC_STATE_BTOP;
/** @hide */
- public static final int REASON_PROC_STATE_FGS = 14;
+ public static final int REASON_PROC_STATE_FGS = PowerExemptionManager.REASON_PROC_STATE_FGS;
/** @hide */
- public static final int REASON_PROC_STATE_BFGS = 15;
+ public static final int REASON_PROC_STATE_BFGS = PowerExemptionManager.REASON_PROC_STATE_BFGS;
/* Reason code range 50-99 are reserved for BG-FGS-launch allowed reasons */
/** @hide */
- public static final int REASON_UID_VISIBLE = 50;
+ public static final int REASON_UID_VISIBLE = PowerExemptionManager.REASON_UID_VISIBLE;
/** @hide */
- public static final int REASON_SYSTEM_UID = 51;
+ public static final int REASON_SYSTEM_UID = PowerExemptionManager.REASON_SYSTEM_UID;
/** @hide */
- public static final int REASON_ACTIVITY_STARTER = 52;
+ public static final int REASON_ACTIVITY_STARTER = PowerExemptionManager.REASON_ACTIVITY_STARTER;
/** @hide */
- public static final int REASON_START_ACTIVITY_FLAG = 53;
+ public static final int REASON_START_ACTIVITY_FLAG =
+ PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
/** @hide */
- public static final int REASON_FGS_BINDING = 54;
+ public static final int REASON_FGS_BINDING = PowerExemptionManager.REASON_FGS_BINDING;
/** @hide */
- public static final int REASON_DEVICE_OWNER = 55;
+ public static final int REASON_DEVICE_OWNER = PowerExemptionManager.REASON_DEVICE_OWNER;
/** @hide */
- public static final int REASON_PROFILE_OWNER = 56;
+ public static final int REASON_PROFILE_OWNER = PowerExemptionManager.REASON_PROFILE_OWNER;
/** @hide */
- public static final int REASON_COMPANION_DEVICE_MANAGER = 57;
+ public static final int REASON_COMPANION_DEVICE_MANAGER =
+ PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
/**
* START_ACTIVITIES_FROM_BACKGROUND permission.
* @hide
*/
- public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION = 58;
+ public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION =
+ PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
/**
* START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
* @hide
*/
- public static final int REASON_BACKGROUND_FGS_PERMISSION = 59;
+ public static final int REASON_BACKGROUND_FGS_PERMISSION =
+ PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
/** @hide */
- public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60;
+ public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION =
+ PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
/** @hide */
- public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61;
+ public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION =
+ PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION;
/** @hide */
- public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62;
+ public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION =
+ PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
/** @hide */
- public static final int REASON_DEVICE_DEMO_MODE = 63;
+ public static final int REASON_DEVICE_DEMO_MODE = PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
/** @hide */
- public static final int REASON_EXEMPTED_PACKAGE = 64;
+ public static final int REASON_EXEMPTED_PACKAGE = PowerExemptionManager.REASON_EXEMPTED_PACKAGE;
/** @hide */
- public static final int REASON_ALLOWLISTED_PACKAGE = 65;
+ public static final int REASON_ALLOWLISTED_PACKAGE =
+ PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
/** @hide */
- public static final int REASON_APPOP = 66;
+ public static final int REASON_APPOP = PowerExemptionManager.REASON_APPOP;
/* BG-FGS-launch is allowed by temp-allowlist or system-allowlist.
Reason code for temp and system allowlist starts here.
@@ -181,117 +189,128 @@
/**
* Set temp-allowlist for location geofence purpose.
*/
- public static final int REASON_GEOFENCING = 100;
+ public static final int REASON_GEOFENCING = PowerExemptionManager.REASON_GEOFENCING;
/**
* Set temp-allowlist for server push messaging.
*/
- public static final int REASON_PUSH_MESSAGING = 101;
+ public static final int REASON_PUSH_MESSAGING = PowerExemptionManager.REASON_PUSH_MESSAGING;
/**
* Set temp-allowlist for server push messaging over the quota.
*/
- public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102;
+ public static final int REASON_PUSH_MESSAGING_OVER_QUOTA =
+ PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
/**
* Set temp-allowlist for activity recognition.
*/
- public static final int REASON_ACTIVITY_RECOGNITION = 103;
+ public static final int REASON_ACTIVITY_RECOGNITION =
+ PowerExemptionManager.REASON_ACTIVITY_RECOGNITION;
/* Reason code range 200-299 are reserved for broadcast actions */
/**
* Broadcast ACTION_BOOT_COMPLETED.
* @hide
*/
- public static final int REASON_BOOT_COMPLETED = 200;
+ public static final int REASON_BOOT_COMPLETED = PowerExemptionManager.REASON_BOOT_COMPLETED;
/**
* Broadcast ACTION_PRE_BOOT_COMPLETED.
* @hide
*/
- public static final int REASON_PRE_BOOT_COMPLETED = 201;
+ public static final int REASON_PRE_BOOT_COMPLETED =
+ PowerExemptionManager.REASON_PRE_BOOT_COMPLETED;
/**
* Broadcast ACTION_LOCKED_BOOT_COMPLETED.
* @hide
*/
- public static final int REASON_LOCKED_BOOT_COMPLETED = 202;
+ public static final int REASON_LOCKED_BOOT_COMPLETED =
+ PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
/* Reason code range 300-399 are reserved for other internal reasons */
/**
* Device idle system allowlist, including EXCEPT-IDLE
* @hide
*/
- public static final int REASON_SYSTEM_ALLOW_LISTED = 300;
+ public static final int REASON_SYSTEM_ALLOW_LISTED =
+ PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
/** @hide */
- public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 301;
+ public static final int REASON_ALARM_MANAGER_ALARM_CLOCK =
+ PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK;
/**
* AlarmManagerService.
* @hide
*/
- public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 302;
+ public static final int REASON_ALARM_MANAGER_WHILE_IDLE =
+ PowerExemptionManager.REASON_ALARM_MANAGER_WHILE_IDLE;
/**
* ActiveServices.
* @hide
*/
- public static final int REASON_SERVICE_LAUNCH = 303;
+ public static final int REASON_SERVICE_LAUNCH = PowerExemptionManager.REASON_SERVICE_LAUNCH;
/**
* KeyChainSystemService.
* @hide
*/
- public static final int REASON_KEY_CHAIN = 304;
+ public static final int REASON_KEY_CHAIN = PowerExemptionManager.REASON_KEY_CHAIN;
/**
* PackageManagerService.
* @hide
*/
- public static final int REASON_PACKAGE_VERIFIER = 305;
+ public static final int REASON_PACKAGE_VERIFIER = PowerExemptionManager.REASON_PACKAGE_VERIFIER;
/**
* SyncManager.
* @hide
*/
- public static final int REASON_SYNC_MANAGER = 306;
+ public static final int REASON_SYNC_MANAGER = PowerExemptionManager.REASON_SYNC_MANAGER;
/**
* DomainVerificationProxyV1.
* @hide
*/
- public static final int REASON_DOMAIN_VERIFICATION_V1 = 307;
+ public static final int REASON_DOMAIN_VERIFICATION_V1 =
+ PowerExemptionManager.REASON_DOMAIN_VERIFICATION_V1;
/**
* DomainVerificationProxyV2.
* @hide
*/
- public static final int REASON_DOMAIN_VERIFICATION_V2 = 308;
+ public static final int REASON_DOMAIN_VERIFICATION_V2 =
+ PowerExemptionManager.REASON_DOMAIN_VERIFICATION_V2;
/** @hide */
public static final int REASON_VPN = 309;
/**
* NotificationManagerService.
* @hide
*/
- public static final int REASON_NOTIFICATION_SERVICE = 310;
+ public static final int REASON_NOTIFICATION_SERVICE =
+ PowerExemptionManager.REASON_NOTIFICATION_SERVICE;
/**
* Broadcast ACTION_MY_PACKAGE_REPLACED.
* @hide
*/
- public static final int REASON_PACKAGE_REPLACED = 311;
+ public static final int REASON_PACKAGE_REPLACED = PowerExemptionManager.REASON_PACKAGE_REPLACED;
/**
* LocationProviderManager.
* @hide
*/
- public static final int REASON_LOCATION_PROVIDER = 312;
+ public static final int REASON_LOCATION_PROVIDER =
+ PowerExemptionManager.REASON_LOCATION_PROVIDER;
/**
* MediaButtonReceiver.
* @hide
*/
- public static final int REASON_MEDIA_BUTTON = 313;
+ public static final int REASON_MEDIA_BUTTON = PowerExemptionManager.REASON_MEDIA_BUTTON;
/**
* InboundSmsHandler.
* @hide
*/
- public static final int REASON_EVENT_SMS = 314;
+ public static final int REASON_EVENT_SMS = PowerExemptionManager.REASON_EVENT_SMS;
/**
* InboundSmsHandler.
* @hide
*/
- public static final int REASON_EVENT_MMS = 315;
+ public static final int REASON_EVENT_MMS = PowerExemptionManager.REASON_EVENT_MMS;
/**
* Shell app.
* @hide
*/
- public static final int REASON_SHELL = 316;
+ public static final int REASON_SHELL = PowerExemptionManager.REASON_SHELL;
/**
* The list of BG-FGS-Launch and temp-allowlist reason code.
@@ -360,26 +379,29 @@
public PowerWhitelistManager(@NonNull Context context) {
mContext = context;
mService = context.getSystemService(DeviceIdleManager.class).getService();
+ mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
}
/**
* Add the specified package to the permanent power save whitelist.
+ *
+ * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(String)} instead
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void addToWhitelist(@NonNull String packageName) {
- addToWhitelist(Collections.singletonList(packageName));
+ mPowerExemptionManager.addToPermanentAllowList(packageName);
}
/**
* Add the specified packages to the permanent power save whitelist.
+ *
+ * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(List)} instead
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void addToWhitelist(@NonNull List<String> packageNames) {
- try {
- mService.addPowerSaveWhitelistApps(packageNames);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mPowerExemptionManager.addToPermanentAllowList(packageNames);
}
/**
@@ -388,19 +410,13 @@
*
* @param includingIdle Set to true if the app should be whitelisted from device idle as well
* as other power save restrictions
+ * @deprecated Use {@link PowerExemptionManager#getAllowListedAppIds(boolean)} instead
* @hide
*/
+ @Deprecated
@NonNull
public int[] getWhitelistedAppIds(boolean includingIdle) {
- try {
- if (includingIdle) {
- return mService.getAppIdWhitelist();
- } else {
- return mService.getAppIdWhitelistExceptIdle();
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mPowerExemptionManager.getAllowListedAppIds(includingIdle);
}
/**
@@ -409,18 +425,12 @@
*
* @param includingIdle Set to true if the app should be whitelisted from device
* idle as well as other power save restrictions
+ * @deprecated Use {@link PowerExemptionManager#isAllowListed(String, boolean)} instead
* @hide
*/
+ @Deprecated
public boolean isWhitelisted(@NonNull String packageName, boolean includingIdle) {
- try {
- if (includingIdle) {
- return mService.isPowerSaveWhitelistApp(packageName);
- } else {
- return mService.isPowerSaveWhitelistExceptIdleApp(packageName);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mPowerExemptionManager.isAllowListed(packageName, includingIdle);
}
/**
@@ -429,14 +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
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void removeFromWhitelist(@NonNull String packageName) {
- try {
- mService.removePowerSaveWhitelistApp(packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mPowerExemptionManager.removeFromAllowList(packageName);
}
/**
@@ -446,16 +454,14 @@
* @param durationMs How long to keep the app on the temp whitelist for (in milliseconds)
* @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
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public void whitelistAppTemporarily(@NonNull String packageName, long durationMs,
@ReasonCode int reasonCode, @Nullable String reason) {
- try {
- mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(),
- reasonCode, reason);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mPowerExemptionManager.addToTemporaryAllowList(packageName, durationMs, reasonCode, reason);
}
/**
@@ -463,12 +469,14 @@
*
* @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 #whitelistAppTemporarily(String, long, int, String)} instead
+ * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowList(
+ * String, long, int, String)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) {
- whitelistAppTemporarily(packageName, durationMs, REASON_UNKNOWN, packageName);
+ mPowerExemptionManager.addToTemporaryAllowList(
+ packageName, durationMs, REASON_UNKNOWN, packageName);
}
/**
@@ -481,13 +489,15 @@
* @param reason A human-readable reason explaining why the app is temp whitelisted. Only
* used for logging purposes. Could be null or empty string.
* @return The duration (in milliseconds) that the app is whitelisted for
- * @deprecated Use {@link #whitelistAppTemporarilyForEvent(String, int, int, String)} instead
+ * @deprecated Use {@link PowerExemptionManager#addToTemporaryAllowListForEvent(
+ * String, int, int, String)} instead
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST)
public long whitelistAppTemporarilyForEvent(@NonNull String packageName,
@WhitelistEvent int event, @Nullable String reason) {
- return whitelistAppTemporarilyForEvent(packageName, event, REASON_UNKNOWN, reason);
+ return mPowerExemptionManager.addToTemporaryAllowListForEvent(
+ packageName, event, REASON_UNKNOWN, reason);
}
/**
@@ -501,47 +511,25 @@
* @param reason A human-readable reason explaining why the app is temp whitelisted. Only
* 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
*/
+ @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) {
- try {
- switch (event) {
- case EVENT_MMS:
- return mService.addPowerSaveTempWhitelistAppForMms(
- packageName, mContext.getUserId(), reasonCode, reason);
- case EVENT_SMS:
- return mService.addPowerSaveTempWhitelistAppForSms(
- packageName, mContext.getUserId(), reasonCode, reason);
- case EVENT_UNSPECIFIED:
- default:
- return mService.whitelistAppTemporarily(
- packageName, mContext.getUserId(), reasonCode, reason);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mPowerExemptionManager.addToTemporaryAllowListForEvent(
+ packageName, event, reasonCode, reason);
}
/**
* @hide
+ *
+ * @deprecated Use {@link PowerExemptionManager#getReasonCodeFromProcState(int)} instead
*/
+ @Deprecated
public static @ReasonCode int getReasonCodeFromProcState(int procState) {
- if (procState <= PROCESS_STATE_PERSISTENT) {
- return REASON_PROC_STATE_PERSISTENT;
- } else if (procState <= PROCESS_STATE_PERSISTENT_UI) {
- return REASON_PROC_STATE_PERSISTENT_UI;
- } else if (procState <= PROCESS_STATE_TOP) {
- return REASON_PROC_STATE_TOP;
- } else if (procState <= PROCESS_STATE_BOUND_TOP) {
- return REASON_PROC_STATE_BTOP;
- } else if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) {
- return REASON_PROC_STATE_FGS;
- } else if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
- return REASON_PROC_STATE_BFGS;
- } else {
- return REASON_DENIED;
- }
+ return PowerExemptionManager.getReasonCodeFromProcState(procState);
}
/**
@@ -549,111 +537,10 @@
* @hide
* @param reasonCode
* @return string name of the reason code.
+ * @deprecated Use {@link PowerExemptionManager#reasonCodeToString(int)} instead
*/
+ @Deprecated
public static String reasonCodeToString(@ReasonCode int reasonCode) {
- switch (reasonCode) {
- case REASON_DENIED:
- return "DENIED";
- case REASON_UNKNOWN:
- return "UNKNOWN";
- case REASON_OTHER:
- return "OTHER";
- case REASON_PROC_STATE_PERSISTENT:
- return "PROC_STATE_PERSISTENT";
- case REASON_PROC_STATE_PERSISTENT_UI:
- return "PROC_STATE_PERSISTENT_UI";
- case REASON_PROC_STATE_TOP:
- return "PROC_STATE_TOP";
- case REASON_PROC_STATE_BTOP:
- return "PROC_STATE_BTOP";
- case REASON_PROC_STATE_FGS:
- return "PROC_STATE_FGS";
- case REASON_PROC_STATE_BFGS:
- return "PROC_STATE_BFGS";
- case REASON_UID_VISIBLE:
- return "UID_VISIBLE";
- case REASON_SYSTEM_UID:
- return "SYSTEM_UID";
- case REASON_ACTIVITY_STARTER:
- return "ACTIVITY_STARTER";
- case REASON_START_ACTIVITY_FLAG:
- return "START_ACTIVITY_FLAG";
- case REASON_FGS_BINDING:
- return "FGS_BINDING";
- case REASON_DEVICE_OWNER:
- return "DEVICE_OWNER";
- case REASON_PROFILE_OWNER:
- return "PROFILE_OWNER";
- case REASON_COMPANION_DEVICE_MANAGER:
- return "COMPANION_DEVICE_MANAGER";
- case REASON_BACKGROUND_ACTIVITY_PERMISSION:
- return "BACKGROUND_ACTIVITY_PERMISSION";
- case REASON_BACKGROUND_FGS_PERMISSION:
- return "BACKGROUND_FGS_PERMISSION";
- case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION:
- return "INSTR_BACKGROUND_ACTIVITY_PERMISSION";
- case REASON_INSTR_BACKGROUND_FGS_PERMISSION:
- return "INSTR_BACKGROUND_FGS_PERMISSION";
- case REASON_SYSTEM_ALERT_WINDOW_PERMISSION:
- return "SYSTEM_ALERT_WINDOW_PERMISSION";
- case REASON_DEVICE_DEMO_MODE:
- return "DEVICE_DEMO_MODE";
- case REASON_EXEMPTED_PACKAGE:
- return "EXEMPTED_PACKAGE";
- case REASON_ALLOWLISTED_PACKAGE:
- return "ALLOWLISTED_PACKAGE";
- case REASON_APPOP:
- return "APPOP";
- case REASON_GEOFENCING:
- return "GEOFENCING";
- case REASON_PUSH_MESSAGING:
- return "PUSH_MESSAGING";
- case REASON_PUSH_MESSAGING_OVER_QUOTA:
- return "PUSH_MESSAGING_OVER_QUOTA";
- case REASON_ACTIVITY_RECOGNITION:
- return "ACTIVITY_RECOGNITION";
- case REASON_BOOT_COMPLETED:
- return "BOOT_COMPLETED";
- case REASON_PRE_BOOT_COMPLETED:
- return "PRE_BOOT_COMPLETED";
- case REASON_LOCKED_BOOT_COMPLETED:
- return "LOCKED_BOOT_COMPLETED";
- case REASON_SYSTEM_ALLOW_LISTED:
- return "SYSTEM_ALLOW_LISTED";
- case REASON_ALARM_MANAGER_ALARM_CLOCK:
- return "ALARM_MANAGER_ALARM_CLOCK";
- case REASON_ALARM_MANAGER_WHILE_IDLE:
- return "ALARM_MANAGER_WHILE_IDLE";
- case REASON_SERVICE_LAUNCH:
- return "SERVICE_LAUNCH";
- case REASON_KEY_CHAIN:
- return "KEY_CHAIN";
- case REASON_PACKAGE_VERIFIER:
- return "PACKAGE_VERIFIER";
- case REASON_SYNC_MANAGER:
- return "SYNC_MANAGER";
- case REASON_DOMAIN_VERIFICATION_V1:
- return "DOMAIN_VERIFICATION_V1";
- case REASON_DOMAIN_VERIFICATION_V2:
- return "DOMAIN_VERIFICATION_V2";
- case REASON_VPN:
- return "VPN";
- case REASON_NOTIFICATION_SERVICE:
- return "NOTIFICATION_SERVICE";
- case REASON_PACKAGE_REPLACED:
- return "PACKAGE_REPLACED";
- case REASON_LOCATION_PROVIDER:
- return "LOCATION_PROVIDER";
- case REASON_MEDIA_BUTTON:
- return "MEDIA_BUTTON";
- case REASON_EVENT_SMS:
- return "EVENT_SMS";
- case REASON_EVENT_MMS:
- return "EVENT_MMS";
- case REASON_SHELL:
- return "SHELL";
- default:
- return "(unknown:" + reasonCode + ")";
- }
+ return PowerExemptionManager.reasonCodeToString(reasonCode);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 119dcb6..667fc60 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2692,7 +2692,7 @@
void addPowerSaveTempAllowlistAppChecked(String packageName, long duration,
int userId, @ReasonCode int reasonCode, @Nullable String reason)
throws RemoteException {
- getContext().enforceCallingPermission(
+ getContext().enforceCallingOrSelfPermission(
Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
"No permission to change device idle whitelist");
final int callingUid = Binder.getCallingUid();
@@ -2715,7 +2715,7 @@
void removePowerSaveTempAllowlistAppChecked(String packageName, int userId)
throws RemoteException {
- getContext().enforceCallingPermission(
+ getContext().enforceCallingOrSelfPermission(
Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
"No permission to change device idle whitelist");
final int callingUid = Binder.getCallingUid();
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 33f6e06..58fc874 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -388,6 +388,8 @@
@VisibleForTesting
static final String KEY_MAX_INTERVAL = "max_interval";
@VisibleForTesting
+ static final String KEY_MIN_WINDOW = "min_window";
+ @VisibleForTesting
static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
= "allow_while_idle_whitelist_duration";
@VisibleForTesting
@@ -428,11 +430,13 @@
@VisibleForTesting
static final String KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW = "allow_while_idle_compat_window";
- private static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps";
+ @VisibleForTesting
+ static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
private static final long DEFAULT_MAX_INTERVAL = 365 * INTERVAL_DAY;
+ private static final long DEFAULT_MIN_WINDOW = 10_000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000;
private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
private static final int DEFAULT_MAX_ALARMS_PER_UID = 500;
@@ -475,6 +479,9 @@
// Maximum alarm recurrence interval
public long MAX_INTERVAL = DEFAULT_MAX_INTERVAL;
+ // Minimum window size for inexact alarms
+ public long MIN_WINDOW = DEFAULT_MIN_WINDOW;
+
// BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
= DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -575,6 +582,9 @@
ALLOW_WHILE_IDLE_QUOTA = 1;
}
break;
+ case KEY_MIN_WINDOW:
+ MIN_WINDOW = properties.getLong(KEY_MIN_WINDOW, DEFAULT_MIN_WINDOW);
+ break;
case KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA:
ALLOW_WHILE_IDLE_COMPAT_QUOTA = properties.getInt(
KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA,
@@ -738,6 +748,11 @@
TimeUtils.formatDuration(MAX_INTERVAL, pw);
pw.println();
+ pw.print(KEY_MIN_WINDOW);
+ pw.print("=");
+ TimeUtils.formatDuration(MIN_WINDOW, pw);
+ pw.println();
+
pw.print(KEY_LISTENER_TIMEOUT);
pw.print("=");
TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
@@ -1642,6 +1657,7 @@
// Fix this window in place, so that as time approaches we don't collapse it.
windowLength = maxElapsed - triggerElapsed;
} else {
+ windowLength = Math.max(windowLength, mConstants.MIN_WINDOW);
maxElapsed = triggerElapsed + windowLength;
}
synchronized (mLock) {
@@ -1981,8 +1997,10 @@
* Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact,
* allow-while-idle alarms.
*/
- boolean isExemptFromPermission(int uid) {
- return (UserHandle.isSameApp(mSystemUiUid, uid) || mLocalDeviceIdleController == null
+ boolean isExemptFromExactAlarmPermission(int uid) {
+ return (UserHandle.isSameApp(mSystemUiUid, uid)
+ || UserHandle.isCore(uid)
+ || mLocalDeviceIdleController == null
|| mLocalDeviceIdleController.isAppOnWhitelist(UserHandle.getAppId(uid)));
}
@@ -2002,54 +2020,43 @@
mAppOps.checkPackage(callingUid, callingPackage);
final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
+ final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT);
+ // make sure the caller is allowed to use the requested kind of alarm, and also
+ // decide what quota and broadcast options to use.
Bundle idleOptions = null;
- if (alarmClock != null || allowWhileIdle) {
- // make sure the caller is allowed to use the requested kind of alarm, and also
- // decide what broadcast options to use.
+ if (exact || allowWhileIdle) {
final boolean needsPermission;
- boolean lowQuota;
+ boolean lowerQuota;
if (CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
- if (windowLength != AlarmManager.WINDOW_EXACT) {
- needsPermission = false;
- lowQuota = true;
- idleOptions = isExemptFromPermission(callingUid) ? mOptsWithFgs.toBundle()
- : mOptsWithoutFgs.toBundle();
- } else if (alarmClock != null) {
- needsPermission = true;
- lowQuota = false;
- idleOptions = mOptsWithFgs.toBundle();
- } else {
- needsPermission = true;
- lowQuota = false;
- idleOptions = mOptsWithFgs.toBundle();
- }
+ needsPermission = exact;
+ lowerQuota = !exact;
+ idleOptions = exact ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle();
} else {
needsPermission = false;
- lowQuota = allowWhileIdle;
+ lowerQuota = allowWhileIdle;
idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
}
if (needsPermission && !canScheduleExactAlarms()) {
- if (alarmClock == null && isExemptFromPermission(callingUid)) {
- // If the app is on the full system allow-list (not except-idle), we still
- // allow the alarms, but with a lower quota to keep pre-S compatibility.
- lowQuota = true;
- } else {
+ if (alarmClock != null || !isExemptFromExactAlarmPermission(callingUid)) {
final String errorMessage = "Caller " + callingPackage + " needs to hold "
+ Manifest.permission.SCHEDULE_EXACT_ALARM + " to set "
- + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock")
- + " alarms.";
+ + "exact alarms.";
if (mConstants.CRASH_NON_CLOCK_APPS) {
throw new SecurityException(errorMessage);
} else {
Slog.wtf(TAG, errorMessage);
- idleOptions = mOptsWithoutFgs.toBundle();
- lowQuota = allowWhileIdle;
}
}
+ // If the app is on the full system power allow-list (not except-idle), or we're
+ // in a soft failure mode, we still allow the alarms.
+ // We give temporary allowlist to allow-while-idle alarms but without FGS
+ // capability. Note that apps that are in the power allow-list do not need it.
+ idleOptions = allowWhileIdle ? mOptsWithoutFgs.toBundle() : null;
+ lowerQuota = allowWhileIdle;
}
- if (lowQuota) {
+ if (lowerQuota) {
flags &= ~FLAG_ALLOW_WHILE_IDLE;
flags |= FLAG_ALLOW_WHILE_IDLE_COMPAT;
}
@@ -2998,13 +3005,10 @@
/**
* Called when an app loses {@link Manifest.permission#SCHEDULE_EXACT_ALARM} to remove alarms
* that the app is no longer eligible to use.
- * TODO (b/179541791): Revisit and write tests once UX is final.
+ * TODO (b/179541791): Add revocation history to dumpsys.
*/
void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) {
- if (UserHandle.isCore(uid) || uid == mSystemUiUid) {
- return;
- }
- if (isExemptFromPermission(uid)) {
+ if (isExemptFromExactAlarmPermission(uid)) {
return;
}
if (!CompatChanges.isChangeEnabled(
@@ -3015,7 +3019,7 @@
final Predicate<Alarm> whichAlarms =
a -> (a.uid == uid && a.packageName.equals(packageName)
- && ((a.flags & FLAG_ALLOW_WHILE_IDLE) != 0 || a.alarmClock != null));
+ && a.windowLength == AlarmManager.WINDOW_EXACT);
final ArrayList<Alarm> removed = mAlarmStore.remove(whichAlarms);
final boolean didRemove = !removed.isEmpty();
if (didRemove) {
@@ -3873,6 +3877,7 @@
return alarm.creatorUid;
}
+
@VisibleForTesting
class AlarmHandler extends Handler {
public static final int ALARM_EVENT = 1;
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
index 0e442d0..e684b84 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
@@ -134,6 +134,11 @@
void dumpProto(ProtoOutputStream pos, long nowElapsed);
/**
+ * @return a name for this alarm store that can be used for debugging and tests.
+ */
+ String getName();
+
+ /**
* A functional interface used to update the alarm. Used to describe the update in
* {@link #updateAlarmDeliveries(AlarmDeliveryCalculator)}
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
index e7edfb7..cb528ba 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
@@ -27,6 +27,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StatLogger;
import java.text.SimpleDateFormat;
@@ -40,6 +41,8 @@
* This keeps the alarms in batches, which are sorted on the start time of their delivery window.
*/
public class BatchingAlarmStore implements AlarmStore {
+ @VisibleForTesting
+ static final String TAG = BatchingAlarmStore.class.getSimpleName();
private final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
private int mSize;
@@ -49,7 +52,7 @@
int REBATCH_ALL_ALARMS = 0;
}
- final StatLogger mStatLogger = new StatLogger("BatchingAlarmStore stats", new String[]{
+ final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{
"REBATCH_ALL_ALARMS",
});
@@ -211,6 +214,11 @@
}
}
+ @Override
+ public String getName() {
+ return TAG;
+ }
+
private void insertAndBatchAlarm(Alarm alarm) {
final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
: attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed());
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
index 8ca1446..c37d2c3 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
@@ -25,6 +25,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StatLogger;
import java.text.SimpleDateFormat;
@@ -38,6 +39,8 @@
* This keeps the alarms in a sorted list, and only batches them at the time of delivery.
*/
public class LazyAlarmStore implements AlarmStore {
+ @VisibleForTesting
+ static final String TAG = LazyAlarmStore.class.getSimpleName();
private final ArrayList<Alarm> mAlarms = new ArrayList<>();
private Runnable mOnAlarmClockRemoved;
@@ -47,7 +50,7 @@
int GET_NEXT_WAKEUP_DELIVERY_TIME = 1;
}
- final StatLogger mStatLogger = new StatLogger("LazyAlarmStore stats", new String[]{
+ final StatLogger mStatLogger = new StatLogger(TAG + " stats", new String[]{
"GET_NEXT_DELIVERY_TIME",
"GET_NEXT_WAKEUP_DELIVERY_TIME",
});
@@ -214,4 +217,9 @@
a.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARMS, nowElapsed);
}
}
+
+ @Override
+ public String getName() {
+ return TAG;
+ }
}
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 b70e68b..784c63a 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
@@ -32,7 +32,6 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.IUidObserver;
@@ -348,12 +347,20 @@
private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray();
/**
- * Mapping of app IDs to the when their temp allowlist grace period ends (in the elapsed
+ * Mapping of UIDs to the when their temp allowlist grace period ends (in the elapsed
* realtime timebase).
*/
private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray();
- private final ActivityManagerInternal mActivityManagerInternal;
+ /** Current set of UIDs in the {@link ActivityManager#PROCESS_STATE_TOP} state. */
+ private final SparseBooleanArray mTopAppCache = new SparseBooleanArray();
+
+ /**
+ * Mapping of UIDs to the when their top app grace period ends (in the elapsed realtime
+ * timebase).
+ */
+ private final SparseLongArray mTopAppGraceCache = new SparseLongArray();
+
private final AlarmManager mAlarmManager;
private final ChargingTracker mChargeTracker;
private final QcHandler mHandler;
@@ -412,7 +419,7 @@
}
};
- private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+ private class QcUidObserver extends IUidObserver.Stub {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
@@ -433,7 +440,7 @@
@Override
public void onUidCachedChanged(int uid, boolean cached) {
}
- };
+ }
private final BroadcastReceiver mPackageAddedReceiver = new BroadcastReceiver() {
@Override
@@ -548,8 +555,10 @@
*/
private long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS;
- private long mEJTempAllowlistGracePeriodMs =
- QcConstants.DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS;
+ private long mEJGracePeriodTempAllowlistMs =
+ QcConstants.DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS;
+
+ private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
/** The package verifier app. */
@Nullable
@@ -586,7 +595,6 @@
mHandler = new QcHandler(mContext.getMainLooper());
mChargeTracker = new ChargingTracker();
mChargeTracker.startTracking();
- mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
mQcConstants = new QcConstants();
mBackgroundJobsController = backgroundJobsController;
@@ -606,9 +614,12 @@
pai.registerTempAllowlistChangeListener(new TempAllowlistTracker());
try {
- ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+ ActivityManager.getService().registerUidObserver(new QcUidObserver(),
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_TOP, null);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
@@ -658,7 +669,7 @@
}
final int uid = jobStatus.getSourceUid();
- if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
+ if (mTopAppCache.get(uid)) {
if (DEBUG) {
Slog.d(TAG, jobStatus.toShortString() + " is top started job");
}
@@ -715,6 +726,8 @@
mUidToPackageCache.remove(uid);
mTempAllowlistCache.delete(uid);
mTempAllowlistGraceCache.delete(uid);
+ mTopAppCache.delete(uid);
+ mTopAppGraceCache.delete(uid);
}
@Override
@@ -770,14 +783,10 @@
/** Returns the maximum amount of time this job could run for. */
public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
- // Need to look at current proc state as well in the case where the job hasn't started yet.
- final boolean isTop = mActivityManagerInternal
- .getUidProcessState(jobStatus.getSourceUid()) <= ActivityManager.PROCESS_STATE_TOP;
-
if (!jobStatus.shouldTreatAsExpeditedJob()) {
// If quota is currently "free", then the job can run for the full amount of time.
if (mChargeTracker.isCharging()
- || isTop
+ || mTopAppCache.get(jobStatus.getSourceUid())
|| isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
@@ -790,7 +799,7 @@
if (mChargeTracker.isCharging()) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
- if (isTop || isTopStartedJobLocked(jobStatus)) {
+ if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) {
return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2,
getTimeUntilEJQuotaConsumedLocked(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()));
@@ -813,12 +822,30 @@
// 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)
if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) {
return true;
}
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long tempAllowlistGracePeriodEndElapsed =
+ mTempAllowlistGraceCache.get(jobStatus.getSourceUid());
+ final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid())
+ || nowElapsed < tempAllowlistGracePeriodEndElapsed;
+ if (hasTempAllowlistExemption) {
+ return true;
+ }
+
+ final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(jobStatus.getSourceUid());
+ final boolean hasTopAppExemption = mTopAppCache.get(jobStatus.getSourceUid())
+ || nowElapsed < topAppGracePeriodEndElapsed;
+ if (hasTopAppExemption) {
+ return true;
+ }
+
Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(),
jobStatus.getSourcePackageName());
- // Any already executing expedited jbos should be allowed to finish.
+ // Any already executing expedited jobs should be allowed to finish.
if (ejTimer != null && ejTimer.isRunning(jobStatus)) {
return true;
}
@@ -2092,8 +2119,12 @@
final boolean hasTempAllowlistExemption = !mRegularJobTimer
&& (mTempAllowlistCache.get(mUid)
|| nowElapsed < tempAllowlistGracePeriodEndElapsed);
+ final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
+ final boolean hasTopAppExemption = !mRegularJobTimer
+ && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);
return (standbyBucket == RESTRICTED_INDEX || !mChargeTracker.isCharging())
- && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption;
+ && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption
+ && !hasTopAppExemption;
}
void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
@@ -2412,11 +2443,11 @@
public void onAppRemoved(int uid) {
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
- final long endElapsed = nowElapsed + mEJTempAllowlistGracePeriodMs;
+ final long endElapsed = nowElapsed + mEJGracePeriodTempAllowlistMs;
mTempAllowlistCache.delete(uid);
mTempAllowlistGraceCache.put(uid, endElapsed);
Message msg = mHandler.obtainMessage(MSG_END_GRACE_PERIOD, uid, 0);
- mHandler.sendMessageDelayed(msg, mEJTempAllowlistGracePeriodMs);
+ mHandler.sendMessageDelayed(msg, mEJGracePeriodTempAllowlistMs);
}
}
}
@@ -2562,12 +2593,37 @@
synchronized (mLock) {
boolean isQuotaFree;
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= ActivityManager.PROCESS_STATE_TOP) {
+ mTopAppCache.put(uid, true);
+ mTopAppGraceCache.delete(uid);
+ if (mForegroundUids.get(uid)) {
+ // Went from FGS to TOP. We don't need to reprocess timers or
+ // jobs.
+ break;
+ }
mForegroundUids.put(uid, true);
isQuotaFree = true;
} else {
- mForegroundUids.delete(uid);
- isQuotaFree = false;
+ final boolean reprocess;
+ if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ reprocess = !mForegroundUids.get(uid);
+ mForegroundUids.put(uid, true);
+ isQuotaFree = true;
+ } else {
+ reprocess = true;
+ mForegroundUids.delete(uid);
+ isQuotaFree = false;
+ }
+ if (mTopAppCache.get(uid)) {
+ final long endElapsed = nowElapsed + mEJGracePeriodTopAppMs;
+ mTopAppCache.delete(uid);
+ mTopAppGraceCache.put(uid, endElapsed);
+ sendMessageDelayed(obtainMessage(MSG_END_GRACE_PERIOD, uid, 0),
+ mEJGracePeriodTopAppMs);
+ }
+ if (!reprocess) {
+ break;
+ }
}
// Update Timers first.
if (mPkgTimers.indexOfKey(userId) >= 0
@@ -2635,20 +2691,31 @@
case MSG_END_GRACE_PERIOD: {
final int uid = msg.arg1;
synchronized (mLock) {
- if (mTempAllowlistCache.get(uid)) {
- // App added back to the temp allowlist during the grace period.
+ if (mTempAllowlistCache.get(uid) || mTopAppCache.get(uid)) {
+ // App added back to the temp allowlist or became top again
+ // during the grace period.
if (DEBUG) {
Slog.d(TAG, uid + " is still allowed");
}
break;
}
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (nowElapsed < mTempAllowlistGraceCache.get(uid)
+ || nowElapsed < mTopAppGraceCache.get(uid)) {
+ // One of the grace periods is still in effect.
+ if (DEBUG) {
+ Slog.d(TAG, uid + " is still in grace period");
+ }
+ break;
+ }
if (DEBUG) {
Slog.d(TAG, uid + " is now out of grace period");
}
+ mTempAllowlistGraceCache.delete(uid);
+ mTopAppGraceCache.delete(uid);
final ArraySet<String> packages = getPackagesForUidLocked(uid);
if (packages != null) {
final int userId = UserHandle.getUserId(uid);
- final long nowElapsed = sElapsedRealtimeClock.millis();
for (int i = packages.size() - 1; i >= 0; --i) {
Timer t = mEJPkgTimers.get(userId, packages.valueAt(i));
if (t != null) {
@@ -2980,8 +3047,11 @@
static final String KEY_EJ_REWARD_NOTIFICATION_SEEN_MS =
QC_CONSTANT_PREFIX + "ej_reward_notification_seen_ms";
@VisibleForTesting
- static final String KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS =
- QC_CONSTANT_PREFIX + "ej_temp_allowlist_grace_period_ms";
+ static final String KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS =
+ QC_CONSTANT_PREFIX + "ej_grace_period_temp_allowlist_ms";
+ @VisibleForTesting
+ static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
+ QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms";
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
10 * 60 * 1000L; // 10 minutes
@@ -3034,7 +3104,8 @@
private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0;
- private static final long DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS = 3 * MINUTE_IN_MILLIS;
+ private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS;
+ private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS;
/** How much time each app will have to run jobs within their standby bucket window. */
public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
@@ -3266,7 +3337,12 @@
* How much additional grace period to add to the end of an app's temp allowlist
* duration.
*/
- public long EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS = DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS;
+ public long EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS;
+
+ /**
+ * How much additional grace period to give an app when it leaves the TOP state.
+ */
+ public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
@@ -3461,13 +3537,21 @@
mEJRewardNotificationSeenMs = Math.min(5 * MINUTE_IN_MILLIS,
Math.max(0, EJ_REWARD_NOTIFICATION_SEEN_MS));
break;
- case KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS:
+ case KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS:
// We don't need to re-evaluate execution stats or constraint status for this.
- EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS =
- properties.getLong(key, DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS);
+ EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS =
+ properties.getLong(key, DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS);
// Limit grace period to be in the range [0 minutes, 1 hour].
- mEJTempAllowlistGracePeriodMs = Math.min(HOUR_IN_MILLIS,
- Math.max(0, EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS));
+ mEJGracePeriodTempAllowlistMs = Math.min(HOUR_IN_MILLIS,
+ Math.max(0, EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS));
+ break;
+ case KEY_EJ_GRACE_PERIOD_TOP_APP_MS:
+ // We don't need to re-evaluate execution stats or constraint status for this.
+ EJ_GRACE_PERIOD_TOP_APP_MS =
+ properties.getLong(key, DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS);
+ // Limit grace period to be in the range [0 minutes, 1 hour].
+ mEJGracePeriodTopAppMs = Math.min(HOUR_IN_MILLIS,
+ Math.max(0, EJ_GRACE_PERIOD_TOP_APP_MS));
break;
}
}
@@ -3730,8 +3814,9 @@
pw.print(KEY_EJ_REWARD_TOP_APP_MS, EJ_REWARD_TOP_APP_MS).println();
pw.print(KEY_EJ_REWARD_INTERACTION_MS, EJ_REWARD_INTERACTION_MS).println();
pw.print(KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, EJ_REWARD_NOTIFICATION_SEEN_MS).println();
- pw.print(KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS,
- EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS).println();
+ pw.print(KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS,
+ EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS).println();
+ pw.print(KEY_EJ_GRACE_PERIOD_TOP_APP_MS, EJ_GRACE_PERIOD_TOP_APP_MS).println();
pw.decreaseIndent();
}
@@ -3844,6 +3929,16 @@
}
@VisibleForTesting
+ long getEJGracePeriodTempAllowlistMs() {
+ return mEJGracePeriodTempAllowlistMs;
+ }
+
+ @VisibleForTesting
+ long getEJGracePeriodTopAppMs() {
+ return mEJGracePeriodTopAppMs;
+ }
+
+ @VisibleForTesting
@NonNull
long[] getEJLimitsMs() {
return mEJLimitsMs;
@@ -3879,11 +3974,6 @@
}
@VisibleForTesting
- long getEJTempAllowlistGracePeriodMs() {
- return mEJTempAllowlistGracePeriodMs;
- }
-
- @VisibleForTesting
@Nullable
List<TimingSession> getEJTimingSessions(int userId, String packageName) {
return mEJTimingSessions.get(userId, packageName);
@@ -3955,6 +4045,17 @@
pw.println(mForegroundUids.toString());
pw.println();
+ pw.print("Cached top apps: ");
+ pw.println(mTopAppCache.toString());
+ pw.print("Cached top app grace period: ");
+ pw.println(mTopAppGraceCache.toString());
+
+ pw.print("Cached temp allowlist: ");
+ pw.println(mTempAllowlistCache.toString());
+ pw.print("Cached temp allowlist grace period: ");
+ pw.println(mTempAllowlistGraceCache.toString());
+ pw.println();
+
pw.println("Cached UID->package map:");
pw.increaseIndent();
for (int i = 0; i < mUidToPackageCache.size(); ++i) {
@@ -3966,12 +4067,6 @@
pw.decreaseIndent();
pw.println();
- pw.print("Cached temp allowlist: ");
- pw.println(mTempAllowlistCache.toString());
- pw.print("Cached temp allowlist grace period: ");
- pw.println(mTempAllowlistGraceCache.toString());
-
- pw.println();
mTrackedJobs.forEach((jobs) -> {
for (int j = 0; j < jobs.size(); j++) {
final JobStatus js = jobs.valueAt(j);
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 3d129d8..20ce133 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -135,6 +135,10 @@
":updatable-media-srcs",
],
+ api_lint: {
+ enabled: false,
+ },
+
libs: [
"framework_media_annotation",
],
diff --git a/apex/media/framework/api/system-current.txt b/apex/media/framework/api/system-current.txt
index 6158e2e..1d912eb 100644
--- a/apex/media/framework/api/system-current.txt
+++ b/apex/media/framework/api/system-current.txt
@@ -3,38 +3,19 @@
public final class MediaTranscodeManager {
method @Nullable public android.media.MediaTranscodeManager.TranscodingSession enqueueRequest(@NonNull android.media.MediaTranscodeManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodeManager.OnTranscodingFinishedListener);
- field public static final int PRIORITY_REALTIME = 1; // 0x1
- field public static final int TRANSCODING_TYPE_VIDEO = 1; // 0x1
}
@java.lang.FunctionalInterface public static interface MediaTranscodeManager.OnTranscodingFinishedListener {
method public void onTranscodingFinished(@NonNull android.media.MediaTranscodeManager.TranscodingSession);
}
- public static final class MediaTranscodeManager.TranscodingRequest {
+ public abstract static class MediaTranscodeManager.TranscodingRequest {
method public int getClientPid();
method public int getClientUid();
method @Nullable public android.os.ParcelFileDescriptor getDestinationFileDescriptor();
method @NonNull public android.net.Uri getDestinationUri();
- method public int getPriority();
method @Nullable public android.os.ParcelFileDescriptor getSourceFileDescriptor();
method @NonNull public android.net.Uri getSourceUri();
- method public int getType();
- method @Nullable public android.media.MediaFormat getVideoTrackFormat();
- }
-
- public static final class MediaTranscodeManager.TranscodingRequest.Builder {
- ctor public MediaTranscodeManager.TranscodingRequest.Builder();
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build();
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setType(int);
- method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat);
}
public static class MediaTranscodeManager.TranscodingRequest.MediaFormatResolver {
@@ -71,5 +52,18 @@
method public void onProgressUpdate(@NonNull android.media.MediaTranscodeManager.TranscodingSession, @IntRange(from=0, to=100) int);
}
+ public static final class MediaTranscodeManager.VideoTranscodingRequest extends android.media.MediaTranscodeManager.TranscodingRequest {
+ method @NonNull public android.media.MediaFormat getVideoTrackFormat();
+ }
+
+ public static final class MediaTranscodeManager.VideoTranscodingRequest.Builder {
+ ctor public MediaTranscodeManager.VideoTranscodingRequest.Builder(@NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.media.MediaFormat);
+ method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest build();
+ method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientPid(int);
+ method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setClientUid(int);
+ method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setDestinationFileDescriptor(android.os.ParcelFileDescriptor);
+ method @NonNull public android.media.MediaTranscodeManager.VideoTranscodingRequest.Builder setSourceFileDescriptor(android.os.ParcelFileDescriptor);
+ }
+
}
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index 906071f..3f30d3e 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -19,11 +19,14 @@
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import com.android.modules.annotation.MinSdk;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -77,6 +80,7 @@
There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform,
application will only need to specify individual types they supported.
*/
+@MinSdk(Build.VERSION_CODES.S)
public final class ApplicationMediaCapabilities implements Parcelable {
private static final String TAG = "ApplicationMediaCapabilities";
diff --git a/apex/media/framework/java/android/media/MediaFeature.java b/apex/media/framework/java/android/media/MediaFeature.java
index 0e461888..8d1b159 100644
--- a/apex/media/framework/java/android/media/MediaFeature.java
+++ b/apex/media/framework/java/android/media/MediaFeature.java
@@ -17,6 +17,9 @@
package android.media;
import android.annotation.StringDef;
+import android.os.Build;
+
+import com.android.modules.annotation.MinSdk;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -24,6 +27,7 @@
/**
* MediaFeature defines various media features, e.g. hdr type.
*/
+@MinSdk(Build.VERSION_CODES.S)
public final class MediaFeature {
/**
* Defines tye type of HDR(high dynamic range) video.
diff --git a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
index 9332835..de2924e 100644
--- a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
+++ b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
@@ -21,7 +21,9 @@
import android.annotation.SystemApi.Client;
import android.app.SystemServiceRegistry;
import android.content.Context;
+import android.os.Build;
+import com.android.modules.annotation.MinSdk;
import com.android.modules.utils.build.SdkLevel;
/**
@@ -29,6 +31,7 @@
*
* @hide
*/
+@MinSdk(Build.VERSION_CODES.S)
@SystemApi(client = Client.MODULE_LIBRARIES)
public class MediaFrameworkInitializer {
private MediaFrameworkInitializer() {
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index 30d1896..79e0d58 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -34,6 +35,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.annotation.MinSdk;
import java.io.FileNotFoundException;
import java.lang.annotation.Retention;
@@ -48,27 +50,23 @@
import java.util.concurrent.Executors;
/**
- MediaTranscodeManager provides an interface to the system's media transcoding service and can be
- used to transcode media files, e.g. transcoding a video from HEVC to AVC.
+ Android 12 introduces Compatible media transcoding feature. See
+ <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding">
+ Compatible media transcoding</a>. MediaTranscodeManager provides an interface to the system's media
+ transcoding service and can be used to transcode media files, e.g. transcoding a video from HEVC to
+ AVC.
<h3>Transcoding Types</h3>
<h4>Video Transcoding</h4>
- When transcoding a video file, the video file could be of any of the following types:
- <ul>
- <li> Video file with single video track. </li>
- <li> Video file with multiple video track. </li>
- <li> Video file with multiple video tracks and audio tracks. </li>
- <li> Video file with video/audio tracks and metadata track. Note that metadata track will be passed
- through only if it could be recognized by {@link MediaExtractor}.
- TODO(hkuang): Finalize the metadata track behavior. </li>
- </ul>
+ When transcoding a video file, the video track will be transcoded based on the desired track format
+ and the audio track will be pass through without any modification.
<p class=note>
- Note that currently only support transcoding video file in mp4 format.
+ Note that currently only support transcoding video file in mp4 format and with single video track.
<h3>Transcoding Request</h3>
<p>
To transcode a media file, first create a {@link TranscodingRequest} through its builder class
- {@link TranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through
+ {@link VideoTranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through
{@link MediaTranscodeManager#enqueueRequest(
TranscodingRequest, Executor, OnTranscodingFinishedListener)}
TranscodeRequest are processed based on client process's priority and request priority. When a
@@ -80,25 +78,12 @@
Here is an example where <code>Builder</code> is used to specify all parameters
<pre class=prettyprint>
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(srcUri)
- .setDestinationUri(dstUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(REALTIME)
- .setVideoTrackFormat(videoFormat)
- .build();
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(srcUri, dstUri, videoFormat).build();
}</pre>
-
- TODO(hkuang): Add architecture diagram showing the transcoding service and api.
- TODO(hkuang): Add sample code when API is settled.
- TODO(hkuang): Clarify whether multiple video tracks is supported or not.
- TODO(hkuang): Clarify whether image/audio transcoding is supported or not.
- TODO(hkuang): Clarify what will happen if there is unrecognized track in the source.
- TODO(hkuang): Clarify whether supports scaling.
- TODO(hkuang): Clarify whether supports framerate conversion.
@hide
*/
+@MinSdk(Build.VERSION_CODES.S)
@SystemApi
public final class MediaTranscodeManager {
private static final String TAG = "MediaTranscodeManager";
@@ -113,68 +98,6 @@
private static final float BPP = 0.25f;
/**
- * Default transcoding type.
- * @hide
- */
- public static final int TRANSCODING_TYPE_UNKNOWN = 0;
-
- /**
- * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video file.
- * <p>Note that currently only support transcoding video file in mp4 format.
- */
- public static final int TRANSCODING_TYPE_VIDEO = 1;
-
- /**
- * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image file.
- * @hide
- */
- public static final int TRANSCODING_TYPE_IMAGE = 2;
-
- /** @hide */
- @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = {
- TRANSCODING_TYPE_UNKNOWN,
- TRANSCODING_TYPE_VIDEO,
- TRANSCODING_TYPE_IMAGE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TranscodingType {}
-
- /**
- * Default value.
- * @hide
- */
- public static final int PRIORITY_UNKNOWN = 0;
- /**
- * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the
- * client wants the transcoding result as soon as possible.
- * <p> Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve
- * performance penalty due to resource reallocation to prioritize the sessions with higher
- * priority.
- * TODO(hkuang): Add more description of this when priority is finalized.
- */
- public static final int PRIORITY_REALTIME = 1;
-
- /**
- * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not need
- * the transcoding result as soon as possible.
- * <p>Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set to
- * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept
- * delay of the transcoding result.
- * @hide
- * TODO(hkuang): Add more description of this when priority is finalized.
- */
- public static final int PRIORITY_OFFLINE = 2;
-
- /** @hide */
- @IntDef(prefix = {"PRIORITY_"}, value = {
- PRIORITY_UNKNOWN,
- PRIORITY_REALTIME,
- PRIORITY_OFFLINE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TranscodingPriority {}
-
- /**
* Listener that gets notified when a transcoding operation has finished.
* This listener gets notified regardless of how the operation finished. It is up to the
* listener implementation to check the result and take appropriate action.
@@ -500,7 +423,79 @@
}
}
- public static final class TranscodingRequest {
+ /**
+ * Abstract base class for all the TranscodingRequest.
+ * <p> TranscodingRequest encapsulates the desired configuration for the transcoding.
+ */
+ public abstract static class TranscodingRequest {
+ /**
+ *
+ * Default transcoding type.
+ * @hide
+ */
+ public static final int TRANSCODING_TYPE_UNKNOWN = 0;
+
+ /**
+ * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video.
+ * <p>Note that currently only support transcoding video file in mp4 format.
+ * @hide
+ */
+ public static final int TRANSCODING_TYPE_VIDEO = 1;
+
+ /**
+ * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image.
+ * @hide
+ */
+ public static final int TRANSCODING_TYPE_IMAGE = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = {
+ TRANSCODING_TYPE_UNKNOWN,
+ TRANSCODING_TYPE_VIDEO,
+ TRANSCODING_TYPE_IMAGE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TranscodingType {}
+
+ /**
+ * Default value.
+ *
+ * @hide
+ */
+ public static final int PRIORITY_UNKNOWN = 0;
+ /**
+ * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the
+ * client wants the transcoding result as soon as possible.
+ * <p> Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve
+ * performance penalty due to resource reallocation to prioritize the sessions with higher
+ * priority.
+ *
+ * @hide
+ */
+ public static final int PRIORITY_REALTIME = 1;
+
+ /**
+ * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not
+ * need the transcoding result as soon as possible.
+ * <p>Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set
+ * to
+ * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept
+ * delay of the transcoding result.
+ *
+ * @hide
+ *
+ */
+ public static final int PRIORITY_OFFLINE = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"PRIORITY_"}, value = {
+ PRIORITY_UNKNOWN,
+ PRIORITY_REALTIME,
+ PRIORITY_OFFLINE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TranscodingPriority {}
+
/** Uri of the source media file. */
private @NonNull Uri mSourceUri;
@@ -534,22 +529,6 @@
private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
/**
- * Desired output video format of the destination file.
- * <p> If this is null, source file's video track will be passed through and copied to the
- * destination file.
- * <p>
- */
- private @Nullable MediaFormat mVideoTrackFormat = null;
-
- /**
- * Desired output audio format of the destination file.
- * <p> If this is null, source file's audio track will be passed through and copied to the
- * destination file.
- * @hide
- */
- private @Nullable MediaFormat mAudioTrackFormat = null;
-
- /**
* Desired image format for the destination file.
* <p> If this is null, source file's image track will be passed through and copied to the
* destination file.
@@ -560,6 +539,12 @@
@VisibleForTesting
private TranscodingTestConfig mTestConfig = null;
+ /**
+ * Prevent public constructor access.
+ */
+ /* package private */ TranscodingRequest() {
+ }
+
private TranscodingRequest(Builder b) {
mSourceUri = b.mSourceUri;
mSourceFileDescriptor = b.mSourceFileDescriptor;
@@ -569,13 +554,13 @@
mClientPid = b.mClientPid;
mPriority = b.mPriority;
mType = b.mType;
- mVideoTrackFormat = b.mVideoTrackFormat;
- mAudioTrackFormat = b.mAudioTrackFormat;
- mImageFormat = b.mImageFormat;
mTestConfig = b.mTestConfig;
}
- /** Return the type of the transcoding. */
+ /**
+ * Return the type of the transcoding.
+ * @hide
+ */
@TranscodingType
public int getType() {
return mType;
@@ -621,22 +606,16 @@
return mDestinationFileDescriptor;
}
- /** Return priority of the transcoding. */
+ /**
+ * Return priority of the transcoding.
+ * @hide
+ */
@TranscodingPriority
public int getPriority() {
return mPriority;
}
/**
- * Return the video track format of the transcoding.
- * This will be null is the transcoding is not for video transcoding.
- */
- @Nullable
- public MediaFormat getVideoTrackFormat() {
- return mVideoTrackFormat;
- }
-
- /**
* Return TestConfig of the transcoding.
* @hide
*/
@@ -645,6 +624,8 @@
return mTestConfig;
}
+ abstract void writeFormatToParcel(TranscodingRequestParcel parcel);
+
/* Writes the TranscodingRequest to a parcel. */
private TranscodingRequestParcel writeToParcel(@NonNull Context context) {
TranscodingRequestParcel parcel = new TranscodingRequestParcel();
@@ -668,7 +649,7 @@
}
parcel.clientPackageName = packageName;
}
- parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat);
+ writeFormatToParcel(parcel);
if (mTestConfig != null) {
parcel.isForTesting = true;
parcel.testConfig = mTestConfig;
@@ -676,71 +657,12 @@
return parcel;
}
- /* Converts the MediaFormat to TranscodingVideoTrackFormat. */
- private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) {
- if (format == null) {
- throw new IllegalArgumentException("Invalid MediaFormat");
- }
-
- TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat();
-
- if (format.containsKey(MediaFormat.KEY_MIME)) {
- String mime = format.getString(MediaFormat.KEY_MIME);
- if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
- trackFormat.codecType = TranscodingVideoCodecType.kAvc;
- } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
- trackFormat.codecType = TranscodingVideoCodecType.kHevc;
- } else {
- throw new UnsupportedOperationException("Only support transcode to avc/hevc");
- }
- }
-
- if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
- int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE);
- if (bitrateBps <= 0) {
- throw new IllegalArgumentException("Bitrate must be larger than 0");
- }
- trackFormat.bitrateBps = bitrateBps;
- }
-
- if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey(
- MediaFormat.KEY_HEIGHT)) {
- int width = format.getInteger(MediaFormat.KEY_WIDTH);
- int height = format.getInteger(MediaFormat.KEY_HEIGHT);
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("Width and height must be larger than 0");
- }
- // TODO(hkuang): Validate the aspect ratio after adding scaling.
- trackFormat.width = width;
- trackFormat.height = height;
- }
-
- if (format.containsKey(MediaFormat.KEY_PROFILE)) {
- int profile = format.getInteger(MediaFormat.KEY_PROFILE);
- if (profile <= 0) {
- throw new IllegalArgumentException("Invalid codec profile");
- }
- // TODO(hkuang): Validate the profile according to codec type.
- trackFormat.profile = profile;
- }
-
- if (format.containsKey(MediaFormat.KEY_LEVEL)) {
- int level = format.getInteger(MediaFormat.KEY_LEVEL);
- if (level <= 0) {
- throw new IllegalArgumentException("Invalid codec level");
- }
- // TODO(hkuang): Validate the level according to codec type.
- trackFormat.level = level;
- }
-
- return trackFormat;
- }
-
/**
- * Builder class for {@link TranscodingRequest} objects.
- * Use this class to configure and create a <code>TranscodingRequest</code> instance.
+ * Builder to build a {@link TranscodingRequest} object.
+ *
+ * @param <T> The subclass to be built.
*/
- public static final class Builder {
+ abstract static class Builder<T extends Builder<T>> {
private @NonNull Uri mSourceUri;
private @NonNull Uri mDestinationUri;
private @Nullable ParcelFileDescriptor mSourceFileDescriptor = null;
@@ -749,80 +671,68 @@
private int mClientPid = -1;
private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN;
private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
- private @Nullable MediaFormat mVideoTrackFormat;
- private @Nullable MediaFormat mAudioTrackFormat;
- private @Nullable MediaFormat mImageFormat;
private TranscodingTestConfig mTestConfig;
+ abstract T self();
+
/**
- * Specifies the uri of source media file.
+ * Creates a builder for building {@link TranscodingRequest}s.
*
* Client must set the source Uri. If client also provides the source fileDescriptor
* through is provided by {@link #setSourceFileDescriptor(ParcelFileDescriptor)},
* TranscodingSession will use the fd instead of calling back to the client to open the
* sourceUri.
+ *
+ *
+ * @param type The transcoding type.
* @param sourceUri Content uri for the source media file.
- * @return The same builder instance.
- * @throws IllegalArgumentException if Uri is null or empty.
+ * @param destinationUri Content uri for the destination media file.
+ *
*/
- @NonNull
- public Builder setSourceUri(@NonNull Uri sourceUri) {
+ private Builder(@TranscodingType int type, @NonNull Uri sourceUri,
+ @NonNull Uri destinationUri) {
+ mType = type;
+
if (sourceUri == null || Uri.EMPTY.equals(sourceUri)) {
throw new IllegalArgumentException(
"You must specify a non-empty source Uri.");
}
mSourceUri = sourceUri;
- return this;
+
+ if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) {
+ throw new IllegalArgumentException(
+ "You must specify a non-empty destination Uri.");
+ }
+ mDestinationUri = destinationUri;
}
/**
* Specifies the fileDescriptor opened from the source media file.
*
* This call is optional. If the source fileDescriptor is provided, TranscodingSession
- * will use it directly instead of opening the uri from {@link #setSourceUri(Uri)}. It
- * is client's responsibility to make sure the fileDescriptor is opened from the source
- * uri.
+ * will use it directly instead of opening the uri from {@link #Builder(int, Uri, Uri)}.
+ * It is client's responsibility to make sure the fileDescriptor is opened from the
+ * source uri.
* @param fileDescriptor a {@link ParcelFileDescriptor} opened from source media file.
* @return The same builder instance.
* @throws IllegalArgumentException if fileDescriptor is invalid.
*/
@NonNull
- public Builder setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) {
+ public T setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) {
if (fileDescriptor == null || fileDescriptor.getFd() < 0) {
throw new IllegalArgumentException(
"Invalid source descriptor.");
}
mSourceFileDescriptor = fileDescriptor;
- return this;
- }
-
- /**
- * Specifies the uri of the destination media file.
- *
- * Client must set the destination Uri. If client also provides the destination
- * fileDescriptor through {@link #setDestinationFileDescriptor(ParcelFileDescriptor)},
- * TranscodingSession will use the fd instead of calling back to the client to open the
- * destinationUri.
- * @param destinationUri Content uri for the destination media file.
- * @return The same builder instance.
- * @throws IllegalArgumentException if Uri is null or empty.
- */
- @NonNull
- public Builder setDestinationUri(@NonNull Uri destinationUri) {
- if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) {
- throw new IllegalArgumentException(
- "You must specify a non-empty destination Uri.");
- }
- mDestinationUri = destinationUri;
- return this;
+ return self();
}
/**
* Specifies the fileDescriptor opened from the destination media file.
*
* This call is optional. If the destination fileDescriptor is provided,
- * TranscodingSession will use it directly instead of opening the uri from
- * {@link #setDestinationUri(Uri)} upon transcoding starts. It is client's
+ * TranscodingSession will use it directly instead of opening the source uri from
+ * {@link #Builder(int, Uri, Uri)} upon transcoding starts. It is client's
* responsibility to make sure the fileDescriptor is opened from the destination uri.
* @param fileDescriptor a {@link ParcelFileDescriptor} opened from destination media
* file.
@@ -830,46 +740,54 @@
* @throws IllegalArgumentException if fileDescriptor is invalid.
*/
@NonNull
- public Builder setDestinationFileDescriptor(
+ public T setDestinationFileDescriptor(
@NonNull ParcelFileDescriptor fileDescriptor) {
if (fileDescriptor == null || fileDescriptor.getFd() < 0) {
throw new IllegalArgumentException(
"Invalid destination descriptor.");
}
mDestinationFileDescriptor = fileDescriptor;
- return this;
+ return self();
}
/**
* Specify the UID of the client that this request is for.
+ * <p>
+ * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the
+ * pid. Note that the permission check happens on the service side upon starting the
+ * transcoding. If the client does not have the permission, the transcoding will fail.
+ *
* @param uid client Uid.
* @return The same builder instance.
* @throws IllegalArgumentException if uid is invalid.
- * TODO(hkuang): Check the permission if it is allowed.
*/
@NonNull
- public Builder setClientUid(int uid) {
+ public T setClientUid(int uid) {
if (uid < 0) {
throw new IllegalArgumentException("Invalid Uid");
}
mClientUid = uid;
- return this;
+ return self();
}
/**
- * Specify the PID of the client that this request is for.
+ * Specify the pid of the client that this request is for.
+ * <p>
+ * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the
+ * pid. Note that the permission check happens on the service side upon starting the
+ * transcoding. If the client does not have the permission, the transcoding will fail.
+ *
* @param pid client Pid.
* @return The same builder instance.
* @throws IllegalArgumentException if pid is invalid.
- * TODO(hkuang): Check the permission if it is allowed.
*/
@NonNull
- public Builder setClientPid(int pid) {
+ public T setClientPid(int pid) {
if (pid < 0) {
throw new IllegalArgumentException("Invalid pid");
}
mClientPid = pid;
- return this;
+ return self();
}
/**
@@ -878,64 +796,15 @@
* @param priority Must be one of the {@code PRIORITY_*}
* @return The same builder instance.
* @throws IllegalArgumentException if flags is invalid.
+ * @hide
*/
@NonNull
- public Builder setPriority(@TranscodingPriority int priority) {
+ public T setPriority(@TranscodingPriority int priority) {
if (priority != PRIORITY_OFFLINE && priority != PRIORITY_REALTIME) {
throw new IllegalArgumentException("Invalid priority: " + priority);
}
mPriority = priority;
- return this;
- }
-
- /**
- * Specifies the type of transcoding.
- * <p> Clients must provide the source and destination that corresponds to the
- * transcoding type.
- *
- * @param type Must be one of the {@code TRANSCODING_TYPE_*}
- * @return The same builder instance.
- * @throws IllegalArgumentException if flags is invalid.
- */
- @NonNull
- public Builder setType(@TranscodingType int type) {
- if (type != TRANSCODING_TYPE_VIDEO && type != TRANSCODING_TYPE_IMAGE) {
- throw new IllegalArgumentException("Invalid transcoding type");
- }
- mType = type;
- return this;
- }
-
- /**
- * Specifies the desired video track format in the destination media file.
- * <p>Client could only specify the settings that matters to them, e.g. codec format or
- * bitrate. And by default, transcoding will preserve the original video's
- * settings(bitrate, framerate, resolution) if not provided.
- * <p>Note that some settings may silently fail to apply if the device does not
- * support them.
- * TODO(hkuang): Add MediaTranscodeUtil to help client generate transcoding setting.
- * TODO(hkuang): Add MediaTranscodeUtil to check if the setting is valid.
- *
- * @param videoFormat MediaFormat containing the settings that client wants override in
- * the original video's video track.
- * @return The same builder instance.
- * @throws IllegalArgumentException if videoFormat is invalid.
- */
- @NonNull
- public Builder setVideoTrackFormat(@NonNull MediaFormat videoFormat) {
- if (videoFormat == null) {
- throw new IllegalArgumentException("videoFormat must not be null");
- }
-
- // Check if the MediaFormat is for video by looking at the MIME type.
- String mime = videoFormat.containsKey(MediaFormat.KEY_MIME)
- ? videoFormat.getString(MediaFormat.KEY_MIME) : null;
- if (mime == null || !mime.startsWith("video/")) {
- throw new IllegalArgumentException("Invalid video format: wrong mime type");
- }
-
- mVideoTrackFormat = videoFormat;
- return this;
+ return self();
}
/**
@@ -946,44 +815,9 @@
*/
@VisibleForTesting
@NonNull
- public Builder setTestConfig(@NonNull TranscodingTestConfig config) {
+ public T setTestConfig(@NonNull TranscodingTestConfig config) {
mTestConfig = config;
- return this;
- }
-
- /**
- * @return a new {@link TranscodingRequest} instance successfully initialized with all
- * the parameters set on this <code>Builder</code>.
- * @throws UnsupportedOperationException if the parameters set on the
- * <code>Builder</code> were incompatible, or if they are not supported by the
- * device.
- */
- @NonNull
- public TranscodingRequest build() {
- if (mSourceUri == null) {
- throw new UnsupportedOperationException("Source URI must not be null");
- }
-
- if (mDestinationUri == null) {
- throw new UnsupportedOperationException("Destination URI must not be null");
- }
-
- if (mPriority == PRIORITY_UNKNOWN) {
- throw new UnsupportedOperationException("Must specify transcoding priority");
- }
-
- // Only support video transcoding now.
- if (mType != TRANSCODING_TYPE_VIDEO) {
- throw new UnsupportedOperationException("Only supports video transcoding now");
- }
-
- // Must provide video track format for video transcoding.
- if (mType == TRANSCODING_TYPE_VIDEO && mVideoTrackFormat == null) {
- throw new UnsupportedOperationException(
- "Must provide video track format for video transcoding");
- }
-
- return new TranscodingRequest(this);
+ return self();
}
}
@@ -1198,6 +1032,206 @@
}
/**
+ * VideoTranscodingRequest encapsulates the configuration for transcoding a video.
+ */
+ public static final class VideoTranscodingRequest extends TranscodingRequest {
+ /**
+ * Desired output video format of the destination file.
+ * <p> If this is null, source file's video track will be passed through and copied to the
+ * destination file.
+ */
+ private @Nullable MediaFormat mVideoTrackFormat = null;
+
+ /**
+ * Desired output audio format of the destination file.
+ * <p> If this is null, source file's audio track will be passed through and copied to the
+ * destination file.
+ */
+ private @Nullable MediaFormat mAudioTrackFormat = null;
+
+ private VideoTranscodingRequest(VideoTranscodingRequest.Builder builder) {
+ super(builder);
+ mVideoTrackFormat = builder.mVideoTrackFormat;
+ mAudioTrackFormat = builder.mAudioTrackFormat;
+ }
+
+ /**
+ * Return the video track format of the transcoding.
+ * This will be null if client has not specified the video track format.
+ */
+ @NonNull
+ public MediaFormat getVideoTrackFormat() {
+ return mVideoTrackFormat;
+ }
+
+ @Override
+ void writeFormatToParcel(TranscodingRequestParcel parcel) {
+ parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat);
+ }
+
+ /* Converts the MediaFormat to TranscodingVideoTrackFormat. */
+ private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) {
+ if (format == null) {
+ throw new IllegalArgumentException("Invalid MediaFormat");
+ }
+
+ TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat();
+
+ if (format.containsKey(MediaFormat.KEY_MIME)) {
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
+ trackFormat.codecType = TranscodingVideoCodecType.kAvc;
+ } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
+ trackFormat.codecType = TranscodingVideoCodecType.kHevc;
+ } else {
+ throw new UnsupportedOperationException("Only support transcode to avc/hevc");
+ }
+ }
+
+ if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
+ int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE);
+ if (bitrateBps <= 0) {
+ throw new IllegalArgumentException("Bitrate must be larger than 0");
+ }
+ trackFormat.bitrateBps = bitrateBps;
+ }
+
+ if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey(
+ MediaFormat.KEY_HEIGHT)) {
+ int width = format.getInteger(MediaFormat.KEY_WIDTH);
+ int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Width and height must be larger than 0");
+ }
+ // TODO: Validate the aspect ratio after adding scaling.
+ trackFormat.width = width;
+ trackFormat.height = height;
+ }
+
+ if (format.containsKey(MediaFormat.KEY_PROFILE)) {
+ int profile = format.getInteger(MediaFormat.KEY_PROFILE);
+ if (profile <= 0) {
+ throw new IllegalArgumentException("Invalid codec profile");
+ }
+ // TODO: Validate the profile according to codec type.
+ trackFormat.profile = profile;
+ }
+
+ if (format.containsKey(MediaFormat.KEY_LEVEL)) {
+ int level = format.getInteger(MediaFormat.KEY_LEVEL);
+ if (level <= 0) {
+ throw new IllegalArgumentException("Invalid codec level");
+ }
+ // TODO: Validate the level according to codec type.
+ trackFormat.level = level;
+ }
+
+ return trackFormat;
+ }
+
+ /**
+ * Builder class for {@link VideoTranscodingRequest}.
+ */
+ public static final class Builder extends
+ TranscodingRequest.Builder<VideoTranscodingRequest.Builder> {
+ /**
+ * Desired output video format of the destination file.
+ * <p> If this is null, source file's video track will be passed through and
+ * copied to the destination file.
+ */
+ private @Nullable MediaFormat mVideoTrackFormat = null;
+
+ /**
+ * Desired output audio format of the destination file.
+ * <p> If this is null, source file's audio track will be passed through and copied
+ * to the destination file.
+ */
+ private @Nullable MediaFormat mAudioTrackFormat = null;
+
+ /**
+ * Creates a builder for building {@link VideoTranscodingRequest}s.
+ *
+ * <p> Client could only specify the settings that matters to them, e.g. codec format or
+ * bitrate. And by default, transcoding will preserve the original video's settings
+ * (bitrate, framerate, resolution) if not provided.
+ * <p>Note that some settings may silently fail to apply if the device does not support
+ * them.
+ * @param sourceUri Content uri for the source media file.
+ * @param destinationUri Content uri for the destination media file.
+ * @param videoFormat MediaFormat containing the settings that client wants override in
+ * the original video's video track.
+ * @throws IllegalArgumentException if videoFormat is invalid.
+ */
+ public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri,
+ @NonNull MediaFormat videoFormat) {
+ super(TRANSCODING_TYPE_VIDEO, sourceUri, destinationUri);
+ setVideoTrackFormat(videoFormat);
+ }
+
+ @Override
+ @NonNull
+ public Builder setClientUid(int uid) {
+ super.setClientUid(uid);
+ return self();
+ }
+
+ @Override
+ @NonNull
+ public Builder setClientPid(int pid) {
+ super.setClientPid(pid);
+ return self();
+ }
+
+ @Override
+ @NonNull
+ public Builder setSourceFileDescriptor(ParcelFileDescriptor fd) {
+ super.setSourceFileDescriptor(fd);
+ return self();
+ }
+
+ @Override
+ @NonNull
+ public Builder setDestinationFileDescriptor(ParcelFileDescriptor fd) {
+ super.setDestinationFileDescriptor(fd);
+ return self();
+ }
+
+ private void setVideoTrackFormat(@NonNull MediaFormat videoFormat) {
+ if (videoFormat == null) {
+ throw new IllegalArgumentException("videoFormat must not be null");
+ }
+
+ // Check if the MediaFormat is for video by looking at the MIME type.
+ String mime = videoFormat.containsKey(MediaFormat.KEY_MIME)
+ ? videoFormat.getString(MediaFormat.KEY_MIME) : null;
+ if (mime == null || !mime.startsWith("video/")) {
+ throw new IllegalArgumentException("Invalid video format: wrong mime type");
+ }
+
+ mVideoTrackFormat = videoFormat;
+ }
+
+ /**
+ * @return a new {@link TranscodingRequest} instance successfully initialized
+ * with all the parameters set on this <code>Builder</code>.
+ * @throws UnsupportedOperationException if the parameters set on the
+ * <code>Builder</code> were incompatible, or
+ * if they are not supported by the
+ * device.
+ */
+ @NonNull
+ public VideoTranscodingRequest build() {
+ return new VideoTranscodingRequest(this);
+ }
+
+ @Override
+ VideoTranscodingRequest.Builder self() {
+ return this;
+ }
+ }
+ }
+
+ /**
* Handle to an enqueued transcoding operation. An instance of this class represents a single
* enqueued transcoding operation. The caller can use that instance to query the status or
* progress, and to get the result once the operation has completed.
diff --git a/api/Android.bp b/api/Android.bp
index 1fdf177..4baf7c1 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -351,6 +351,7 @@
genrule {
name: "services-system-server-current.txt",
srcs: [
+ ":service-media-s{.system-server.api.txt}",
":service-permission{.system-server.api.txt}",
":non-updatable-system-server-current.txt",
],
@@ -374,6 +375,7 @@
genrule {
name: "services-system-server-removed.txt",
srcs: [
+ ":service-media-s{.system-server.removed-api.txt}",
":service-permission{.system-server.removed-api.txt}",
":non-updatable-system-server-removed.txt",
],
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index 4e5b3ba..0eff83c 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -22,13 +22,9 @@
multilib: {
lib32: {
- // TODO(b/142944043): Remove version script when libsigchain is a DSO.
- version_script: "version-script32.txt",
suffix: "32",
},
lib64: {
- // TODO(b/142944043): Remove version script when libsigchain is a DSO.
- version_script: "version-script64.txt",
suffix: "64",
},
},
@@ -43,6 +39,13 @@
"libhidlbase",
"liblog",
"libnativeloader",
+
+ // Even though app_process doesn't call into libsigchain, we need to
+ // make sure it's in the DT list of app_process, as we want all code
+ // in app_process and the libraries it loads to find libsigchain
+ // symbols before libc symbols.
+ "libsigchain",
+
"libutils",
// This is a list of libraries that need to be included in order to avoid
@@ -52,8 +55,6 @@
"libwilhelm",
],
- whole_static_libs: ["libsigchain"],
-
compile_multilib: "both",
cflags: [
diff --git a/cmds/app_process/version-script32.txt b/cmds/app_process/version-script32.txt
deleted file mode 100644
index 70810e0..0000000
--- a/cmds/app_process/version-script32.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-global:
- EnsureFrontOfChain;
- AddSpecialSignalHandlerFn;
- RemoveSpecialSignalHandlerFn;
- SkipAddSignalHandler;
- bsd_signal;
- sigaction;
- sigaction64;
- signal;
- sigprocmask;
- sigprocmask64;
-local:
- *;
-};
diff --git a/cmds/app_process/version-script64.txt b/cmds/app_process/version-script64.txt
deleted file mode 100644
index 7bcd76b..0000000
--- a/cmds/app_process/version-script64.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-global:
- EnsureFrontOfChain;
- AddSpecialSignalHandlerFn;
- RemoveSpecialSignalHandlerFn;
- SkipAddSignalHandler;
- sigaction;
- sigaction64;
- signal;
- sigprocmask;
- sigprocmask64;
-local:
- *;
-};
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 260c8a4..f5bee6c 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -258,7 +258,7 @@
public void runDisableAppDataIsolation() throws RemoteException {
if (!SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
throw new IllegalStateException("Storage app data isolation is not enabled.");
}
final String pkgName = nextArg();
diff --git a/core/api/current.txt b/core/api/current.txt
index 5a3125a..a00eae9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -99,6 +99,7 @@
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
+ field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
@@ -1394,6 +1395,7 @@
field public static final int supportsRtl = 16843695; // 0x10103af
field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
field public static final int supportsUploading = 16843419; // 0x101029b
+ field public static final int suppressesSpellChecker = 16844354; // 0x1010642
field public static final int switchMinWidth = 16843632; // 0x1010370
field public static final int switchPadding = 16843633; // 0x1010371
field public static final int switchPreferenceStyle = 16843629; // 0x101036d
@@ -1678,7 +1680,7 @@
field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
field public static final int windowSplashScreenBackground = 16844332; // 0x101062c
field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f
- field public static final int windowSplashscreenContent = 16844132; // 0x1010564
+ field @Deprecated public static final int windowSplashscreenContent = 16844132; // 0x1010564
field @Deprecated public static final int windowSwipeToDismiss = 16843763; // 0x10103f3
field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c
field public static final int windowTitleSize = 16842842; // 0x101005a
@@ -3026,6 +3028,7 @@
field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12; // 0xc
field public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; // 0xd
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
+ field public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15; // 0xf
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
field public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10; // 0xa
field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
@@ -4349,8 +4352,8 @@
method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
- method public void setExact(int, long, android.app.PendingIntent);
- method public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
+ method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, android.app.PendingIntent);
+ method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
method public void setRepeating(int, long, long, android.app.PendingIntent);
@@ -6134,13 +6137,13 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
- field public static final String EDIT_CONVERSATION = "convo";
+ field public static final String EDIT_CONVERSATION = "conversation";
field public static final String EDIT_IMPORTANCE = "importance";
field public static final String EDIT_LAUNCHER = "launcher";
field public static final String EDIT_LOCKED_DEVICE = "locked";
field public static final String EDIT_SOUND = "sound";
field public static final String EDIT_VIBRATION = "vibration";
- field public static final String EDIT_ZEN = "dnd";
+ field public static final String EDIT_ZEN = "zen";
}
public final class NotificationChannelGroup implements android.os.Parcelable {
@@ -8254,13 +8257,13 @@
}
public class NetworkStatsManager {
- method public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException;
- method public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException;
- method public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException;
- method public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
- method public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats queryDetails(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUid(int, String, long, long, int) throws java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTag(int, String, long, long, int, int) throws java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(int, String, long, long, int, int, int) throws java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats querySummary(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
+ method @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForUser(int, String, long, long) throws android.os.RemoteException, java.lang.SecurityException;
method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback);
method public void registerUsageCallback(int, String, long, android.app.usage.NetworkStatsManager.UsageCallback, @Nullable android.os.Handler);
method public void unregisterUsageCallback(android.app.usage.NetworkStatsManager.UsageCallback);
@@ -9908,6 +9911,7 @@
method public String getHtmlText();
method public android.content.Intent getIntent();
method public CharSequence getText();
+ method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
method public android.net.Uri getUri();
}
@@ -9917,6 +9921,8 @@
method public static boolean compareMimeTypes(String, String);
method public int describeContents();
method public String[] filterMimeTypes(String);
+ method public int getClassificationStatus();
+ method @FloatRange(from=0.0, to=1.0) public float getConfidenceScore(@NonNull String);
method public android.os.PersistableBundle getExtras();
method public CharSequence getLabel();
method public String getMimeType(int);
@@ -9926,6 +9932,9 @@
method public boolean isStyledText();
method public void setExtras(android.os.PersistableBundle);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CLASSIFICATION_COMPLETE = 3; // 0x3
+ field public static final int CLASSIFICATION_NOT_COMPLETE = 1; // 0x1
+ field public static final int CLASSIFICATION_NOT_PERFORMED = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
field public static final String MIMETYPE_TEXT_HTML = "text/html";
field public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
@@ -10501,6 +10510,7 @@
field public static final String DEVICE_POLICY_SERVICE = "device_policy";
field public static final String DISPLAY_HASH_SERVICE = "display_hash";
field public static final String DISPLAY_SERVICE = "display";
+ field public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
field public static final String DOWNLOAD_SERVICE = "download";
field public static final String DROPBOX_SERVICE = "dropbox";
field public static final String EUICC_SERVICE = "euicc";
@@ -10952,6 +10962,7 @@
field public static final String ACTION_MANAGED_PROFILE_UNLOCKED = "android.intent.action.MANAGED_PROFILE_UNLOCKED";
field public static final String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
field public static final String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
+ field public static final String ACTION_MANAGE_UNUSED_APPS = "android.intent.action.MANAGE_UNUSED_APPS";
field public static final String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
field public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
field public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING";
@@ -18427,12 +18438,12 @@
}
public class MultiResolutionImageReader implements java.lang.AutoCloseable {
+ ctor public MultiResolutionImageReader(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int);
method public void close();
method protected void finalize();
method public void flush();
method @NonNull public android.hardware.camera2.params.MultiResolutionStreamInfo getStreamInfoForImageReader(@NonNull android.media.ImageReader);
method @NonNull public android.view.Surface getSurface();
- method @NonNull public static android.hardware.camera2.MultiResolutionImageReader newInstance(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int);
method public void setOnImageAvailableListener(@Nullable android.media.ImageReader.OnImageAvailableListener, @Nullable java.util.concurrent.Executor);
}
@@ -18543,10 +18554,10 @@
}
public class MultiResolutionStreamInfo {
- ctor public MultiResolutionStreamInfo(int, int, @NonNull String);
- method public int getHeight();
+ ctor public MultiResolutionStreamInfo(@IntRange(from=1) int, @IntRange(from=1) int, @NonNull String);
+ method @IntRange(from=1) public int getHeight();
method @NonNull public String getPhysicalCameraId();
- method public int getWidth();
+ method @IntRange(from=1) public int getWidth();
}
public final class OisSample {
@@ -20667,6 +20678,7 @@
method public android.media.AudioDeviceInfo getPreferredDevice();
method public android.media.AudioDeviceInfo getRoutedDevice();
method public int getSampleRate();
+ method @IntRange(from=1) public int getStartThresholdInFrames();
method public int getState();
method public int getStreamType();
method public boolean getTimestamp(android.media.AudioTimestamp);
@@ -20697,6 +20709,7 @@
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
method public int setPresentation(@NonNull android.media.AudioPresentation);
+ method @IntRange(from=1) public int setStartThresholdInFrames(@IntRange(from=1) int);
method @Deprecated protected void setState(int);
method @Deprecated public int setStereoVolume(float, float);
method public int setVolume(float);
@@ -21889,6 +21902,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 @NonNull public byte[] getPropertyByteArray(String);
method @NonNull public String getPropertyString(@NonNull String);
method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest();
@@ -22306,6 +22320,8 @@
field public static final String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
field public static final String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
field public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg";
+ field public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1";
+ field public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1";
field public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
field public static final String MIMETYPE_AUDIO_OPUS = "audio/opus";
field public static final String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
@@ -26810,17 +26826,14 @@
public final class VcnGatewayConnectionConfig {
method @NonNull public int[] getExposedCapabilities();
method @IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) public int getMaxMtu();
- method @NonNull public int[] getRequiredUnderlyingCapabilities();
method @NonNull public long[] getRetryInterval();
}
public static final class VcnGatewayConnectionConfig.Builder {
ctor public VcnGatewayConnectionConfig.Builder(@NonNull android.net.vcn.VcnControlPlaneConfig);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addRequiredUnderlyingCapability(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
- method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeRequiredUnderlyingCapability(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=android.net.vcn.VcnGatewayConnectionConfig.MIN_MTU_V6) int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryInterval(@NonNull long[]);
}
@@ -34817,6 +34830,7 @@
field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+ field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
@@ -36862,11 +36876,12 @@
method @NonNull public static android.content.Intent createInstallIntent();
method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy);
method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException;
- method @NonNull public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException;
+ method @NonNull @WorkerThread public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException;
method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException;
method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String);
- method public static boolean isCredentialManagementApp(@NonNull android.content.Context);
+ method @WorkerThread public static boolean isCredentialManagementApp(@NonNull android.content.Context);
method public static boolean isKeyAlgorithmSupported(@NonNull String);
+ method @RequiresPermission(value="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP", conditional=true) @WorkerThread public static boolean removeCredentialManagementApp(@NonNull android.content.Context);
field public static final String ACTION_KEYCHAIN_CHANGED = "android.security.action.KEYCHAIN_CHANGED";
field public static final String ACTION_KEY_ACCESS_CHANGED = "android.security.action.KEY_ACCESS_CHANGED";
field @Deprecated public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED";
@@ -37386,12 +37401,15 @@
method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender);
method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String);
method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
}
public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -37492,6 +37510,7 @@
method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation);
method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle);
method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...);
method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int);
@@ -37519,6 +37538,7 @@
public final class InlinePresentation implements android.os.Parcelable {
ctor public InlinePresentation(@NonNull android.app.slice.Slice, @NonNull android.widget.inline.InlinePresentationSpec, boolean);
+ method @NonNull public static android.service.autofill.InlinePresentation createTooltipPresentation(@NonNull android.app.slice.Slice, @NonNull android.widget.inline.InlinePresentationSpec);
method public int describeContents();
method @NonNull public android.widget.inline.InlinePresentationSpec getInlinePresentationSpec();
method @NonNull public android.app.slice.Slice getSlice();
@@ -40299,7 +40319,6 @@
method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
method public void notifyConfigChangedForSubId(int);
field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
- field public static final int CARRIER_NR_AVAILABILITY_NONE = 0; // 0x0
field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0; // 0x0
@@ -40364,7 +40383,8 @@
field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int";
field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
- field public static final String KEY_CARRIER_NR_AVAILABILITY_INT = "carrier_nr_availability_int";
+ field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
+ field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
@@ -40514,6 +40534,7 @@
field public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool";
field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool";
field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
+ field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -40559,6 +40580,7 @@
field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
+ field public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL = "vt_upgrade_supported_for_downgraded_rtt_call";
field public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
field public static final String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
@@ -40606,7 +40628,10 @@
public static final class CarrierConfigManager.ImsServiceEntitlement {
field public static final String KEY_ENTITLEMENT_SERVER_URL_STRING = "imsserviceentitlement.entitlement_server_url_string";
+ field public static final String KEY_FCM_SENDER_ID_STRING = "imsserviceentitlement.fcm_sender_id_string";
+ field public static final String KEY_IMS_PROVISIONING_BOOL = "imsserviceentitlement.ims_provisioning_bool";
field public static final String KEY_PREFIX = "imsserviceentitlement.";
+ field public static final String KEY_SHOW_VOWIFI_WEBVIEW_BOOL = "imsserviceentitlement.show_vowifi_webview_bool";
}
public static final class CarrierConfigManager.Iwlan {
@@ -42015,6 +42040,7 @@
method public static int getDefaultSmsSubscriptionId();
method public static int getDefaultSubscriptionId();
method public static int getDefaultVoiceSubscriptionId();
+ method public int getDeviceToDeviceStatusSharing(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);
@@ -42027,6 +42053,7 @@
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 boolean setOpportunistic(boolean, int);
method public void setSubscriptionOverrideCongested(int, boolean, long);
method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long);
@@ -42038,6 +42065,11 @@
field public static final String ACTION_DEFAULT_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED";
field public static final String ACTION_MANAGE_SUBSCRIPTION_PLANS = "android.telephony.action.MANAGE_SUBSCRIPTION_PLANS";
field public static final String ACTION_REFRESH_SUBSCRIPTION_PLANS = "android.telephony.action.REFRESH_SUBSCRIPTION_PLANS";
+ 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 String D2D_STATUS_SHARING = "d2d_sharing_status";
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
@@ -42160,6 +42192,10 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onMessageWaitingIndicatorChanged(boolean);
}
+ public static interface TelephonyCallback.PhysicalChannelConfigListener {
+ method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
+ }
+
public static interface TelephonyCallback.PreciseDataConnectionStateListener {
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
}
@@ -49870,6 +49906,7 @@
public interface WindowManager extends android.view.ViewManager {
method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method public default void addCrossWindowBlurEnabledListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @NonNull public default android.view.WindowMetrics getCurrentWindowMetrics();
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
@@ -51306,6 +51343,7 @@
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getHostPackageName();
method @NonNull public java.util.List<android.widget.inline.InlinePresentationSpec> getInlinePresentationSpecs();
+ method @Nullable public android.widget.inline.InlinePresentationSpec getInlineTooltipPresentationSpec();
method public int getMaxSuggestionCount();
method @NonNull public android.os.LocaleList getSupportedLocales();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -51317,9 +51355,12 @@
ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
+ method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
+ method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
+ method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
}
@@ -51451,7 +51492,6 @@
method public int describeContents();
method public void dump(android.util.Printer, String);
method public android.content.ComponentName getComponent();
- method public int getConfigChanges();
method public String getId();
method public int getIsDefaultResourceId();
method public String getPackageName();
@@ -51462,6 +51502,7 @@
method public int getSubtypeCount();
method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
method public CharSequence loadLabel(android.content.pm.PackageManager);
+ method public boolean suppressesSpellChecker();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
}
@@ -51483,6 +51524,7 @@
method public boolean isActive(android.view.View);
method public boolean isActive();
method public boolean isFullscreenMode();
+ method public boolean isInputMethodSuppressingSpellChecker();
method @Deprecated public boolean isWatchingCursor(android.view.View);
method public void restartInput(android.view.View);
method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
@@ -51903,6 +51945,7 @@
field public static final String TYPE_PHONE = "phone";
field public static final String TYPE_UNKNOWN = "";
field public static final String TYPE_URL = "url";
+ field public static final String WIDGET_TYPE_CLIPBOARD = "clipboard";
field public static final String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
field public static final String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
field public static final String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
@@ -52274,10 +52317,10 @@
public final class TextServicesManager {
method @Nullable public android.view.textservice.SpellCheckerInfo getCurrentSpellCheckerInfo();
- method @Nullable public java.util.List<android.view.textservice.SpellCheckerInfo> getEnabledSpellCheckerInfos();
+ method @NonNull public java.util.List<android.view.textservice.SpellCheckerInfo> getEnabledSpellCheckerInfos();
method public boolean isSpellCheckerEnabled();
method @Nullable public android.view.textservice.SpellCheckerSession newSpellCheckerSession(@Nullable android.os.Bundle, @Nullable java.util.Locale, @NonNull android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener, boolean);
- method @Nullable public android.view.textservice.SpellCheckerSession newSpellCheckerSession(@Nullable android.os.Bundle, @Nullable java.util.Locale, @NonNull android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener, boolean, int);
+ method @Nullable public android.view.textservice.SpellCheckerSession newSpellCheckerSession(@Nullable java.util.Locale, boolean, int, @Nullable android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener);
}
}
@@ -52379,6 +52422,17 @@
method @Nullable @WorkerThread public android.view.translation.TranslationResponse translate(@NonNull android.view.translation.TranslationRequest);
}
+ public final class UiTranslationManager {
+ method public void registerUiTranslationStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.translation.UiTranslationStateCallback);
+ method public void unregisterUiTranslationStateCallback(@NonNull android.view.translation.UiTranslationStateCallback);
+ }
+
+ public interface UiTranslationStateCallback {
+ method public void onFinished();
+ method public void onPaused();
+ method public void onStarted(@NonNull String, @NonNull String);
+ }
+
public final class ViewTranslationRequest implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.view.autofill.AutofillId getAutofillId();
@@ -55017,6 +55071,7 @@
method public void setRelativeScrollPosition(@IdRes int, int);
method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+ method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
method public void setScrollPosition(@IdRes int, int);
method public void setShort(@IdRes int, String, short);
method public void setString(@IdRes int, String, String);
@@ -55056,6 +55111,25 @@
ctor public RemoteViews.ActionException(String);
}
+ public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getItemCount();
+ method public long getItemId(int);
+ method @NonNull public android.widget.RemoteViews getItemView(int);
+ method public int getViewTypeCount();
+ method public boolean hasStableIds();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews.RemoteCollectionItems> CREATOR;
+ }
+
+ public static final class RemoteViews.RemoteCollectionItems.Builder {
+ ctor public RemoteViews.RemoteCollectionItems.Builder();
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder addItem(long, @NonNull android.widget.RemoteViews);
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems build();
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setHasStableIds(boolean);
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setViewTypeCount(int);
+ }
+
public static class RemoteViews.RemoteResponse {
ctor public RemoteViews.RemoteResponse();
method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(@IdRes int, @NonNull String);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 18b0a43..e3b7c88 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -55,8 +55,13 @@
package android.content {
+ public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
+ method @NonNull public static android.net.Uri createContentUriAsUser(@NonNull android.net.Uri, @NonNull android.os.UserHandle);
+ }
+
public abstract class Context {
method @NonNull public android.os.UserHandle getUser();
+ field public static final String TEST_NETWORK_SERVICE = "test_network";
}
public class Intent implements java.lang.Cloneable android.os.Parcelable {
@@ -187,6 +192,29 @@
method public int getResourceId();
}
+ public class NetworkPolicyManager {
+ method @NonNull public static String blockedReasonsToString(int);
+ method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getMultipathPreference(@NonNull android.net.Network);
+ method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getRestrictBackgroundStatus(int);
+ method public static boolean isUidBlocked(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean);
+ 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 {
+ method public default void onUidBlockedReasonChanged(int, int);
+ }
+
public final class NetworkStateSnapshot implements android.os.Parcelable {
ctor public NetworkStateSnapshot(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @Nullable String, int);
method public int describeContents();
@@ -203,6 +231,16 @@
method @Nullable public byte[] getWatchlistConfigHash();
}
+ public class PacProxyManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void addPacProxyInstalledListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.PacProxyManager.PacProxyInstalledListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void removePacProxyInstalledListener(@NonNull android.net.PacProxyManager.PacProxyInstalledListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setCurrentProxyScriptUrl(@Nullable android.net.ProxyInfo);
+ }
+
+ public static interface PacProxyManager.PacProxyInstalledListener {
+ method public void onPacProxyInstalled(@Nullable android.net.Network, @NonNull android.net.ProxyInfo);
+ }
+
public final class Proxy {
method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
}
@@ -220,6 +258,7 @@
public class VpnManager {
field @Deprecated public static final int TYPE_VPN_LEGACY = 3; // 0x3
field public static final int TYPE_VPN_NONE = -1; // 0xffffffff
+ field public static final int TYPE_VPN_OEM = 4; // 0x4
field public static final int TYPE_VPN_PLATFORM = 2; // 0x2
field public static final int TYPE_VPN_SERVICE = 1; // 0x1
}
@@ -244,6 +283,10 @@
method public default int getStability();
}
+ public class Process {
+ field public static final int VPN_UID = 1016; // 0x3f8
+ }
+
public class StatsServiceManager {
method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsCompanionServiceRegisterer();
method @NonNull public android.os.StatsServiceManager.ServiceRegisterer getStatsManagerServiceRegisterer();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6019ab5..921ef92 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -126,6 +126,7 @@
field public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
+ field public static final String MANAGE_APP_HIBERNATION = "android.permission.MANAGE_APP_HIBERNATION";
field public static final String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS";
field public static final String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
field public static final String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
@@ -136,6 +137,7 @@
field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS";
field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
+ field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
@@ -201,7 +203,6 @@
field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
field public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
- field public static final String READ_NETWORK_DEVICE_CONFIG = "android.permission.READ_NETWORK_DEVICE_CONFIG";
field public static final String READ_NETWORK_USAGE_HISTORY = "android.permission.READ_NETWORK_USAGE_HISTORY";
field public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
field public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA";
@@ -407,7 +408,7 @@
method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean startProfile(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean stopProfile(@NonNull android.os.UserHandle);
- method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull android.os.UserHandle);
}
public static interface ActivityManager.OnUidImportanceListener {
@@ -910,7 +911,6 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
- method public boolean isNetworkSlicingEnabledForUser(@NonNull android.os.UserHandle);
method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
@@ -1695,6 +1695,13 @@
package android.app.time {
+ public final class Capabilities {
+ field public static final int CAPABILITY_NOT_ALLOWED = 20; // 0x14
+ field public static final int CAPABILITY_NOT_APPLICABLE = 30; // 0x1e
+ field public static final int CAPABILITY_NOT_SUPPORTED = 10; // 0xa
+ field public static final int CAPABILITY_POSSESSED = 40; // 0x28
+ }
+
public final class TimeManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void addTimeZoneDetectorListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.time.TimeManager.TimeZoneDetectorListener);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig();
@@ -1711,10 +1718,6 @@
method public int getConfigureAutoDetectionEnabledCapability();
method public int getConfigureGeoDetectionEnabledCapability();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CAPABILITY_NOT_ALLOWED = 20; // 0x14
- field public static final int CAPABILITY_NOT_APPLICABLE = 30; // 0x1e
- field public static final int CAPABILITY_NOT_SUPPORTED = 10; // 0xa
- field public static final int CAPABILITY_POSSESSED = 40; // 0x28
field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilities> CREATOR;
}
@@ -1795,6 +1798,7 @@
public final class UsageStats implements android.os.Parcelable {
method public int getAppLaunchCount();
+ method public long getLastTimeComponentUsed();
}
public final class UsageStatsManager {
@@ -1828,10 +1832,10 @@
package android.apphibernation {
public final class AppHibernationManager {
- method public boolean isHibernatingForUser(@NonNull String);
- method public boolean isHibernatingGlobally(@NonNull String);
- method public void setHibernatingForUser(@NonNull String, boolean);
- method public void setHibernatingGlobally(@NonNull String, boolean);
+ 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);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingGlobally(@NonNull String, boolean);
}
}
@@ -1888,6 +1892,7 @@
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
@@ -2064,6 +2069,55 @@
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BufferConstraints> CREATOR;
}
+ public final class OobData implements android.os.Parcelable {
+ method @NonNull public static android.bluetooth.OobData.ClassicBuilder createClassicBuilder(@NonNull byte[], @NonNull byte[], @NonNull byte[]);
+ method @NonNull public static android.bluetooth.OobData.LeBuilder createLeBuilder(@NonNull byte[], @NonNull byte[], int);
+ method @NonNull public byte[] getClassOfDevice();
+ method @NonNull public byte[] getClassicLength();
+ method @NonNull public byte[] getConfirmationHash();
+ method @NonNull public byte[] getDeviceAddressWithType();
+ method @Nullable public byte[] getDeviceName();
+ method @Nullable public byte[] getLeAppearance();
+ method @NonNull public int getLeDeviceRole();
+ method @NonNull public int getLeFlags();
+ method @Nullable public byte[] getLeTemporaryKey();
+ method @NonNull public byte[] getRandomizerHash();
+ field public static final int CLASS_OF_DEVICE_OCTETS = 3; // 0x3
+ field public static final int CONFIRMATION_OCTETS = 16; // 0x10
+ field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.OobData> CREATOR;
+ field public static final int DEVICE_ADDRESS_OCTETS = 7; // 0x7
+ field public static final int LE_APPEARANCE_OCTETS = 2; // 0x2
+ field public static final int LE_DEVICE_FLAG_OCTETS = 1; // 0x1
+ field public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 3; // 0x3
+ field public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 2; // 0x2
+ field public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 1; // 0x1
+ field public static final int LE_DEVICE_ROLE_OCTETS = 1; // 0x1
+ field public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0; // 0x0
+ field public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 2; // 0x2
+ field public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 1; // 0x1
+ field public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0; // 0x0
+ field public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 3; // 0x3
+ field public static final int LE_FLAG_SIMULTANEOUS_HOST = 4; // 0x4
+ field public static final int LE_TK_OCTETS = 16; // 0x10
+ field public static final int OOB_LENGTH_OCTETS = 2; // 0x2
+ field public static final int RANDOMIZER_OCTETS = 16; // 0x10
+ }
+
+ public static final class OobData.ClassicBuilder {
+ method @NonNull public android.bluetooth.OobData build();
+ method @NonNull public android.bluetooth.OobData.ClassicBuilder setClassOfDevice(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.ClassicBuilder setDeviceName(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.ClassicBuilder setRandomizerHash(@NonNull byte[]);
+ }
+
+ public static final class OobData.LeBuilder {
+ method @NonNull public android.bluetooth.OobData build();
+ method @NonNull public android.bluetooth.OobData.LeBuilder setDeviceName(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.LeBuilder setLeFlags(int);
+ method @NonNull public android.bluetooth.OobData.LeBuilder setLeTemporaryKey(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.LeBuilder setRandomizerHash(@NonNull byte[]);
+ }
+
}
package android.bluetooth.le {
@@ -2162,7 +2216,6 @@
field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
- field public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
field public static final String FONT_SERVICE = "font";
@@ -2174,7 +2227,6 @@
field public static final String OEM_LOCK_SERVICE = "oem_lock";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
- field public static final String POWER_EXEMPTION_SERVICE = "power_exemption";
field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness";
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String SEARCH_UI_SERVICE = "search_ui";
@@ -2241,6 +2293,7 @@
field @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES) public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_ONGOING_PERMISSION_USAGE = "android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE";
field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
+ field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_PERMISSION_HISTORY = "android.intent.action.REVIEW_PERMISSION_HISTORY";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
@@ -2787,21 +2840,29 @@
method @NonNull public String getPackageName();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR;
+ field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400
+ field public static final int STATE_MODIFIABLE_UNVERIFIED = 3; // 0x3
+ field public static final int STATE_MODIFIABLE_VERIFIED = 4; // 0x4
+ field public static final int STATE_NO_RESPONSE = 0; // 0x0
+ field public static final int STATE_SUCCESS = 1; // 0x1
+ field public static final int STATE_UNMODIFIABLE = 2; // 0x2
}
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 @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
- method public static boolean isStateModifiable(int);
- method public static boolean isStateVerified(int);
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 void 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 void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.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;
+ 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 String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
- field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400
- field public static final int STATE_NO_RESPONSE = 0; // 0x0
- field public static final int STATE_SUCCESS = 1; // 0x1
+ field public static final int STATUS_OK = 0; // 0x0
}
public final class DomainVerificationRequest implements android.os.Parcelable {
@@ -3022,6 +3083,7 @@
field public final float reduceBrightColorsOffset;
field public final int reduceBrightColorsStrength;
field public final long timeStamp;
+ field @NonNull public final String uniqueDisplayId;
}
public final class BrightnessConfiguration implements android.os.Parcelable {
@@ -3597,7 +3659,6 @@
field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2
field public static final int RESULT_FAILED_BUSY = 4; // 0x4
field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8
- field public static final int RESULT_FAILED_PERMISSION_DENIED = 9; // 0x9
field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7
field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6
field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3
@@ -4999,8 +5060,8 @@
method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
- method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
- method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
@@ -5146,6 +5207,21 @@
field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
}
+ public final class MediaRouter2 {
+ method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
+ method @Nullable public String getClientPackageName();
+ method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
+ method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
+ method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
+ method public void startScan();
+ method public void stopScan();
+ method public void transfer(@NonNull android.media.MediaRouter2.RoutingController, @NonNull android.media.MediaRoute2Info);
+ }
+
+ public abstract static class MediaRouter2.RouteCallback {
+ method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>);
+ }
+
public class PlayerProxy {
method public void pause();
method public void setPan(float);
@@ -7521,12 +7597,12 @@
package android.net.vcn {
public class VcnManager {
- method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyListener);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void addVcnNetworkPolicyChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.vcn.VcnNetworkPolicyResult applyVcnNetworkPolicy(@NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties);
- method public void removeVcnNetworkPolicyListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyListener);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void removeVcnNetworkPolicyChangeListener(@NonNull android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener);
}
- public static interface VcnManager.VcnNetworkPolicyListener {
+ public static interface VcnManager.VcnNetworkPolicyChangeListener {
method public void onPolicyChanged();
}
@@ -8184,6 +8260,7 @@
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
+ field public static final int REASON_ACCOUNT_TRANSFER = 104; // 0x68
field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67
field public static final int REASON_GEOFENCING = 100; // 0x64
field public static final int REASON_OTHER = 1; // 0x1
@@ -8224,25 +8301,25 @@
field public static final int USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS = 1; // 0x1
}
- public class PowerWhitelistManager {
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long, int, @Nullable String);
+ @Deprecated public class PowerWhitelistManager {
+ method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long, int, @Nullable String);
method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long);
method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, int, @Nullable 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
- field public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67
- field public static final int REASON_GEOFENCING = 100; // 0x64
- field public static final int REASON_OTHER = 1; // 0x1
- field public static final int REASON_PUSH_MESSAGING = 101; // 0x65
- field public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66
- field public static final int REASON_UNKNOWN = 0; // 0x0
- field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
- field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
+ method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, int, @Nullable String);
+ field @Deprecated public static final int EVENT_MMS = 2; // 0x2
+ field @Deprecated public static final int EVENT_SMS = 1; // 0x1
+ field @Deprecated public static final int EVENT_UNSPECIFIED = 0; // 0x0
+ field @Deprecated public static final int REASON_ACTIVITY_RECOGNITION = 103; // 0x67
+ field @Deprecated public static final int REASON_GEOFENCING = 100; // 0x64
+ field @Deprecated public static final int REASON_OTHER = 1; // 0x1
+ field @Deprecated public static final int REASON_PUSH_MESSAGING = 101; // 0x65
+ field @Deprecated public static final int REASON_PUSH_MESSAGING_OVER_QUOTA = 102; // 0x66
+ field @Deprecated public static final int REASON_UNKNOWN = 0; // 0x0
+ field @Deprecated public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
+ field @Deprecated public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
}
public class RecoverySystem {
@@ -9629,10 +9706,28 @@
package android.service.displayhash {
+ public final class DisplayHashParams implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.util.Size getBufferSize();
+ method public boolean isBufferScaleWithFiltering();
+ method public boolean isGrayscaleBuffer();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.displayhash.DisplayHashParams> CREATOR;
+ }
+
+ public static final class DisplayHashParams.Builder {
+ ctor public DisplayHashParams.Builder();
+ method @NonNull public android.service.displayhash.DisplayHashParams build();
+ method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean);
+ method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferSize(int, int);
+ method @NonNull public android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean);
+ }
+
public abstract class DisplayHasherService extends android.app.Service {
ctor public DisplayHasherService();
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Nullable public abstract void onGenerateDisplayHash(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String, @NonNull android.view.displayhash.DisplayHashResultCallback);
+ method @NonNull public abstract java.util.Map<java.lang.String,android.service.displayhash.DisplayHashParams> onGetDisplayHashAlgorithms();
method @Nullable public abstract android.view.displayhash.VerifiedDisplayHash onVerifyDisplayHash(@NonNull byte[], @NonNull android.view.displayhash.DisplayHash);
field public static final String SERVICE_INTERFACE = "android.service.displayhash.DisplayHasherService";
}
@@ -10262,7 +10357,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 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.Bundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
}
@@ -10363,14 +10458,36 @@
public abstract class CallDiagnosticService extends android.app.Service {
ctor public CallDiagnosticService();
+ method @NonNull public java.util.concurrent.Executor getExecutor();
method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onBluetoothCallQualityReportReceived(@NonNull android.telecom.BluetoothCallQualityReport);
method public abstract void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState);
- method @NonNull public abstract android.telecom.DiagnosticCall onInitializeDiagnosticCall(@NonNull android.telecom.Call.Details);
- method public abstract void onRemoveDiagnosticCall(@NonNull android.telecom.DiagnosticCall);
+ method @NonNull public abstract android.telecom.CallDiagnostics onInitializeCallDiagnostics(@NonNull android.telecom.Call.Details);
+ method public abstract void onRemoveCallDiagnostics(@NonNull android.telecom.CallDiagnostics);
field public static final String SERVICE_INTERFACE = "android.telecom.CallDiagnosticService";
}
+ public abstract class CallDiagnostics {
+ ctor public CallDiagnostics();
+ method public final void clearDiagnosticMessage(int);
+ method public final void displayDiagnosticMessage(int, @NonNull CharSequence);
+ method public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details);
+ method @Nullable public abstract CharSequence onCallDisconnected(int, int);
+ method @Nullable public abstract CharSequence onCallDisconnected(@NonNull android.telephony.ims.ImsReasonInfo);
+ method public abstract void onCallQualityReceived(@NonNull android.telephony.CallQuality);
+ method public abstract void onReceiveDeviceToDeviceMessage(int, int);
+ method public final void sendDeviceToDeviceMessage(int, int);
+ field public static final int BATTERY_STATE_CHARGING = 3; // 0x3
+ field public static final int BATTERY_STATE_GOOD = 2; // 0x2
+ field public static final int BATTERY_STATE_LOW = 1; // 0x1
+ field public static final int COVERAGE_GOOD = 2; // 0x2
+ field public static final int COVERAGE_POOR = 1; // 0x1
+ field public static final int MESSAGE_CALL_AUDIO_CODEC = 2; // 0x2
+ field public static final int MESSAGE_CALL_NETWORK_TYPE = 1; // 0x1
+ field public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; // 0x3
+ field public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; // 0x4
+ }
+
public static class CallScreeningService.CallResponse.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public android.telecom.CallScreeningService.CallResponse.Builder setShouldScreenCallViaAudioProcessing(boolean);
}
@@ -10431,32 +10548,8 @@
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
}
- public abstract class DiagnosticCall {
- ctor public DiagnosticCall();
- method public final void clearDiagnosticMessage(int);
- method public final void displayDiagnosticMessage(int, @NonNull CharSequence);
- method @NonNull public android.telecom.Call.Details getCallDetails();
- method public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details);
- method @Nullable public abstract CharSequence onCallDisconnected(int, int);
- method @Nullable public abstract CharSequence onCallDisconnected(@NonNull android.telephony.ims.ImsReasonInfo);
- method public abstract void onCallQualityReceived(@NonNull android.telephony.CallQuality);
- method public abstract void onReceiveDeviceToDeviceMessage(int, int);
- method public final void sendDeviceToDeviceMessage(int, int);
- field public static final int AUDIO_CODEC_AMR_NB = 3; // 0x3
- field public static final int AUDIO_CODEC_AMR_WB = 2; // 0x2
- field public static final int AUDIO_CODEC_EVS = 1; // 0x1
- field public static final int BATTERY_STATE_CHARGING = 3; // 0x3
- field public static final int BATTERY_STATE_GOOD = 2; // 0x2
- field public static final int BATTERY_STATE_LOW = 1; // 0x1
- field public static final int COVERAGE_GOOD = 2; // 0x2
- field public static final int COVERAGE_POOR = 1; // 0x1
- field public static final int MESSAGE_CALL_AUDIO_CODEC = 2; // 0x2
- field public static final int MESSAGE_CALL_NETWORK_TYPE = 1; // 0x1
- field public static final int MESSAGE_DEVICE_BATTERY_STATE = 3; // 0x3
- field public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4; // 0x4
- field public static final int NETWORK_TYPE_IWLAN = 2; // 0x2
- field public static final int NETWORK_TYPE_LTE = 1; // 0x1
- field public static final int NETWORK_TYPE_NR = 3; // 0x3
+ @Deprecated public abstract class DiagnosticCall extends android.telecom.CallDiagnostics {
+ ctor @Deprecated public DiagnosticCall();
}
public abstract class InCallService extends android.app.Service {
@@ -10789,7 +10882,14 @@
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void overrideConfig(int, @Nullable android.os.PersistableBundle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void updateConfigForPhoneId(int, String);
+ field public static final int GBA_DIGEST = 3; // 0x3
+ field public static final int GBA_ME = 1; // 0x1
+ field public static final int GBA_U = 2; // 0x2
field public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
+ field public static final String KEY_GBA_MODE_INT = "gba_mode_int";
+ field public static final String KEY_GBA_UA_SECURITY_ORGANIZATION_INT = "gba_ua_security_organization_int";
+ field public static final String KEY_GBA_UA_SECURITY_PROTOCOL_INT = "gba_ua_security_protocol_int";
+ field public static final String KEY_GBA_UA_TLS_CIPHER_SUITE_INT = "gba_ua_tls_cipher_suite_int";
field public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool";
}
@@ -10947,6 +11047,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ImsiEncryptionInfo> CREATOR;
}
+ public final class LinkCapacityEstimate implements android.os.Parcelable {
+ ctor public LinkCapacityEstimate(int, int, int);
+ method public int describeContents();
+ method public int getDownlinkCapacityKbps();
+ method public int getType();
+ method public int getUplinkCapacityKbps();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;
+ field public static final int INVALID = -1; // 0xffffffff
+ field public static final int LCE_TYPE_COMBINED = 2; // 0x2
+ field public static final int LCE_TYPE_PRIMARY = 0; // 0x0
+ field public static final int LCE_TYPE_SECONDARY = 1; // 0x1
+ }
+
public final class LteVopsSupportInfo implements android.os.Parcelable {
ctor public LteVopsSupportInfo(int, int);
method public int describeContents();
@@ -11459,6 +11572,7 @@
field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_EMERGENCY_NUMBER_LIST_CHANGED = 25; // 0x19
field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; // 0x1c
field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_LEGACY_CALL_STATE_CHANGED = 36; // 0x24
+ field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_LINK_CAPACITY_ESTIMATE_CHANGED = 37; // 0x25
field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; // 0x3
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_OEM_HOOK_RAW = 15; // 0xf
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; // 0x1d
@@ -11478,7 +11592,7 @@
}
public static interface TelephonyCallback.AllowedNetworkTypesListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onAllowedNetworkTypesChanged(@NonNull java.util.Map<java.lang.Integer,java.lang.Long>);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onAllowedNetworkTypesChanged(int, long);
}
public static interface TelephonyCallback.CallAttributesListener {
@@ -11489,6 +11603,10 @@
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onDataEnabledChanged(boolean, int);
}
+ public static interface TelephonyCallback.LinkCapacityEstimateChangedListener {
+ method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onLinkCapacityEstimateChanged(@NonNull java.util.List<android.telephony.LinkCapacityEstimate>);
+ }
+
public static interface TelephonyCallback.OutgoingEmergencyCallListener {
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
}
@@ -11501,10 +11619,6 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void onPhoneCapabilityChanged(@NonNull android.telephony.PhoneCapability);
}
- public static interface TelephonyCallback.PhysicalChannelConfigListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPhysicalChannelConfigChanged(@NonNull java.util.List<android.telephony.PhysicalChannelConfig>);
- }
-
public static interface TelephonyCallback.PreciseCallStateListener {
method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
}
@@ -11560,7 +11674,6 @@
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierBandwidth getCarrierBandwidth();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -11621,7 +11734,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
- method public boolean isNrDualConnectivityEnabled();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -11707,6 +11820,7 @@
field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
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 int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
@@ -11929,7 +12043,7 @@
method public int getPduSessionId();
method public int getProtocolType();
method public long getRetryDurationMillis();
- method @Nullable public android.telephony.data.SliceInfo getSliceInfo();
+ method @Nullable public android.telephony.data.NetworkSliceInfo getSliceInfo();
method @Deprecated public int getSuggestedRetryTime();
method @NonNull public java.util.List<android.telephony.data.TrafficDescriptor> getTrafficDescriptors();
method public void writeToParcel(android.os.Parcel, int);
@@ -11962,10 +12076,10 @@
method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV6(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>);
- method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(int);
+ method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(@IntRange(from=android.telephony.data.DataCallResponse.PDU_SESSION_ID_NOT_SET, to=15) int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setRetryDurationMillis(long);
- method @NonNull public android.telephony.data.DataCallResponse.Builder setSliceInfo(@Nullable android.telephony.data.SliceInfo);
+ method @NonNull public android.telephony.data.DataCallResponse.Builder setSliceInfo(@Nullable android.telephony.data.NetworkSliceInfo);
method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setSuggestedRetryTime(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setTrafficDescriptors(@NonNull java.util.List<android.telephony.data.TrafficDescriptor>);
}
@@ -12038,7 +12152,7 @@
method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback);
- method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @IntRange(from=0, to=15) int, @Nullable android.telephony.data.SliceInfo, @Nullable android.telephony.data.TrafficDescriptor, boolean, @NonNull android.telephony.data.DataServiceCallback);
+ method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @IntRange(from=0, to=15) int, @Nullable android.telephony.data.NetworkSliceInfo, @Nullable android.telephony.data.TrafficDescriptor, boolean, @NonNull android.telephony.data.DataServiceCallback);
}
public class DataServiceCallback {
@@ -12068,6 +12182,32 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR;
}
+ public final class NetworkSliceInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getMappedHplmnSliceDifferentiator();
+ method public int getMappedHplmnSliceServiceType();
+ method @IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getSliceDifferentiator();
+ method public int getSliceServiceType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.NetworkSliceInfo> CREATOR;
+ field public static final int MAX_SLICE_DIFFERENTIATOR = 16777214; // 0xfffffe
+ field public static final int MIN_SLICE_DIFFERENTIATOR = -1; // 0xffffffff
+ field public static final int SLICE_DIFFERENTIATOR_NO_SLICE = -1; // 0xffffffff
+ field public static final int SLICE_SERVICE_TYPE_EMBB = 1; // 0x1
+ field public static final int SLICE_SERVICE_TYPE_MIOT = 3; // 0x3
+ field public static final int SLICE_SERVICE_TYPE_NONE = 0; // 0x0
+ field public static final int SLICE_SERVICE_TYPE_URLLC = 2; // 0x2
+ }
+
+ public static final class NetworkSliceInfo.Builder {
+ ctor public NetworkSliceInfo.Builder();
+ method @NonNull public android.telephony.data.NetworkSliceInfo build();
+ method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setMappedHplmnSliceDifferentiator(@IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) int);
+ method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setMappedHplmnSliceServiceType(int);
+ method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setSliceDifferentiator(@IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) int);
+ method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setSliceServiceType(int);
+ }
+
public abstract class QualifiedNetworksService extends android.app.Service {
ctor public QualifiedNetworksService();
method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
@@ -12082,32 +12222,6 @@
method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
}
- public final class SliceInfo implements android.os.Parcelable {
- method public int describeContents();
- method @IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getMappedHplmnSliceDifferentiator();
- method public int getMappedHplmnSliceServiceType();
- method @IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getSliceDifferentiator();
- method public int getSliceServiceType();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.SliceInfo> CREATOR;
- field public static final int MAX_SLICE_DIFFERENTIATOR = 16777214; // 0xfffffe
- field public static final int MIN_SLICE_DIFFERENTIATOR = -1; // 0xffffffff
- field public static final int SLICE_DIFFERENTIATOR_NO_SLICE = -1; // 0xffffffff
- field public static final int SLICE_SERVICE_TYPE_EMBB = 1; // 0x1
- field public static final int SLICE_SERVICE_TYPE_MIOT = 3; // 0x3
- field public static final int SLICE_SERVICE_TYPE_NONE = 0; // 0x0
- field public static final int SLICE_SERVICE_TYPE_URLLC = 2; // 0x2
- }
-
- public static final class SliceInfo.Builder {
- ctor public SliceInfo.Builder();
- method @NonNull public android.telephony.data.SliceInfo build();
- method @NonNull public android.telephony.data.SliceInfo.Builder setMappedHplmnSliceDifferentiator(@IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) int);
- method @NonNull public android.telephony.data.SliceInfo.Builder setMappedHplmnSliceServiceType(int);
- method @NonNull public android.telephony.data.SliceInfo.Builder setSliceDifferentiator(@IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) int);
- method @NonNull public android.telephony.data.SliceInfo.Builder setSliceServiceType(int);
- }
-
public final class ThrottleStatus implements android.os.Parcelable {
method public int describeContents();
method public int getApnType();
@@ -13039,6 +13153,7 @@
method public void onAutoConfigurationErrorReceived(int, @NonNull String);
method public void onConfigurationChanged(@NonNull byte[]);
method public void onConfigurationReset();
+ method public void onPreProvisioningReceived(@NonNull byte[]);
method public void onRemoved();
}
@@ -13508,6 +13623,7 @@
method public int getConfigInt(int);
method public String getConfigString(int);
method public final void notifyAutoConfigurationErrorReceived(int, @NonNull String);
+ method public final void notifyPreProvisioningReceived(@NonNull byte[]);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1713e2d..a97c3ae 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -28,6 +28,7 @@
field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
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";
@@ -103,7 +104,9 @@
method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
+ 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
@@ -426,6 +429,7 @@
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
+ field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
@@ -699,7 +703,8 @@
field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
field public static final String DREAM_SERVICE = "dream";
field public static final String FONT_SERVICE = "font";
- field public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
+ field public static final String POWER_EXEMPTION_SERVICE = "power_exemption";
+ field @Deprecated public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
field public static final String TEST_NETWORK_SERVICE = "test_network";
}
@@ -707,6 +712,10 @@
method public int getDisplayId();
}
+ public class Intent implements java.lang.Cloneable android.os.Parcelable {
+ field public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED";
+ }
+
public class SyncAdapterType implements android.os.Parcelable {
method @Nullable public String getPackageName();
}
@@ -728,6 +737,11 @@
method public static boolean isTranslucentOrFloating(android.content.res.TypedArray);
field public static final long FORCE_NON_RESIZE_APP = 181136395L; // 0xacbec0bL
field public static final long FORCE_RESIZE_APP = 174042936L; // 0xa5faf38L
+ field public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // 0xa5faf64L
+ field public static final long OVERRIDE_MIN_ASPECT_RATIO_LARGE = 180326787L; // 0xabf9183L
+ field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f;
+ field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
+ field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -1019,6 +1033,14 @@
method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public String getUiPackage();
}
+ public class BiometricPrompt {
+ method @NonNull public java.util.List<java.lang.Integer> getAllowedSensorIds();
+ }
+
+ public static class BiometricPrompt.Builder {
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowedSensorIds(@NonNull java.util.List<java.lang.Integer>);
+ }
+
public class BiometricTestSession implements java.lang.AutoCloseable {
method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void acceptAuthentication(int);
method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void cleanupInternalState(int);
@@ -1031,6 +1053,7 @@
}
public class SensorProperties {
+ method @NonNull public java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo> getComponentInfo();
method public int getSensorId();
method public int getSensorStrength();
field public static final int STRENGTH_CONVENIENCE = 0; // 0x0
@@ -1038,6 +1061,14 @@
field public static final int STRENGTH_WEAK = 1; // 0x1
}
+ public static final class SensorProperties.ComponentInfo {
+ method @NonNull public String getComponentId();
+ method @NonNull public String getFirmwareVersion();
+ method @NonNull public String getHardwareVersion();
+ method @NonNull public String getSerialNumber();
+ method @NonNull public String getSoftwareVersion();
+ }
+
}
package android.hardware.camera2 {
@@ -1322,6 +1353,7 @@
public class AudioManager {
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method public boolean hasRegisteredDynamicPolicy();
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
}
public static final class AudioRecord.MetricsConstants {
@@ -1379,10 +1411,6 @@
method public int getMaxMacroBlocks();
}
- public final class MediaDrm implements java.lang.AutoCloseable {
- method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
- }
-
public final class MediaRoute2Info implements android.os.Parcelable {
method @NonNull public String getOriginalId();
}
@@ -1477,6 +1505,8 @@
public class NetworkPolicyManager {
method public boolean getRestrictBackground();
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidRestrictedOnMeteredNetworks(int);
method @NonNull public static String resolveNetworkId(@NonNull android.net.wifi.WifiConfiguration);
method public void setRestrictBackground(boolean);
}
@@ -1949,8 +1979,7 @@
package android.security {
public final class KeyChain {
- method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean removeCredentialManagementApp(@NonNull android.content.Context);
- method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean setCredentialManagementApp(@NonNull android.content.Context, @NonNull String, @NonNull android.security.AppUriAuthenticationPolicy);
+ method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") @WorkerThread public static boolean setCredentialManagementApp(@NonNull android.content.Context, @NonNull String, @NonNull android.security.AppUriAuthenticationPolicy);
}
public class KeyStoreException extends java.lang.Exception {
@@ -2733,12 +2762,9 @@
method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
}
- public final class InputMethodInfo implements android.os.Parcelable {
- ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
- }
-
public final class InputMethodManager {
method public int getDisplayId();
+ method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
method public boolean isInputMethodPickerShown();
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index dab4a5d..d536821 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -555,6 +555,11 @@
*/
public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14;
+ /**
+ * Action to dismiss the notification shade
+ */
+ public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f5f0b42..86e2723 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1628,6 +1628,18 @@
}
/**
+ * Clear the splash screen view if exist.
+ * @hide
+ */
+ public void detachSplashScreenView() {
+ synchronized (this) {
+ if (mSplashScreenView != null) {
+ mSplashScreenView = null;
+ }
+ }
+ }
+
+ /**
* Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
* the attribute {@link android.R.attr#persistableMode} set to
* <code>persistAcrossReboots</code>.
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index fbabfac..633b986 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -25,6 +25,7 @@
import android.os.RemoteException;
import android.util.Singleton;
import android.view.RemoteAnimationDefinition;
+import android.window.SizeConfigurationBuckets;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -104,12 +105,9 @@
}
}
- void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
- int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
+ void reportSizeConfigurations(IBinder token, SizeConfigurationBuckets sizeConfigurations) {
try {
- getActivityClientController().reportSizeConfigurations(token,
- horizontalSizeConfiguration, verticalSizeConfigurations,
- smallestSizeConfigurations);
+ getActivityClientController().reportSizeConfigurations(token, sizeConfigurations);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f905ec8..3fedda3 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -31,6 +31,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
@@ -4048,7 +4049,8 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
public boolean switchUser(@NonNull UserHandle user) {
if (user == null) {
throw new IllegalArgumentException("UserHandle cannot be null.");
@@ -4144,6 +4146,25 @@
}
}
+ /**
+ * Stops the given {@code userId}.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ public boolean stopUser(@UserIdInt int userId, boolean force) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ return false;
+ }
+ try {
+ return USER_OP_SUCCESS == getService().stopUser(
+ userId, force, /* callback= */ null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** {@hide} */
public static final int FLAG_OR_STOPPED = 1 << 0;
/** {@hide} */
@@ -4811,6 +4832,21 @@
}
/**
+ * Blocks until all broadcast queues become idle.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void waitForBroadcastIdle() {
+ try {
+ getService().waitForBroadcastIdle();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A subset of immutable pending intent information suitable for caching on the client side.
*
* @hide
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 233f737..a24f871 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -394,6 +394,20 @@
}
/**
+ * Whether to allow non-resizable apps to be shown in multi-window. The app will be letterboxed
+ * if the request orientation is not met, and will be shown in size-compat mode if the container
+ * size has changed.
+ * @hide
+ */
+ public static boolean supportsNonResizableMultiWindow() {
+ try {
+ return ActivityTaskManager.getService().supportsNonResizableMultiWindow();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @return whether the UI mode of the given config supports error dialogs (ANR, crash, etc).
* @hide
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8977ba7..c1d8541 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -158,7 +158,6 @@
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import android.util.SuperNotCalledException;
import android.util.UtilConfig;
import android.util.proto.ProtoOutputStream;
@@ -180,6 +179,7 @@
import android.view.contentcapture.IContentCaptureOptionsCallback;
import android.view.translation.TranslationSpec;
import android.webkit.WebView;
+import android.window.SizeConfigurationBuckets;
import android.window.SplashScreen;
import android.window.SplashScreenView;
@@ -292,7 +292,6 @@
/** Use background GC policy and default JIT threshold. */
private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
- private static final int REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT = 5000;
/**
* Denotes an invalid sequence number corresponding to a process state change.
*/
@@ -604,6 +603,8 @@
@LifecycleState
private int mLifecycleState = PRE_ON_CREATE;
+ private SizeConfigurationBuckets mSizeConfigurations;
+
@VisibleForTesting
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public ActivityClientRecord() {
@@ -1954,8 +1955,6 @@
public static final int INSTRUMENT_WITHOUT_RESTART = 170;
public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
- public static final int REMOVE_SPLASH_SCREEN_VIEW = 172;
-
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -2004,8 +2003,6 @@
case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
- case REMOVE_SPLASH_SCREEN_VIEW:
- return "REMOVE_SPLASH_SCREEN_VIEW";
}
}
return Integer.toString(code);
@@ -2202,9 +2199,6 @@
case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
handleFinishInstrumentationWithoutRestart();
break;
- case REMOVE_SPLASH_SCREEN_VIEW:
- handleRemoveSplashScreenView((ActivityClientRecord) msg.obj);
- break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -3764,23 +3758,8 @@
if (configurations == null) {
return;
}
- SparseIntArray horizontal = new SparseIntArray();
- SparseIntArray vertical = new SparseIntArray();
- SparseIntArray smallest = new SparseIntArray();
- for (int i = configurations.length - 1; i >= 0; i--) {
- Configuration config = configurations[i];
- if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- vertical.put(config.screenHeightDp, 0);
- }
- if (config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- horizontal.put(config.screenWidthDp, 0);
- }
- if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
- smallest.put(config.smallestScreenWidthDp, 0);
- }
- }
- ActivityClient.getInstance().reportSizeConfigurations(r.token, horizontal.copyKeys(),
- vertical.copyKeys(), smallest.copyKeys());
+ r.mSizeConfigurations = new SizeConfigurationBuckets(configurations);
+ ActivityClient.getInstance().reportSizeConfigurations(r.token, r.mSizeConfigurations);
}
private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
@@ -4033,6 +4012,7 @@
view.cacheRootWindow(r.window);
view.makeSystemUIColorsTransparent();
r.activity.mSplashScreenView = view;
+ view.attachHostActivity(r.activity);
view.requestLayout();
// Ensure splash screen view is shown before remove the splash screen window.
final ViewRootImpl impl = decorView.getViewRootImpl();
@@ -4075,8 +4055,6 @@
@Override
public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
if (r.activity.mSplashScreenView != null) {
- Message msg = mH.obtainMessage(H.REMOVE_SPLASH_SCREEN_VIEW, r);
- mH.sendMessageDelayed(msg, REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT);
synchronized (this) {
if (mSplashScreenGlobal != null) {
mSplashScreenGlobal.dispatchOnExitAnimation(r.token,
@@ -4087,16 +4065,6 @@
}
/**
- * Force remove splash screen view.
- */
- private void handleRemoveSplashScreenView(@NonNull ActivityClientRecord r) {
- if (r.activity.mSplashScreenView != null) {
- r.activity.mSplashScreenView.remove();
- r.activity.mSplashScreenView = null;
- }
- }
-
- /**
* Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
* return to its previous state. This allows activities that rely on onUserLeaveHint instead of
* onPictureInPictureRequested to enter picture-in-picture.
@@ -5773,7 +5741,10 @@
// onConfigurationChanged.
// TODO(b/173090263): Use diff instead after the improvement of AssetManager and
// ResourcesImpl constructions.
- final int diff = activity.mCurrentConfig.diffPublicOnly(newConfig);
+ int diff = activity.mCurrentConfig.diffPublicOnly(newConfig);
+ final ActivityClientRecord cr = getActivityClient(activityToken);
+ diff = SizeConfigurationBuckets.filterDiff(diff, activity.mCurrentConfig, newConfig,
+ cr != null ? cr.mSizeConfigurations : null);
if (diff == 0) {
if (!shouldUpdateWindowMetricsBounds(activity.mCurrentConfig, newConfig)
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index dd1bc7c..27b19bc 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -56,7 +56,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserManager;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LongSparseArray;
@@ -89,6 +89,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -200,9 +201,12 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
public static final long SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE = 151105954L;
+ private static final String FULL_LOG = "privacy_attribution_tag_full_log_enabled";
private static final int MAX_UNFORWARDED_OPS = 10;
+ private static Boolean sFullLog = null;
+
final Context mContext;
@UnsupportedAppUsage
@@ -1203,9 +1207,21 @@
*/
public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE;
+ /**
+ * Allow apps to create the requests to manage the media files without user confirmation.
+ *
+ * @see android.Manifest.permission#MANAGE_MEDIA
+ * @see android.provider.MediaStore#createDeleteRequest(ContentResolver, Collection)
+ * @see android.provider.MediaStore#createTrashRequest(ContentResolver, Collection, boolean)
+ * @see android.provider.MediaStore#createWriteRequest(ContentResolver, Collection)
+ *
+ * @hide
+ */
+ public static final int OP_MANAGE_MEDIA = AppProtoEnums.APP_OP_MANAGE_MEDIA;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 110;
+ public static final int _NUM_OP = 111;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1603,6 +1619,18 @@
*/
public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
+ /**
+ * Allow apps to create the requests to manage the media files without user confirmation.
+ *
+ * @see android.Manifest.permission#MANAGE_MEDIA
+ * @see android.provider.MediaStore#createDeleteRequest(ContentResolver, Collection)
+ * @see android.provider.MediaStore#createTrashRequest(ContentResolver, Collection, boolean)
+ * @see android.provider.MediaStore#createWriteRequest(ContentResolver, Collection)
+ *
+ * @hide
+ */
+ public static final String OPSTR_MANAGE_MEDIA = "android:manage_media";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1684,6 +1712,7 @@
OP_MANAGE_ONGOING_CALLS,
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OP_SCHEDULE_EXACT_ALARM,
+ OP_MANAGE_MEDIA,
};
/**
@@ -1805,6 +1834,7 @@
OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM
OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE
OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE
+ OP_MANAGE_MEDIA, // MANAGE_MEDIA
};
/**
@@ -1921,6 +1951,7 @@
OPSTR_SCHEDULE_EXACT_ALARM,
OPSTR_FINE_LOCATION_SOURCE,
OPSTR_COARSE_LOCATION_SOURCE,
+ OPSTR_MANAGE_MEDIA,
};
/**
@@ -2038,6 +2069,7 @@
"SCHEDULE_EXACT_ALARM",
"FINE_LOCATION_SOURCE",
"COARSE_LOCATION_SOURCE",
+ "MANAGE_MEDIA",
};
/**
@@ -2156,6 +2188,7 @@
Manifest.permission.SCHEDULE_EXACT_ALARM,
null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE,
null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE,
+ Manifest.permission.MANAGE_MEDIA,
};
/**
@@ -2274,6 +2307,7 @@
null, // SCHEDULE_EXACT_ALARM
null, // ACCESS_FINE_LOCATION_SOURCE
null, // ACCESS_COARSE_LOCATION_SOURCE
+ null, // MANAGE_MEDIA
};
/**
@@ -2391,6 +2425,7 @@
null, // SCHEDULE_EXACT_ALARM
null, // ACCESS_FINE_LOCATION_SOURCE
null, // ACCESS_COARSE_LOCATION_SOURCE
+ null, // MANAGE_MEDIA
};
/**
@@ -2507,6 +2542,7 @@
AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE
AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE
+ AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA
};
/**
@@ -2627,6 +2663,7 @@
false, // SCHEDULE_EXACT_ALARM
false, // ACCESS_FINE_LOCATION_SOURCE
false, // ACCESS_COARSE_LOCATION_SOURCE
+ false, // MANAGE_MEDIA
};
/**
@@ -6972,6 +7009,26 @@
AppOpsManager(Context context, IAppOpsService service) {
mContext = context;
mService = service;
+
+ if (mContext != null) {
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ if (pm != null && pm.checkPermission(Manifest.permission.READ_DEVICE_CONFIG,
+ mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
+ mContext.getMainExecutor(), properties -> {
+ if (properties.getKeyset().contains(FULL_LOG)) {
+ sFullLog = properties.getBoolean(FULL_LOG, false);
+ }
+ });
+ return;
+ }
+ } catch (Exception e) {
+ // This manager was made before DeviceConfig is ready, so it's a low-level
+ // system app. We likely don't care about its logs.
+ }
+ }
+ sFullLog = false;
}
/**
@@ -8056,8 +8113,8 @@
} else if (collectionMode == COLLECT_SYNC
// Only collect app-ops when the proxy is trusted
&& (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
- myUid) == PackageManager.PERMISSION_GRANTED || isTrustedVoiceServiceProxy(
- mContext, mContext.getOpPackageName(), op, mContext.getUserId()))) {
+ myUid) == PackageManager.PERMISSION_GRANTED ||
+ Binder.getCallingUid() == proxiedUid)) {
collectNotedOpSync(op, proxiedAttributionTag);
}
}
@@ -8068,28 +8125,6 @@
}
}
- /**
- * Checks if the voice recognition service is a trust proxy.
- *
- * @return {@code true} if the package is a trust voice recognition service proxy
- * @hide
- */
- public static boolean isTrustedVoiceServiceProxy(Context context, String packageName,
- int code, int userId) {
- // This is a workaround for R QPR, new API change is not allowed. We only allow the current
- // voice recognizer is also the voice interactor to noteproxy op.
- if (code != OP_RECORD_AUDIO) {
- return false;
- }
- final String voiceRecognitionComponent = Settings.Secure.getStringForUser(
- context.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE, userId);
-
- final String voiceRecognitionServicePackageName =
- getComponentPackageNameFromString(voiceRecognitionComponent);
- return (Objects.equals(packageName, voiceRecognitionServicePackageName))
- && isPackagePreInstalled(context, packageName, userId);
- }
-
private static String getComponentPackageNameFromString(String from) {
ComponentName componentName = from != null ? ComponentName.unflattenFromString(from) : null;
return componentName != null ? componentName.getPackageName() : "";
@@ -8464,8 +8499,7 @@
// Only collect app-ops when the proxy is trusted
&& (mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
Process.myUid()) == PackageManager.PERMISSION_GRANTED
- || isTrustedVoiceServiceProxy(mContext, mContext.getOpPackageName(), opInt,
- mContext.getUserId()))) {
+ || Binder.getCallingUid() == proxiedUid)) {
collectNotedOpSync(opInt, proxiedAttributionTag);
}
}
@@ -9110,10 +9144,20 @@
StringBuilder sb = new StringBuilder();
for (int i = firstInteresting; i <= lastInteresting; i++) {
+ if (sFullLog == null) {
+ try {
+ sFullLog = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ FULL_LOG, false);
+ } catch (Exception e) {
+ // This should not happen, but it may, in rare cases
+ sFullLog = false;
+ }
+ }
+
if (i != firstInteresting) {
sb.append('\n');
}
- if (sb.length() + trace[i].toString().length() > 600) {
+ if (!sFullLog && sb.length() + trace[i].toString().length() > 600) {
break;
}
sb.append(trace[i]);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3af0763..0358fe5 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2586,9 +2586,7 @@
@Override
public Context createTokenContext(@NonNull IBinder token, @NonNull Display display) {
if (display == null) {
- throw new UnsupportedOperationException("Token context can only be created from "
- + "other visual contexts, such as Activity or one created with "
- + "Context#createDisplayContext(Display)");
+ throw new IllegalArgumentException("Display must not be null");
}
final ContextImpl tokenContext = createBaseWindowContext(token, display);
tokenContext.setResources(createWindowContextResources());
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 573931e..ed4836e 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -25,6 +25,7 @@
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.RemoteAnimationDefinition;
+import android.window.SizeConfigurationBuckets;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -49,8 +50,8 @@
oneway void activityDestroyed(in IBinder token);
oneway void activityRelaunched(in IBinder token);
- oneway void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
- in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
+ oneway void reportSizeConfigurations(in IBinder token,
+ in SizeConfigurationBuckets sizeConfigurations);
boolean moveActivityTaskToBack(in IBinder token, boolean nonRoot);
boolean shouldUpRecreateTask(in IBinder token, in String destAffinity);
boolean navigateUpTo(in IBinder token, in Intent target, int resultCode,
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ef0dcab..4c2433c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -709,4 +709,7 @@
ParceledListSlice queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags);
int getUidProcessCapabilities(int uid, in String callingPackage);
+
+ /** Blocks until all broadcast queues become idle. */
+ void waitForBroadcastIdle();
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 542f754..3bfddf7 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -289,6 +289,13 @@
void setSplitScreenResizing(boolean resizing);
boolean supportsLocalVoiceInteraction();
+ /**
+ * Whether to allow non-resizable apps to be shown in multi-window. The app will be letterboxed
+ * if the request orientation is not met, and will be shown in size-compat mode if the container
+ * size has changed.
+ */
+ boolean supportsNonResizableMultiWindow();
+
// Get device configuration
ConfigurationInfo getDeviceConfigurationInfo();
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 5402381..e83557c 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -167,7 +167,8 @@
* @hide
*/
void removeOnLocalColorsChangedListener(
- in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId);
+ in ILocalWallpaperColorConsumer callback, in List<RectF> area,
+ int which, int userId, int displayId);
/**
* @hide
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2b45723..60506b5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -823,6 +823,17 @@
public static final int VISIBILITY_SECRET = -1;
/**
+ * @hide
+ */
+ @IntDef(prefix = "VISIBILITY_", value = {
+ VISIBILITY_PUBLIC,
+ VISIBILITY_PRIVATE,
+ VISIBILITY_SECRET,
+ NotificationManager.VISIBILITY_NO_OVERRIDE
+ })
+ public @interface NotificationVisibilityOverride{};
+
+ /**
* Notification category: incoming call (voice or video) or similar synchronous communication request.
*/
public static final String CATEGORY_CALL = "call";
@@ -2673,7 +2684,17 @@
if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
if (extras != null) {
- visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI));
+ // 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,
+ // supporting both going forward is the safest choice.
+ Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI);
+ if (audioContentsUri instanceof Uri) {
+ visitor.accept((Uri) audioContentsUri);
+ } else if (audioContentsUri instanceof String) {
+ visitor.accept(Uri.parse((String) audioContentsUri));
+ }
+
if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) {
visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
}
@@ -5011,9 +5032,13 @@
boolean showProgress = handleProgressBar(contentView, ex, p);
boolean hasSecondLine = showProgress;
if (p.hasTitle()) {
- contentView.setViewVisibility(R.id.title, View.VISIBLE);
- contentView.setTextViewText(R.id.title, processTextSpans(p.title));
- setTextViewColorPrimary(contentView, R.id.title, p);
+ contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
+ contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title));
+ setTextViewColorPrimary(contentView, p.mTitleViewId, p);
+ } else if (p.mTitleViewId != R.id.title) {
+ // This alternate title view ID is not cleared by resetStandardTemplate
+ contentView.setViewVisibility(p.mTitleViewId, View.GONE);
+ contentView.setTextViewText(p.mTitleViewId, null);
}
if (p.text != null && p.text.length() != 0
&& (!showProgress || p.mAllowTextWithProgress)) {
@@ -5307,7 +5332,7 @@
contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors except when low-priority mode prevents that
- if (!p.forceDefaultColor) {
+ if (!p.mReduceHighlights) {
textColor = getBackgroundColor(p);
pillColor = getAccentColor(p);
}
@@ -5441,6 +5466,11 @@
// keep the divider visible between that title and the next text element.
return true;
}
+ if (p.mHideAppName) {
+ // The app name is being hidden, so we definitely want to return here.
+ // Assume that there is a title which will replace it in the header.
+ return p.hasTitle();
+ }
contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
@@ -5817,7 +5847,7 @@
*
* @hide
*/
- public RemoteViews makeNotificationHeader() {
+ public RemoteViews makeNotificationGroupHeader() {
return makeNotificationHeader(mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
.fillTextsFrom(this));
@@ -5943,7 +5973,7 @@
.viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
.fillTextsFrom(this);
if (isLowPriority) {
- params.forceDefaultColor();
+ params.reduceHighlights();
}
view = makeNotificationHeader(params);
view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
@@ -5966,7 +5996,7 @@
public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
- .forceDefaultColor()
+ .reduceHighlights()
.fillTextsFrom(this);
if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) {
p.summaryText(createSummaryText());
@@ -6306,7 +6336,9 @@
* @param p the template params to inflate this with
*/
private @ColorInt int getRawColor(StandardTemplateParams p) {
- if (p.forceDefaultColor) {
+ // When notifications are theme-tinted, the raw color is only used for the icon, so go
+ // ahead and keep that color instead of changing the color for minimized notifs.
+ if (p.mReduceHighlights && !mTintWithThemeAccent) {
return COLOR_DEFAULT;
}
return mN.color;
@@ -6583,10 +6615,6 @@
return R.layout.notification_template_material_conversation;
}
- private int getCallLayoutResource() {
- return R.layout.notification_template_material_call;
- }
-
private int getActionLayoutResource() {
return R.layout.notification_material_action;
}
@@ -9329,7 +9357,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- return makeCallLayout();
+ return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
}
/**
@@ -9337,14 +9365,14 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- return makeCallLayout();
+ return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
}
/**
* @hide
*/
public RemoteViews makeBigContentView() {
- return makeCallLayout();
+ return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
}
@NonNull
@@ -9443,8 +9471,10 @@
return resultActions;
}
- private RemoteViews makeCallLayout() {
+ private RemoteViews makeCallLayout(int viewType) {
+ final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
Bundle extras = mBuilder.mN.extras;
+ CharSequence title = mPerson != null ? mPerson.getName() : null;
CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
if (text == null) {
text = getDefaultText();
@@ -9452,20 +9482,30 @@
// Bind standard template
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(viewType)
.callStyleActions(true)
.allowTextWithProgress(true)
.hideLargeIcon(true)
+ .hideAppName(isCollapsed)
+ .titleViewId(R.id.conversation_text)
+ .title(title)
.text(text)
.summaryText(mBuilder.processLegacyText(mVerificationText));
mBuilder.mActions = getActionsListWithSystemActions();
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getCallLayoutResource(), p, null /* result */);
+ final RemoteViews contentView;
+ if (isCollapsed) {
+ contentView = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_call, p, null /* result */);
+ } else {
+ contentView = mBuilder.applyStandardTemplateWithActions(
+ R.layout.notification_template_material_big_call, p, null /* result */);
+ }
// Bind some extra conversation-specific header fields.
- mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
- mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
- contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+ if (!p.mHideAppName) {
+ mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+ contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+ }
bindCallerVerification(contentView, p);
// Bind some custom CallLayout properties
@@ -12142,12 +12182,13 @@
public static int VIEW_TYPE_NORMAL = 1;
public static int VIEW_TYPE_BIG = 2;
public static int VIEW_TYPE_HEADS_UP = 3;
- public static int VIEW_TYPE_MINIMIZED = 4;
- public static int VIEW_TYPE_PUBLIC = 5;
- public static int VIEW_TYPE_GROUP_HEADER = 6;
+ public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state
+ public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version
+ public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
int mViewType = VIEW_TYPE_UNSPECIFIED;
boolean mHeaderless;
+ boolean mHideAppName;
boolean mHideTitle;
boolean mHideActions;
boolean mHideProgress;
@@ -12155,6 +12196,7 @@
boolean mPromotePicture;
boolean mCallStyleActions;
boolean mAllowTextWithProgress;
+ int mTitleViewId;
int mTextViewId;
CharSequence title;
CharSequence text;
@@ -12163,11 +12205,12 @@
int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
boolean hideLargeIcon;
boolean allowColorization = true;
- boolean forceDefaultColor = false;
+ boolean mReduceHighlights = false;
final StandardTemplateParams reset() {
mViewType = VIEW_TYPE_UNSPECIFIED;
mHeaderless = false;
+ mHideAppName = false;
mHideTitle = false;
mHideActions = false;
mHideProgress = false;
@@ -12175,6 +12218,7 @@
mPromotePicture = false;
mCallStyleActions = false;
mAllowTextWithProgress = false;
+ mTitleViewId = R.id.title;
mTextViewId = R.id.text;
title = null;
text = null;
@@ -12182,7 +12226,7 @@
headerTextSecondary = null;
maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
allowColorization = true;
- forceDefaultColor = false;
+ mReduceHighlights = false;
return this;
}
@@ -12200,6 +12244,11 @@
return this;
}
+ public StandardTemplateParams hideAppName(boolean hideAppName) {
+ mHideAppName = hideAppName;
+ return this;
+ }
+
final StandardTemplateParams hideActions(boolean hideActions) {
this.mHideActions = hideActions;
return this;
@@ -12235,6 +12284,11 @@
return this;
}
+ public StandardTemplateParams titleViewId(int titleViewId) {
+ mTitleViewId = titleViewId;
+ return this;
+ }
+
public StandardTemplateParams textViewId(int textViewId) {
mTextViewId = textViewId;
return this;
@@ -12270,8 +12324,8 @@
return this;
}
- final StandardTemplateParams forceDefaultColor() {
- this.forceDefaultColor = true;
+ final StandardTemplateParams reduceHighlights() {
+ this.mReduceHighlights = true;
return this;
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 685c222..6553b61 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -108,13 +108,13 @@
* Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
* that have to do with editing do not disturb bypass {(@link #setBypassDnd(boolean)}) .
*/
- public static final String EDIT_ZEN = "dnd";
+ public static final String EDIT_ZEN = "zen";
/**
* Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
* that have to do with editing conversation settings (demoting or restoring a channel to
* be a Conversation, changing bubble behavior, or setting the priority of a conversation).
*/
- public static final String EDIT_CONVERSATION = "convo";
+ public static final String EDIT_CONVERSATION = "conversation";
/**
* Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
* that have to do with editing launcher behavior (showing badges)}.
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index afcf63b..1765849 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -331,10 +331,12 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.STATUS_BAR)
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Send {@link "
- + "android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS} instead.")
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "This operation"
+ + " is not allowed anymore, please see {@link android.content"
+ + ".Intent#ACTION_CLOSE_SYSTEM_DIALOGS} for more details.")
@TestApi
public void collapsePanels() {
+
try {
final IStatusBarService svc = getService();
if (svc != null) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 43c14a9..31b0d41 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -129,11 +129,13 @@
import android.net.IEthernetManager;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.IPacProxyManager;
import android.net.IVpnManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
+import android.net.PacProxyManager;
import android.net.TetheringManager;
import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
@@ -374,6 +376,15 @@
// (which extends it).
SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
+ registerService(Context.PAC_PROXY_SERVICE, PacProxyManager.class,
+ new CachedServiceFetcher<PacProxyManager>() {
+ @Override
+ public PacProxyManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.PAC_PROXY_SERVICE);
+ IPacProxyManager service = IPacProxyManager.Stub.asInterface(b);
+ return new PacProxyManager(ctx.getOuterContext(), service);
+ }});
+
registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
@Override
public IBinder createService() throws ServiceNotFoundException {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 7dbbc54..3ef6757 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -68,6 +68,7 @@
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
@@ -318,8 +319,20 @@
private int mCachedWallpaperUserId;
private Bitmap mDefaultWallpaper;
private Handler mMainLooperHandler;
- private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer>
- mLocalColorCallbacks = new ArrayMap<>();
+ private ArrayMap<RectF, ArraySet<LocalWallpaperColorConsumer>> mLocalColorAreas =
+ new ArrayMap<>();
+ private ILocalWallpaperColorConsumer mLocalColorCallback =
+ new ILocalWallpaperColorConsumer.Stub() {
+ @Override
+ public void onColorsChanged(RectF area, WallpaperColors colors) {
+ ArraySet<LocalWallpaperColorConsumer> callbacks =
+ mLocalColorAreas.get(area);
+ if (callbacks == null) return;
+ for (LocalWallpaperColorConsumer callback: callbacks) {
+ callback.onColorsChanged(area, colors);
+ }
+ }
+ };
Globals(IWallpaperManager service, Looper looper) {
mService = service;
@@ -361,37 +374,46 @@
}
}
- private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) {
- ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() {
- @Override
- public void onColorsChanged(RectF area, WallpaperColors colors) {
- callback.onColorsChanged(area, colors);
- }
- };
- mLocalColorCallbacks.put(callback, callback2);
- return callback2;
- }
-
public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
@NonNull List<RectF> regions, int which, int userId, int displayId) {
+ for (RectF area: regions) {
+ ArraySet<LocalWallpaperColorConsumer> callbacks = mLocalColorAreas.get(area);
+ if (callbacks == null) {
+ callbacks = new ArraySet<>();
+ mLocalColorAreas.put(area, callbacks);
+ }
+ callbacks.add(callback);
+ }
try {
- mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which,
+ mService.addOnLocalColorsChangedListener(mLocalColorCallback , regions, which,
userId, displayId);
} catch (RemoteException e) {
// Can't get colors, connection lost.
+ Log.e(TAG, "Can't register for local color updates", e);
}
}
public void removeOnColorsChangedListener(
@NonNull LocalWallpaperColorConsumer callback, int which, int userId,
int displayId) {
- ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback);
- if (callback2 == null) return;
+ final ArrayList<RectF> removeAreas = new ArrayList<>();
+ for (RectF area : mLocalColorAreas.keySet()) {
+ ArraySet<LocalWallpaperColorConsumer> callbacks = mLocalColorAreas.get(area);
+ if (callbacks == null) continue;
+ callbacks.remove(callback);
+ if (callbacks.size() == 0) {
+ mLocalColorAreas.remove(area);
+ removeAreas.add(area);
+ }
+ }
try {
- mService.removeOnLocalColorsChangedListener(
- callback2, which, userId, displayId);
+ if (removeAreas.size() > 0) {
+ mService.removeOnLocalColorsChangedListener(
+ mLocalColorCallback, removeAreas, which, userId, displayId);
+ }
} catch (RemoteException e) {
// Can't get colors, connection lost.
+ Log.e(TAG, "Can't unregister for local color updates", e);
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 930717b..594b005 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1766,6 +1766,16 @@
"android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
/**
+ * Broadcast action: notify that a value of {@link Settings.Global#DEVICE_POLICY_CONSTANTS}
+ * has been changed.
+ * @hide
+ */
+ @TestApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED =
+ "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
+
+ /**
* Permission policy to prompt user for new permission requests for runtime permissions.
* Already granted or denied permissions are not affected by this.
*/
@@ -2426,7 +2436,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = {"PRIVATE_DNS_MODE_"}, value = {
+ @IntDef(prefix = {"PRIVATE_DNS_MODE_"}, value = {
PRIVATE_DNS_MODE_UNKNOWN,
PRIVATE_DNS_MODE_OFF,
PRIVATE_DNS_MODE_OPPORTUNISTIC,
@@ -2987,7 +2997,7 @@
/**
* Checks if it's safe to run operations that can be affected by the given {@code reason}.
*
- * <p><b>Note:/b> notice that the operation safety state might change between the time this
+ * <p><b>Note:</b> notice that the operation safety state might change between the time this
* method returns and the operation's method is called, so calls to the latter could still throw
* a {@link UnsafeStateException} even when this method returns {@code true}.
*
@@ -6159,13 +6169,22 @@
// STOPSHIP(b/174298501): clarify the expected return value following generateKeyPair call.
/**
- * Called by a device or profile owner, or delegated certificate installer, to query whether a
- * certificate and private key are installed under a given alias.
+ * This API can be called by the following to query whether a certificate and private key are
+ * installed under a given alias:
+ * <ul>
+ * <li>Device owner</li>
+ * <li>Profile owner</li>
+ * <li>Delegated certificate installer</li>
+ * <li>Credential management app</li>
+ * </ul>
+ *
+ * If called by the credential management app, the alias must exist in the credential
+ * management app's {@link android.security.AppUriAuthenticationPolicy}.
*
* @param alias The alias under which the key pair is installed.
* @return {@code true} if a key pair with this alias exists, {@code false} otherwise.
- * @throws SecurityException if the caller is not a device or profile owner or a delegated
- * certificate installer.
+ * @throws SecurityException if the caller is not a device or profile owner, a delegated
+ * certificate installer or the credential management app.
* @see #setDelegatedScopes
* @see #DELEGATION_CERT_INSTALL
*/
@@ -10004,37 +10023,6 @@
}
/**
- * Indicates whether 5g slicing is enabled for specific user.
- *
- * This method can be called with permission
- * {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG} by the profile owner of
- * a managed profile. And the caller must hold the
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission if query for
- * other users.
- *
- * @param userHandle indicates the user to query the state
- * @return indicates whether 5g Slice is enabled.
- * @throws SecurityException if the caller is not granted the permission
- * {@link android.Manifest.permission#READ_NETWORK_DEVICE_CONFIG}
- * and not profile owner of a managed profile, and not granted the permission
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if query for
- * other users.
- * @hide
- */
- @SystemApi
- public boolean isNetworkSlicingEnabledForUser(@NonNull UserHandle userHandle) {
- throwIfParentInstance("isNetworkSlicingEnabledForUser");
- if (mService == null) {
- return false;
- }
- try {
- return mService.isNetworkSlicingEnabled(userHandle.getIdentifier());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* This method is mostly deprecated.
* Most of the settings that still have an effect have dedicated setter methods or user
* restrictions. See individual settings for details.
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 94ab0dd..9f8fcc1 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -253,7 +253,7 @@
return other == null;
}
return other != null && mInfo.flags == other.flags
- && mInfo.maxAspectRatio == other.maxAspectRatio
+ && mInfo.getMaxAspectRatio() == other.getMaxAspectRatio()
&& Objects.equals(mInfo.launchToken, other.launchToken)
&& Objects.equals(mInfo.getComponentName(), other.getComponentName());
}
diff --git a/core/java/android/app/time/Capabilities.java b/core/java/android/app/time/Capabilities.java
new file mode 100644
index 0000000..33db721
--- /dev/null
+++ b/core/java/android/app/time/Capabilities.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 android.app.time;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A capability is the ability for the user to configure something or perform an action. This
+ * information is exposed so that system apps like SettingsUI can be dynamic, rather than
+ * hard-coding knowledge of when configuration or actions are applicable / available to the user.
+ *
+ * <p>Capabilities have states that users cannot change directly. They may influence some
+ * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or
+ * by changing the configuration. See the {@code CAPABILITY_} constants for details.
+ *
+ * <p>Actions have associated methods, see the documentation for each action for details.
+ *
+ * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
+ *
+ * @hide
+ */
+@SystemApi
+public final class Capabilities {
+
+ /** @hide */
+ @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
+ CAPABILITY_POSSESSED })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CapabilityState {}
+
+ /**
+ * Indicates that a capability is not supported on this device, e.g. because of form factor or
+ * hardware. The associated UI should usually not be shown to the user.
+ */
+ public static final int CAPABILITY_NOT_SUPPORTED = 10;
+
+ /**
+ * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
+ * if the capability relates to the ability to modify settings the user is not able to.
+ * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
+ * device policy. Depending on the capability, this could mean the associated UI
+ * should be hidden, or displayed but disabled.
+ */
+ public static final int CAPABILITY_NOT_ALLOWED = 20;
+
+ /**
+ * Indicates that a capability is possessed but not currently applicable, e.g. if the
+ * capability relates to the ability to modify settings, the user has the ability to modify
+ * it, but it is currently rendered irrelevant by other settings or other device state (flags,
+ * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
+ * ineffective) depending on requirements.
+ */
+ public static final int CAPABILITY_NOT_APPLICABLE = 30;
+
+ /** Indicates that a capability is possessed by the user. */
+ public static final int CAPABILITY_POSSESSED = 40;
+
+ private Capabilities() {}
+
+}
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/core/java/android/app/time/TimeCapabilities.aidl
similarity index 82%
copy from telephony/java/android/telephony/data/SliceInfo.aidl
copy to core/java/android/app/time/TimeCapabilities.aidl
index 286ea5e..f44b791 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/core/java/android/app/time/TimeCapabilities.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package android.app.time;
-parcelable SliceInfo;
+parcelable TimeCapabilities;
diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java
new file mode 100644
index 0000000..fff36c4
--- /dev/null
+++ b/core/java/android/app/time/TimeCapabilities.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.app.time;
+
+import android.annotation.NonNull;
+import android.app.time.Capabilities.CapabilityState;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.util.Objects;
+
+/**
+ * Time-relate capabilities for a user.
+ *
+ * <p>For configuration settings capabilities, the associated settings value can be found via
+ * {@link TimeManager#getTimeCapabilitiesAndConfig()} and may be changed using {@link
+ * TimeManager#updateTimeConfiguration(TimeConfiguration)} (if the user's capabilities
+ * allow).
+ *
+ * @hide
+ */
+public final class TimeCapabilities implements Parcelable {
+
+ public static final @NonNull Creator<TimeCapabilities> CREATOR =
+ new Creator<TimeCapabilities>() {
+ public TimeCapabilities createFromParcel(Parcel in) {
+ return TimeCapabilities.createFromParcel(in);
+ }
+
+ public TimeCapabilities[] newArray(int size) {
+ return new TimeCapabilities[size];
+ }
+ };
+
+
+ /**
+ * The user the capabilities are for. This is used for object equality and debugging but there
+ * is no accessor.
+ */
+ @NonNull
+ private final UserHandle mUserHandle;
+ private final @CapabilityState int mConfigureAutoTimeDetectionEnabledCapability;
+ private final @CapabilityState int mSuggestTimeManuallyCapability;
+
+ private TimeCapabilities(@NonNull Builder builder) {
+ this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
+ this.mConfigureAutoTimeDetectionEnabledCapability =
+ builder.mConfigureAutoDetectionEnabledCapability;
+ this.mSuggestTimeManuallyCapability =
+ builder.mSuggestTimeManuallyCapability;
+ }
+
+ @NonNull
+ private static TimeCapabilities createFromParcel(Parcel in) {
+ UserHandle userHandle = UserHandle.readFromParcel(in);
+ return new TimeCapabilities.Builder(userHandle)
+ .setConfigureAutoTimeDetectionEnabledCapability(in.readInt())
+ .setSuggestTimeManuallyCapability(in.readInt())
+ .build();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ UserHandle.writeToParcel(mUserHandle, dest);
+ dest.writeInt(mConfigureAutoTimeDetectionEnabledCapability);
+ dest.writeInt(mSuggestTimeManuallyCapability);
+ }
+
+ /**
+ * Returns the capability state associated with the user's ability to modify the automatic time
+ * detection setting.
+ */
+ @CapabilityState
+ public int getConfigureAutoTimeDetectionEnabledCapability() {
+ return mConfigureAutoTimeDetectionEnabledCapability;
+ }
+
+ /**
+ * Returns the capability state associated with the user's ability to manually set time on a
+ * device.
+ */
+ @CapabilityState
+ public int getSuggestTimeManuallyCapability() {
+ return mSuggestTimeManuallyCapability;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TimeCapabilities that = (TimeCapabilities) o;
+ return mConfigureAutoTimeDetectionEnabledCapability
+ == that.mConfigureAutoTimeDetectionEnabledCapability
+ && mSuggestTimeManuallyCapability == that.mSuggestTimeManuallyCapability
+ && mUserHandle.equals(that.mUserHandle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUserHandle, mConfigureAutoTimeDetectionEnabledCapability,
+ mSuggestTimeManuallyCapability);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeCapabilities{"
+ + "mUserHandle=" + mUserHandle
+ + ", mConfigureAutoTimeDetectionEnabledCapability="
+ + mConfigureAutoTimeDetectionEnabledCapability
+ + ", mSuggestTimeManuallyCapability=" + mSuggestTimeManuallyCapability
+ + '}';
+ }
+
+ /**
+ * A builder of {@link TimeCapabilities} objects.
+ *
+ * @hide
+ */
+ public static class Builder {
+ @NonNull private final UserHandle mUserHandle;
+ private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+ private @CapabilityState int mSuggestTimeManuallyCapability;
+
+ public Builder(@NonNull TimeCapabilities timeCapabilities) {
+ Objects.requireNonNull(timeCapabilities);
+ this.mUserHandle = timeCapabilities.mUserHandle;
+ this.mConfigureAutoDetectionEnabledCapability =
+ timeCapabilities.mConfigureAutoTimeDetectionEnabledCapability;
+ this.mSuggestTimeManuallyCapability =
+ timeCapabilities.mSuggestTimeManuallyCapability;
+ }
+
+ public Builder(@NonNull UserHandle userHandle) {
+ this.mUserHandle = Objects.requireNonNull(userHandle);
+ }
+
+ /** Sets the state for automatic time detection config. */
+ public Builder setConfigureAutoTimeDetectionEnabledCapability(
+ @CapabilityState int setConfigureAutoTimeDetectionEnabledCapability) {
+ this.mConfigureAutoDetectionEnabledCapability =
+ setConfigureAutoTimeDetectionEnabledCapability;
+ return this;
+ }
+
+ /** Sets the state for manual time change. */
+ public Builder setSuggestTimeManuallyCapability(
+ @CapabilityState int suggestTimeManuallyCapability) {
+ this.mSuggestTimeManuallyCapability = suggestTimeManuallyCapability;
+ return this;
+ }
+
+ /** Returns the {@link TimeCapabilities}. */
+ public TimeCapabilities build() {
+ verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
+ "configureAutoDetectionEnabledCapability");
+ verifyCapabilitySet(mSuggestTimeManuallyCapability, "suggestTimeManuallyCapability");
+ return new TimeCapabilities(this);
+ }
+
+ private void verifyCapabilitySet(int value, String name) {
+ if (value == 0) {
+ throw new IllegalStateException(name + " was not set");
+ }
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/core/java/android/app/time/TimeCapabilitiesAndConfig.aidl
similarity index 82%
copy from telephony/java/android/telephony/data/SliceInfo.aidl
copy to core/java/android/app/time/TimeCapabilitiesAndConfig.aidl
index 286ea5e..183dcaf 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package android.app.time;
-parcelable SliceInfo;
+parcelable TimeCapabilitiesAndConfig;
\ No newline at end of file
diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
new file mode 100644
index 0000000..4a10447
--- /dev/null
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -0,0 +1,119 @@
+/*
+ * 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.time;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A pair containing a user's {@link TimeCapabilities} and {@link TimeConfiguration}.
+ *
+ * @hide
+ */
+public final class TimeCapabilitiesAndConfig implements Parcelable {
+
+ public static final @NonNull Creator<TimeCapabilitiesAndConfig> CREATOR =
+ new Creator<TimeCapabilitiesAndConfig>() {
+ @Override
+ public TimeCapabilitiesAndConfig createFromParcel(Parcel source) {
+ return TimeCapabilitiesAndConfig.readFromParcel(source);
+ }
+
+ @Override
+ public TimeCapabilitiesAndConfig[] newArray(int size) {
+ return new TimeCapabilitiesAndConfig[size];
+ }
+ };
+
+ @NonNull
+ private final TimeCapabilities mTimeCapabilities;
+
+ @NonNull
+ private final TimeConfiguration mTimeConfiguration;
+
+ /**
+ * @hide
+ */
+ public TimeCapabilitiesAndConfig(@NonNull TimeCapabilities timeCapabilities,
+ @NonNull TimeConfiguration timeConfiguration) {
+ mTimeCapabilities = Objects.requireNonNull(timeCapabilities);
+ mTimeConfiguration = Objects.requireNonNull(timeConfiguration);
+ }
+
+ @NonNull
+ private static TimeCapabilitiesAndConfig readFromParcel(Parcel in) {
+ TimeCapabilities capabilities = in.readParcelable(null);
+ TimeConfiguration configuration = in.readParcelable(null);
+ return new TimeCapabilitiesAndConfig(capabilities, configuration);
+ }
+
+ /**
+ * Returns the user's time behaviour capabilities.
+ *
+ * @hide
+ */
+ @NonNull
+ public TimeCapabilities getTimeCapabilities() {
+ return mTimeCapabilities;
+ }
+
+ /**
+ * Returns the user's time behaviour configuration.
+ *
+ * @hide
+ */
+ @NonNull
+ public TimeConfiguration getTimeConfiguration() {
+ return mTimeConfiguration;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mTimeCapabilities, flags);
+ dest.writeParcelable(mTimeConfiguration, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TimeCapabilitiesAndConfig that = (TimeCapabilitiesAndConfig) o;
+ return mTimeCapabilities.equals(that.mTimeCapabilities)
+ && mTimeConfiguration.equals(that.mTimeConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTimeCapabilities, mTimeConfiguration);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeCapabilitiesAndConfig{"
+ + "mTimeCapabilities=" + mTimeCapabilities
+ + ", mTimeConfiguration=" + mTimeConfiguration
+ + '}';
+ }
+}
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/core/java/android/app/time/TimeConfiguration.aidl
similarity index 82%
copy from telephony/java/android/telephony/data/SliceInfo.aidl
copy to core/java/android/app/time/TimeConfiguration.aidl
index 286ea5e..eb5bfd6 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/core/java/android/app/time/TimeConfiguration.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package android.app.time;
-parcelable SliceInfo;
+parcelable TimeConfiguration;
diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java
new file mode 100644
index 0000000..70aede0
--- /dev/null
+++ b/core/java/android/app/time/TimeConfiguration.java
@@ -0,0 +1,141 @@
+/*
+ * 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.time;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * User visible settings that control the behavior of the time zone detector / manual time zone
+ * entry.
+ *
+ * @hide
+ */
+public final class TimeConfiguration implements Parcelable {
+
+ public static final @NonNull Creator<TimeConfiguration> CREATOR =
+ new Creator<TimeConfiguration>() {
+ @Override
+ public TimeConfiguration createFromParcel(Parcel source) {
+ return TimeConfiguration.readFromParcel(source);
+ }
+
+ @Override
+ public TimeConfiguration[] newArray(int size) {
+ return new TimeConfiguration[size];
+ }
+ };
+
+ @StringDef(SETTING_AUTO_DETECTION_ENABLED)
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Setting {}
+
+ @Setting
+ private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+
+ @NonNull
+ private final Bundle mBundle;
+
+ private TimeConfiguration(Builder builder) {
+ this.mBundle = builder.mBundle;
+ }
+
+ /**
+ * Returns the value of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting. This
+ * controls whether a device will attempt to determine the time automatically using
+ * contextual information if the device supports auto detection.
+ */
+ public boolean isAutoDetectionEnabled() {
+ return mBundle.getBoolean(SETTING_AUTO_DETECTION_ENABLED);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBundle(mBundle);
+ }
+
+ private static TimeConfiguration readFromParcel(Parcel in) {
+ return new TimeConfiguration.Builder()
+ .merge(in.readBundle())
+ .build();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TimeConfiguration that = (TimeConfiguration) o;
+ return mBundle.kindofEquals(that.mBundle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBundle);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeConfiguration{"
+ + "mBundle=" + mBundle
+ + '}';
+ }
+
+ /**
+ * A builder for {@link TimeConfiguration} objects.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ private final Bundle mBundle = new Bundle();
+
+ public Builder() {}
+
+ public Builder(@NonNull TimeConfiguration configuration) {
+ mBundle.putAll(configuration.mBundle);
+ }
+
+ /** Sets whether auto detection is enabled or not. */
+ @NonNull
+ public Builder setAutoDetectionEnabled(boolean enabled) {
+ mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
+ return this;
+ }
+
+ Builder merge(@NonNull Bundle bundle) {
+ mBundle.putAll(bundle);
+ return this;
+ }
+
+ /** Returns {@link TimeConfiguration} object. */
+ @NonNull
+ public TimeConfiguration build() {
+ return new TimeConfiguration(this);
+ }
+ }
+}
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index 430960f..c8fa5c8 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -75,7 +75,7 @@
@NonNull
public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() {
if (DEBUG) {
- Log.d(TAG, "getTimeZoneCapabilities called");
+ Log.d(TAG, "getTimeZoneCapabilitiesAndConfig called");
}
try {
return mITimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -85,6 +85,44 @@
}
/**
+ * Returns the calling user's time capabilities and configuration.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ @NonNull
+ public TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig() {
+ if (DEBUG) {
+ Log.d(TAG, "getTimeCapabilitiesAndConfig called");
+ }
+ try {
+ return mITimeDetectorService.getCapabilitiesAndConfig();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Modifies the time detection configuration.
+ *
+ * @return {@code true} if all the configuration settings specified have been set to the
+ * new values, {@code false} if none have
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public boolean updateTimeConfiguration(@NonNull TimeConfiguration configuration) {
+ if (DEBUG) {
+ Log.d(TAG, "updateTimeConfiguration called: " + configuration);
+ }
+ try {
+ return mITimeDetectorService.updateConfiguration(configuration);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Modifies the time zone detection configuration.
*
* <p>Configuration settings vary in scope: some may be global (affect all users), others may be
@@ -97,11 +135,11 @@
* capabilities.
*
* <p>Attempts to modify configuration settings with capabilities that are {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
+ * Capabilities#CAPABILITY_NOT_SUPPORTED} or {@link
+ * Capabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
* will be returned. Modifying configuration settings with capabilities that are {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
- * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
+ * Capabilities#CAPABILITY_NOT_APPLICABLE} or {@link
+ * Capabilities#CAPABILITY_POSSESSED} will succeed. See {@link
* TimeZoneCapabilities} for further details.
*
* <p>If the supplied configuration only has some values set, then only the specified settings
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index a27be96..433b420 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -16,77 +16,33 @@
package android.app.time;
-import android.annotation.IntDef;
+import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.time.Capabilities.CapabilityState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TimeZoneDetector;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
- * Time zone-related capabilities for a user. A capability is the ability for the user to configure
- * something or perform an action. This information is exposed so that system apps like SettingsUI
- * can be dynamic, rather than hard-coding knowledge of when configuration or actions are applicable
- * / available to the user.
- *
- * <p>Capabilities have states that users cannot change directly. They may influence some
- * capabilities indirectly by agreeing to certain device-wide behaviors such as location sharing, or
- * by changing the configuration. See the {@code CAPABILITY_} constants for details.
- *
- * <p>Actions have associated methods, see the documentation for each action for details.
+ * Time zone-related capabilities for a user.
*
* <p>For configuration settings capabilities, the associated settings value can be found via
* {@link TimeManager#getTimeZoneCapabilitiesAndConfig()} and may be changed using {@link
* TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} (if the user's capabilities
* allow).
*
- * <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
- *
* @hide
*/
@SystemApi
public final class TimeZoneCapabilities implements Parcelable {
- /** @hide */
- @IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
- CAPABILITY_POSSESSED })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CapabilityState {}
-
- /**
- * Indicates that a capability is not supported on this device, e.g. because of form factor or
- * hardware. The associated UI should usually not be shown to the user.
- */
- public static final int CAPABILITY_NOT_SUPPORTED = 10;
-
- /**
- * Indicates that a capability is supported on this device, but not allowed for the user, e.g.
- * if the capability relates to the ability to modify settings the user is not able to.
- * This could be because of the user's type (e.g. maybe it applies to the primary user only) or
- * device policy. Depending on the capability, this could mean the associated UI
- * should be hidden, or displayed but disabled.
- */
- public static final int CAPABILITY_NOT_ALLOWED = 20;
-
- /**
- * Indicates that a capability is possessed but not currently applicable, e.g. if the
- * capability relates to the ability to modify settings, the user has the ability to modify
- * it, but it is currently rendered irrelevant by other settings or other device state (flags,
- * resource config, etc.). The associated UI may be hidden, disabled, or left visible (but
- * ineffective) depending on requirements.
- */
- public static final int CAPABILITY_NOT_APPLICABLE = 30;
-
- /** Indicates that a capability is possessed by the user. */
- public static final int CAPABILITY_POSSESSED = 40;
-
public static final @NonNull Creator<TimeZoneCapabilities> CREATOR =
new Creator<TimeZoneCapabilities>() {
public TimeZoneCapabilities createFromParcel(Parcel in) {
@@ -159,7 +115,8 @@
* on a device via {@link TimeZoneDetector#suggestManualTimeZone(ManualTimeZoneSuggestion)}.
*
* <p>The suggestion will be ignored in all cases unless the value is {@link
- * #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
+ * Capabilities#CAPABILITY_POSSESSED}. See also
+ * {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
*
* @hide
*/
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index f9a0c74..a9ea76f 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -113,7 +113,7 @@
@Override
public String toString() {
- return "TimeZoneDetectorCapabilitiesAndConfig{"
+ return "TimeZoneCapabilitiesAndConfig{"
+ "mCapabilities=" + mCapabilities
+ ", mConfiguration=" + mConfiguration
+ '}';
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index c4546be..9a6c335 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -17,6 +17,8 @@
package android.app.timedetector;
import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
@@ -36,6 +38,9 @@
* {@hide}
*/
interface ITimeDetectorService {
+ TimeCapabilitiesAndConfig getCapabilitiesAndConfig();
+ boolean updateConfiguration(in TimeConfiguration timeConfiguration);
+
void suggestExternalTime( in ExternalTimeSuggestion timeSuggestion);
void suggestGnssTime(in GnssTimeSuggestion timeSuggestion);
boolean suggestManualTime(in ManualTimeSuggestion timeSuggestion);
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 098d8b6..9f1132b 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -24,6 +24,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.WorkerThread;
import android.app.usage.NetworkStats.Bucket;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -201,6 +202,7 @@
* default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
* metered {@link NetworkStats.Bucket#METERED_ALL},
* and roaming {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * This may take a long time, and apps should avoid calling this on their main thread.
*
* @param networkType As defined in {@link ConnectivityManager}, e.g.
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -219,6 +221,7 @@
* @return Bucket object or null if permissions are insufficient or error happened during
* statistics collection.
*/
+ @WorkerThread
public Bucket querySummaryForDevice(int networkType, String subscriberId,
long startTime, long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
@@ -240,6 +243,7 @@
* uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE},
* metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming
* {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * This may take a long time, and apps should avoid calling this on their main thread.
*
* @param networkType As defined in {@link ConnectivityManager}, e.g.
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -258,6 +262,7 @@
* @return Bucket object or null if permissions are insufficient or error happened during
* statistics collection.
*/
+ @WorkerThread
public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime,
long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
@@ -283,6 +288,7 @@
* means buckets' start and end timestamps are going to be the same as the 'startTime' and
* 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to
* be the same.
+ * This may take a long time, and apps should avoid calling this on their main thread.
*
* @param networkType As defined in {@link ConnectivityManager}, e.g.
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -301,6 +307,7 @@
* @return Statistics object or null if permissions are insufficient or error happened during
* statistics collection.
*/
+ @WorkerThread
public NetworkStats querySummary(int networkType, String subscriberId, long startTime,
long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
@@ -326,9 +333,11 @@
/**
* Query network usage statistics details for a given uid.
+ * This may take a long time, and apps should avoid calling this on their main thread.
*
* @see #queryDetailsForUidTagState(int, String, long, long, int, int, int)
*/
+ @WorkerThread
public NetworkStats queryDetailsForUid(int networkType, String subscriberId,
long startTime, long endTime, int uid) throws SecurityException {
return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
@@ -344,9 +353,11 @@
/**
* Query network usage statistics details for a given uid and tag.
+ * This may take a long time, and apps should avoid calling this on their main thread.
*
* @see #queryDetailsForUidTagState(int, String, long, long, int, int, int)
*/
+ @WorkerThread
public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId,
long startTime, long endTime, int uid, int tag) throws SecurityException {
return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
@@ -365,6 +376,7 @@
* <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
* interpolate across partial buckets. Since bucket length is in the order of hours, this
* method cannot be used to measure data usage on a fine grained time scale.
+ * This may take a long time, and apps should avoid calling this on their main thread.
*
* @param networkType As defined in {@link ConnectivityManager}, e.g.
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -387,6 +399,7 @@
* @return Statistics object or null if an error happened during statistics collection.
* @throws SecurityException if permissions are insufficient to read network statistics.
*/
+ @WorkerThread
public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId,
long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
NetworkTemplate template;
@@ -425,6 +438,7 @@
* <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
* interpolate across partial buckets. Since bucket length is in the order of hours, this
* method cannot be used to measure data usage on a fine grained time scale.
+ * This may take a long time, and apps should avoid calling this on their main thread.
*
* @param networkType As defined in {@link ConnectivityManager}, e.g.
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -443,6 +457,7 @@
* @return Statistics object or null if permissions are insufficient or error happened during
* statistics collection.
*/
+ @WorkerThread
public NetworkStats queryDetails(int networkType, String subscriberId, long startTime,
long endTime) throws SecurityException, RemoteException {
NetworkTemplate template;
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index dcecd90..5d50c5d7 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -20,6 +20,7 @@
import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
+import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED;
import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
import static android.app.usage.UsageEvents.Event.END_OF_DAY;
@@ -109,6 +110,13 @@
public long mTotalTimeForegroundServiceUsed;
/**
+ * Last time this package's component is used, measured in milliseconds since the epoch.
+ * See {@link UsageEvents.Event#APP_COMPONENT_USED}
+ * @hide
+ */
+ public long mLastTimeComponentUsed;
+
+ /**
* {@hide}
*/
@UnsupportedAppUsage
@@ -166,6 +174,7 @@
mEndTimeStamp = stats.mEndTimeStamp;
mLastTimeUsed = stats.mLastTimeUsed;
mLastTimeVisible = stats.mLastTimeVisible;
+ mLastTimeComponentUsed = stats.mLastTimeComponentUsed;
mLastTimeForegroundServiceUsed = stats.mLastTimeForegroundServiceUsed;
mTotalTimeInForeground = stats.mTotalTimeInForeground;
mTotalTimeVisible = stats.mTotalTimeVisible;
@@ -265,6 +274,16 @@
}
/**
+ * Get the last time this package's component was used, measured in milliseconds since the
+ * epoch.
+ * @hide
+ */
+ @SystemApi
+ public long getLastTimeComponentUsed() {
+ return mLastTimeComponentUsed;
+ }
+
+ /**
* Returns the number of times the app was launched as an activity from outside of the app.
* Excludes intra-app activity transitions.
* @hide
@@ -323,6 +342,7 @@
mergeEventMap(mForegroundServices, right.mForegroundServices);
mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed);
mLastTimeVisible = Math.max(mLastTimeVisible, right.mLastTimeVisible);
+ mLastTimeComponentUsed = Math.max(mLastTimeComponentUsed, right.mLastTimeComponentUsed);
mLastTimeForegroundServiceUsed = Math.max(mLastTimeForegroundServiceUsed,
right.mLastTimeForegroundServiceUsed);
}
@@ -598,6 +618,9 @@
mLastTimeVisible = timeStamp;
}
break;
+ case APP_COMPONENT_USED:
+ mLastTimeComponentUsed = timeStamp;
+ break;
default:
break;
}
@@ -620,6 +643,7 @@
dest.writeLong(mEndTimeStamp);
dest.writeLong(mLastTimeUsed);
dest.writeLong(mLastTimeVisible);
+ dest.writeLong(mLastTimeComponentUsed);
dest.writeLong(mLastTimeForegroundServiceUsed);
dest.writeLong(mTotalTimeInForeground);
dest.writeLong(mTotalTimeVisible);
@@ -674,6 +698,7 @@
stats.mEndTimeStamp = in.readLong();
stats.mLastTimeUsed = in.readLong();
stats.mLastTimeVisible = in.readLong();
+ stats.mLastTimeComponentUsed = in.readLong();
stats.mLastTimeForegroundServiceUsed = in.readLong();
stats.mTotalTimeInForeground = in.readLong();
stats.mTotalTimeVisible = in.readLong();
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
index 7281d50..132cc40 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -17,6 +17,7 @@
package android.apphibernation;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
@@ -54,6 +55,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
public boolean isHibernatingForUser(@NonNull String packageName) {
try {
return mIAppHibernationService.isHibernatingForUser(packageName, mContext.getUserId());
@@ -68,6 +70,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
public void setHibernatingForUser(@NonNull String packageName, boolean isHibernating) {
try {
mIAppHibernationService.setHibernatingForUser(packageName, mContext.getUserId(),
@@ -83,6 +86,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
public boolean isHibernatingGlobally(@NonNull String packageName) {
try {
return mIAppHibernationService.isHibernatingGlobally(packageName);
@@ -99,6 +103,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION)
public void setHibernatingGlobally(@NonNull String packageName, boolean isHibernating) {
try {
mIAppHibernationService.setHibernatingGlobally(packageName, isHibernating);
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index ec94faa..07dbdce 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1311,7 +1311,6 @@
* the bonding process completes, and its result.
* <p>Android system services will handle the necessary user interactions
* to confirm and complete the bonding process.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
* @param transport The transport to use for the pairing procedure.
* @return false on immediate error, true if bonding will begin
@@ -1319,8 +1318,9 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean createBond(int transport) {
- return createBondOutOfBand(transport, null);
+ return createBondInternal(transport, null, null);
}
/**
@@ -1334,22 +1334,39 @@
* <p>Android system services will handle the necessary user interactions
* to confirm and complete the bonding process.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+ * <p>There are two possible versions of OOB Data. This data can come in as
+ * P192 or P256. This is a reference to the cryptography used to generate the key.
+ * The caller may pass one or both. If both types of data are passed, then the
+ * P256 data will be preferred, and thus used.
*
* @param transport - Transport to use
- * @param oobData - Out Of Band data
+ * @param remoteP192Data - Out Of Band data (P192) or null
+ * @param remoteP256Data - Out Of Band data (P256) or null
* @return false on immediate error, true if bonding will begin
* @hide
*/
- public boolean createBondOutOfBand(int transport, OobData oobData) {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data,
+ @Nullable OobData remoteP256Data) {
+ if (remoteP192Data == null && remoteP256Data == null) {
+ throw new IllegalArgumentException(
+ "One or both arguments for the OOB data types are required to not be null."
+ + " Please use createBond() instead if you do not have OOB data to pass.");
+ }
+ return createBondInternal(transport, remoteP192Data, remoteP256Data);
+ }
+
+ private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data,
+ @Nullable OobData remoteP256Data) {
final IBluetooth service = sService;
if (service == null) {
Log.w(TAG, "BT not enabled, createBondOutOfBand failed");
return false;
}
try {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- return service.createBond(this, transport, oobData, adapter.getOpPackageName());
+ return service.createBond(this, transport, remoteP192Data, remoteP256Data,
+ BluetoothAdapter.getDefaultAdapter().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
@@ -1380,27 +1397,6 @@
}
/**
- * Set the Out Of Band data for a remote device to be used later
- * in the pairing mechanism. Users can obtain this data through other
- * trusted channels
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
- *
- * @param hash Simple Secure pairing hash
- * @param randomizer The random key obtained using OOB
- * @return false on error; true otherwise
- * @hide
- */
- public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
- //TODO(BT)
- /*
- try {
- return sService.setDeviceOutOfBandData(this, hash, randomizer);
- } catch (RemoteException e) {Log.e(TAG, "", e);} */
- return false;
- }
-
- /**
* Cancel an in-progress bonding request started with {@link #createBond}.
*
* @return true on success, false on error
diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java
index 0d0c6ab..08d694e 100644
--- a/core/java/android/bluetooth/OobData.java
+++ b/core/java/android/bluetooth/OobData.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,88 +16,949 @@
package android.bluetooth;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
+import java.lang.IllegalArgumentException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Out Of Band Data for Bluetooth device pairing.
*
* <p>This object represents optional data obtained from a remote device through
- * an out-of-band channel (eg. NFC).
+ * an out-of-band channel (eg. NFC, QR).
+ *
+ * <p>References:
+ * NFC AD Forum SSP 1.1 (AD)
+ * {@link https://members.nfc-forum.org//apps/group_public/download.php/24620/NFCForum-AD-BTSSP_1_1.pdf}
+ * Core Specification Supplement (CSS) V9
+ *
+ * <p>There are several BR/EDR Examples
+ *
+ * <p>Negotiated Handover:
+ * Bluetooth Carrier Configuration Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Simple Pairing Hash C
+ * - Simple Pairing Randomizer R
+ * - Service Class UUID
+ * - Bluetooth Local Name
+ *
+ * <p>Static Handover:
+ * Bluetooth Carrier Configuration Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Service Class UUID
+ * - Bluetooth Local Name
+ *
+ * <p>Simplified Tag Format for Single BT Carrier:
+ * Bluetooth OOB Data Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Service Class UUID
+ * - Bluetooth Local Name
*
* @hide
*/
-public class OobData implements Parcelable {
- private byte[] mLeBluetoothDeviceAddress;
- private byte[] mSecurityManagerTk;
- private byte[] mLeSecureConnectionsConfirmation;
- private byte[] mLeSecureConnectionsRandom;
+@SystemApi
+public final class OobData implements Parcelable {
- public byte[] getLeBluetoothDeviceAddress() {
- return mLeBluetoothDeviceAddress;
+ private static final String TAG = "OobData";
+ /** The {@link OobData#mClassicLength} may be. (AD 3.1.1) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int OOB_LENGTH_OCTETS = 2;
+ /**
+ * The length for the {@link OobData#mDeviceAddressWithType}(6) and Address Type(1).
+ * (AD 3.1.2) (CSS 1.6.2)
+ * @hide
+ */
+ @SystemApi
+ public static final int DEVICE_ADDRESS_OCTETS = 7;
+ /** The Class of Device is 3 octets. (AD 3.1.3) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int CLASS_OF_DEVICE_OCTETS = 3;
+ /** The Confirmation data must be 16 octets. (AD 3.2.2) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int CONFIRMATION_OCTETS = 16;
+ /** The Randomizer data must be 16 octets. (AD 3.2.3) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int RANDOMIZER_OCTETS = 16;
+ /** The LE Device Role length is 1 octet. (AD 3.3.2) (CSS 1.17) @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_OCTETS = 1;
+ /** The {@link OobData#mLeTemporaryKey} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_TK_OCTETS = 16;
+ /** The {@link OobData#mLeAppearance} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_APPEARANCE_OCTETS = 2;
+ /** The {@link OobData#mLeFlags} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_DEVICE_FLAG_OCTETS = 1; // 1 octet to hold the 0-4 value.
+
+ // Le Roles
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "LE_DEVICE_ROLE_" },
+ value = {
+ LE_DEVICE_ROLE_PERIPHERAL_ONLY,
+ LE_DEVICE_ROLE_CENTRAL_ONLY,
+ LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL,
+ LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL
+ }
+ )
+ public @interface LeRole {}
+
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0x00;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 0x01;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 0x02;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 0x03;
+
+ // Le Flags
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "LE_FLAG_" },
+ value = {
+ LE_FLAG_LIMITED_DISCOVERY_MODE,
+ LE_FLAG_GENERAL_DISCOVERY_MODE,
+ LE_FLAG_BREDR_NOT_SUPPORTED,
+ LE_FLAG_SIMULTANEOUS_CONTROLLER,
+ LE_FLAG_SIMULTANEOUS_HOST
+ }
+ )
+ public @interface LeFlag {}
+
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0x00;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 0x01;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 0x02;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 0x03;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_SIMULTANEOUS_HOST = 0x04;
+
+ /**
+ * Main creation method for creating a Classic version of {@link OobData}.
+ *
+ * <p>This object will allow the caller to call {@link ClassicBuilder#build()}
+ * to build the data object or add any option information to the builder.
+ *
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * @param classicLength byte array representing the length of data from 8-65535 across 2
+ * octets (0xXXXX).
+ * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+ * that owns the OOB data. (i.e. the originator) [6 octets]
+ *
+ * @return a Classic Builder instance with all the given data set or null.
+ *
+ * @throws IllegalArgumentException if any of the values fail to be set.
+ * @throws NullPointerException if any argument is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public static ClassicBuilder createClassicBuilder(@NonNull byte[] confirmationHash,
+ @NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType) {
+ return new ClassicBuilder(confirmationHash, classicLength, deviceAddressWithType);
}
/**
- * Sets the LE Bluetooth Device Address value to be used during LE pairing.
- * The value shall be 7 bytes. Please see Bluetooth CSSv6, Part A 1.16 for
- * a detailed description.
+ * Main creation method for creating a LE version of {@link OobData}.
+ *
+ * <p>This object will allow the caller to call {@link LeBuilder#build()}
+ * to build the data object or add any option information to the builder.
+ *
+ * @param deviceAddressWithType the LE device address plus the address type (7 octets);
+ * not null.
+ * @param leDeviceRole whether the device supports Peripheral, Central,
+ * Both including preference; not null. (1 octet)
+ * @param confirmationHash Array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is
+ * required for pairing OOB.
+ *
+ * <p>Possible LE Device Role Values:
+ * 0x00 Only Peripheral supported
+ * 0x01 Only Central supported
+ * 0x02 Central & Peripheral supported; Peripheral Preferred
+ * 0x03 Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @return a LeBuilder instance with all the given data set or null.
+ *
+ * @throws IllegalArgumentException if any of the values fail to be set.
+ * @throws NullPointerException if any argument is null.
+ *
+ * @hide
*/
- public void setLeBluetoothDeviceAddress(byte[] leBluetoothDeviceAddress) {
- mLeBluetoothDeviceAddress = leBluetoothDeviceAddress;
- }
-
- public byte[] getSecurityManagerTk() {
- return mSecurityManagerTk;
+ @NonNull
+ @SystemApi
+ public static LeBuilder createLeBuilder(@NonNull byte[] confirmationHash,
+ @NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole) {
+ return new LeBuilder(confirmationHash, deviceAddressWithType, leDeviceRole);
}
/**
- * Sets the Temporary Key value to be used by the LE Security Manager during
- * LE pairing. The value shall be 16 bytes. Please see Bluetooth CSSv6,
- * Part A 1.8 for a detailed description.
+ * Builds an {@link OobData} object and validates that the required combination
+ * of values are present to create the LE specific OobData type.
+ *
+ * @hide
*/
- public void setSecurityManagerTk(byte[] securityManagerTk) {
- mSecurityManagerTk = securityManagerTk;
+ @SystemApi
+ public static final class LeBuilder {
+
+ /**
+ * It is recommended that this Hash C is generated anew for each
+ * pairing.
+ *
+ * <p>It should be noted that on passive NFC this isn't possible as the data is static
+ * and immutable.
+ */
+ private byte[] mConfirmationHash = null;
+
+ /**
+ * Optional, but adds more validity to the pairing.
+ *
+ * <p>If not present a value of 0 is assumed.
+ */
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+
+ /**
+ * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+ *
+ * <p>This is the name that may be displayed to the device user as part of the UI.
+ */
+ private byte[] mDeviceName = null;
+
+ /**
+ * Sets the Bluetooth Device name to be used for UI purposes.
+ *
+ * <p>Optional attribute.
+ *
+ * @param deviceName byte array representing the name, may be 0 in length, not null.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws NullPointerException if deviceName is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setDeviceName(@NonNull byte[] deviceName) {
+ Preconditions.checkNotNull(deviceName);
+ this.mDeviceName = deviceName;
+ return this;
+ }
+
+ /**
+ * The Bluetooth Device Address is the address to which the OOB data belongs.
+ *
+ * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+ *
+ * <p> Address is encoded in Little Endian order.
+ *
+ * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+ */
+ private final byte[] mDeviceAddressWithType;
+
+ /**
+ * During an LE connection establishment, one must be in the Peripheral mode and the other
+ * in the Central role.
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ */
+ private final @LeRole int mLeDeviceRole;
+
+ /**
+ * Temporary key value from the Security Manager.
+ *
+ * <p> Must be {@link LE_TK_OCTETS} in size
+ */
+ private byte[] mLeTemporaryKey = null;
+
+ /**
+ * Defines the representation of the external appearance of the device.
+ *
+ * <p>For example, a mouse, remote control, or keyboard.
+ *
+ * <p>Used for visual on discovering device to represent icon/string/etc...
+ */
+ private byte[] mLeAppearance = null;
+
+ /**
+ * Contains which discoverable mode to use, BR/EDR support and capability.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller).
+ * Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * <b>0x05- 0x07 Reserved</b>
+ */
+ private @LeFlag int mLeFlags = LE_FLAG_GENERAL_DISCOVERY_MODE; // Invalid default
+
+ /**
+ * Constructing an OobData object for use with LE requires
+ * a LE Device Address and LE Device Role as well as the Confirmation
+ * and optionally, the Randomizer, however it is recommended to use.
+ *
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+ * octets of data. Data is derived from controller/host stack and is required for
+ * pairing OOB.
+ * @param deviceAddressWithType 7 bytes containing the 6 byte address with the 1 byte
+ * address type.
+ * @param leDeviceRole indicating device's role and preferences (Central or Peripheral)
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @throws IllegalArgumentException if deviceAddressWithType is not
+ * {@link LE_DEVICE_ADDRESS_OCTETS} octets
+ * @throws NullPointerException if any argument is null.
+ */
+ private LeBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] deviceAddressWithType,
+ @LeRole int leDeviceRole) {
+ Preconditions.checkNotNull(confirmationHash);
+ Preconditions.checkNotNull(deviceAddressWithType);
+ if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.CONFIRMATION_OCTETS + " octets in length.");
+ }
+ this.mConfirmationHash = confirmationHash;
+ if (deviceAddressWithType.length != OobData.DEVICE_ADDRESS_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.DEVICE_ADDRESS_OCTETS+ " octets in length.");
+ }
+ this.mDeviceAddressWithType = deviceAddressWithType;
+ if (leDeviceRole < LE_DEVICE_ROLE_PERIPHERAL_ONLY
+ || leDeviceRole > LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL) {
+ throw new IllegalArgumentException("leDeviceRole must be a valid value.");
+ }
+ this.mLeDeviceRole = leDeviceRole;
+ }
+
+ /**
+ * Sets the Temporary Key value to be used by the LE Security Manager during
+ * LE pairing.
+ *
+ * @param leTemporaryKey byte array that shall be 16 bytes. Please see Bluetooth CSSv6,
+ * Part A 1.8 for a detailed description.
+ *
+ * @return {@link OobData#Builder}
+ *
+ * @throws IllegalArgumentException if the leTemporaryKey is an invalid format.
+ * @throws NullinterException if leTemporaryKey is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setLeTemporaryKey(@NonNull byte[] leTemporaryKey) {
+ Preconditions.checkNotNull(leTemporaryKey);
+ if (leTemporaryKey.length != LE_TK_OCTETS) {
+ throw new IllegalArgumentException("leTemporaryKey must be "
+ + LE_TK_OCTETS + " octets in length.");
+ }
+ this.mLeTemporaryKey = leTemporaryKey;
+ return this;
+ }
+
+ /**
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+ *
+ * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+ * @throws NullPointerException if randomizerHash is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+ Preconditions.checkNotNull(randomizerHash);
+ if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+ throw new IllegalArgumentException("randomizerHash must be "
+ + OobData.RANDOMIZER_OCTETS + " octets in length.");
+ }
+ this.mRandomizerHash = randomizerHash;
+ return this;
+ }
+
+ /**
+ * Sets the LE Flags necessary for the pairing scenario or discovery mode.
+ *
+ * @param leFlags enum value representing the 1 octet of data about discovery modes.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller) Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * 0x05- 0x07 Reserved
+ *
+ * @throws IllegalArgumentException for invalid flag
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setLeFlags(@LeFlag int leFlags) {
+ if (leFlags < LE_FLAG_LIMITED_DISCOVERY_MODE || leFlags > LE_FLAG_SIMULTANEOUS_HOST) {
+ throw new IllegalArgumentException("leFlags must be a valid value.");
+ }
+ this.mLeFlags = leFlags;
+ return this;
+ }
+
+ /**
+ * Validates and builds the {@link OobData} object for LE Security.
+ *
+ * @return {@link OobData} with given builder values
+ *
+ * @throws IllegalStateException if either of the 2 required fields were not set.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public OobData build() {
+ final OobData oob =
+ new OobData(this.mDeviceAddressWithType, this.mLeDeviceRole,
+ this.mConfirmationHash);
+
+ // If we have values, set them, otherwise use default
+ oob.mLeTemporaryKey =
+ (this.mLeTemporaryKey != null) ? this.mLeTemporaryKey : oob.mLeTemporaryKey;
+ oob.mLeAppearance = (this.mLeAppearance != null)
+ ? this.mLeAppearance : oob.mLeAppearance;
+ oob.mLeFlags = (this.mLeFlags != 0xF) ? this.mLeFlags : oob.mLeFlags;
+ oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+ oob.mRandomizerHash = this.mRandomizerHash;
+ return oob;
+ }
}
- public byte[] getLeSecureConnectionsConfirmation() {
- return mLeSecureConnectionsConfirmation;
+ /**
+ * Builds an {@link OobData} object and validates that the required combination
+ * of values are present to create the Classic specific OobData type.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class ClassicBuilder {
+ // Used by both Classic and LE
+ /**
+ * It is recommended that this Hash C is generated anew for each
+ * pairing.
+ *
+ * <p>It should be noted that on passive NFC this isn't possible as the data is static
+ * and immutable.
+ *
+ * @hide
+ */
+ private byte[] mConfirmationHash = null;
+
+ /**
+ * Optional, but adds more validity to the pairing.
+ *
+ * <p>If not present a value of 0 is assumed.
+ *
+ * @hide
+ */
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+
+ /**
+ * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+ *
+ * <p>This is the name that may be displayed to the device user as part of the UI.
+ *
+ * @hide
+ */
+ private byte[] mDeviceName = null;
+
+ /**
+ * This length value provides the absolute length of total OOB data block used for
+ * Bluetooth BR/EDR
+ *
+ * <p>OOB communication, which includes the length field itself and the Bluetooth
+ * Device Address.
+ *
+ * <p>The minimum length that may be represented in this field is 8.
+ *
+ * @hide
+ */
+ private final byte[] mClassicLength;
+
+ /**
+ * The Bluetooth Device Address is the address to which the OOB data belongs.
+ *
+ * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+ *
+ * <p> Address is encoded in Little Endian order.
+ *
+ * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+ *
+ * @hide
+ */
+ private final byte[] mDeviceAddressWithType;
+
+ /**
+ * Class of Device information is to be used to provide a graphical representation
+ * to the user as part of UI involving operations.
+ *
+ * <p>This is not to be used to determine a particular service can be used.
+ *
+ * <p>The length MUST be {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ *
+ * @hide
+ */
+ private byte[] mClassOfDevice = null;
+
+ /**
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+ * octets of data. Data is derived from controller/host stack and is required for pairing
+ * OOB.
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required
+ * for pairing OOB. Also, randomizerHash may be all 0s or null in which case
+ * it becomes all 0s.
+ * @param classicLength byte array representing the length of data from 8-65535 across 2
+ * octets (0xXXXX). Inclusive of this value in the length.
+ * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+ * that owns the OOB data. (i.e. the originator) [7 octets] this includes the Address Type
+ * as the last octet.
+ *
+ * @throws IllegalArgumentException if any value is not the correct length
+ * @throws NullPointerException if anything passed is null
+ *
+ * @hide
+ */
+ private ClassicBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] classicLength,
+ @NonNull byte[] deviceAddressWithType) {
+ Preconditions.checkNotNull(confirmationHash);
+ Preconditions.checkNotNull(classicLength);
+ Preconditions.checkNotNull(deviceAddressWithType);
+ if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.CONFIRMATION_OCTETS + " octets in length.");
+ }
+ this.mConfirmationHash = confirmationHash;
+ if (classicLength.length != OOB_LENGTH_OCTETS) {
+ throw new IllegalArgumentException("classicLength must be "
+ + OOB_LENGTH_OCTETS + " octets in length.");
+ }
+ this.mClassicLength = classicLength;
+ if (deviceAddressWithType.length != DEVICE_ADDRESS_OCTETS) {
+ throw new IllegalArgumentException("deviceAddressWithType must be "
+ + DEVICE_ADDRESS_OCTETS + " octets in length.");
+ }
+ this.mDeviceAddressWithType = deviceAddressWithType;
+ }
+
+ /**
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+ *
+ * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+ * @throws NullPointerException if randomizerHash is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+ Preconditions.checkNotNull(randomizerHash);
+ if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+ throw new IllegalArgumentException("randomizerHash must be "
+ + OobData.RANDOMIZER_OCTETS + " octets in length.");
+ }
+ this.mRandomizerHash = randomizerHash;
+ return this;
+ }
+
+ /**
+ * Sets the Bluetooth Device name to be used for UI purposes.
+ *
+ * <p>Optional attribute.
+ *
+ * @param deviceName byte array representing the name, may be 0 in length, not null.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws NullPointerException if deviceName is null
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setDeviceName(@NonNull byte[] deviceName) {
+ Preconditions.checkNotNull(deviceName);
+ this.mDeviceName = deviceName;
+ return this;
+ }
+
+ /**
+ * Sets the Bluetooth Class of Device; used for UI purposes only.
+ *
+ * <p>Not an indicator of available services!
+ *
+ * <p>Optional attribute.
+ *
+ * @param classOfDevice byte array of {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws IllegalArgumentException if length is not equal to
+ * {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ * @throws NullPointerException if classOfDevice is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setClassOfDevice(@NonNull byte[] classOfDevice) {
+ Preconditions.checkNotNull(classOfDevice);
+ if (classOfDevice.length != OobData.CLASS_OF_DEVICE_OCTETS) {
+ throw new IllegalArgumentException("classOfDevice must be "
+ + OobData.CLASS_OF_DEVICE_OCTETS + " octets in length.");
+ }
+ this.mClassOfDevice = classOfDevice;
+ return this;
+ }
+
+ /**
+ * Validates and builds the {@link OobDat object for Classic Security.
+ *
+ * @return {@link OobData} with previously given builder values.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public OobData build() {
+ final OobData oob =
+ new OobData(this.mClassicLength, this.mDeviceAddressWithType,
+ this.mConfirmationHash);
+ // If we have values, set them, otherwise use default
+ oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+ oob.mClassOfDevice = (this.mClassOfDevice != null)
+ ? this.mClassOfDevice : oob.mClassOfDevice;
+ oob.mRandomizerHash = this.mRandomizerHash;
+ return oob;
+ }
}
- public void setLeSecureConnectionsConfirmation(byte[] leSecureConnectionsConfirmation) {
- mLeSecureConnectionsConfirmation = leSecureConnectionsConfirmation;
+ // Members (Defaults for Optionals must be set or Parceling fails on NPE)
+ // Both
+ private final byte[] mDeviceAddressWithType;
+ private final byte[] mConfirmationHash;
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+ // Default the name to "Bluetooth Device"
+ private byte[] mDeviceName = new byte[] {
+ // Bluetooth
+ 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68,
+ // <space>Device
+ 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65
+ };
+
+ // Classic
+ private final byte[] mClassicLength;
+ private byte[] mClassOfDevice = new byte[CLASS_OF_DEVICE_OCTETS];
+
+ // LE
+ private final @LeRole int mLeDeviceRole;
+ private byte[] mLeTemporaryKey = new byte[LE_TK_OCTETS];
+ private byte[] mLeAppearance = new byte[LE_APPEARANCE_OCTETS];
+ private @LeFlag int mLeFlags = LE_FLAG_LIMITED_DISCOVERY_MODE;
+
+ /**
+ * @return byte array representing the MAC address of a bluetooth device.
+ * The Address is 6 octets long with a 1 octet address type associated with the address.
+ *
+ * <p>For classic this will be 6 byte address plus the default of PUBLIC_ADDRESS Address Type.
+ * For LE there are more choices for Address Type.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getDeviceAddressWithType() {
+ return mDeviceAddressWithType;
}
- public byte[] getLeSecureConnectionsRandom() {
- return mLeSecureConnectionsRandom;
+ /**
+ * @return byte array representing the confirmationHash value
+ * which is used to confirm the identity to the controller.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getConfirmationHash() {
+ return mConfirmationHash;
}
- public void setLeSecureConnectionsRandom(byte[] leSecureConnectionsRandom) {
- mLeSecureConnectionsRandom = leSecureConnectionsRandom;
+ /**
+ * @return byte array representing the randomizerHash value
+ * which is used to verify the identity of the controller.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getRandomizerHash() {
+ return mRandomizerHash;
}
- public OobData() {
+ /**
+ * @return Device Name used for displaying name in UI.
+ *
+ * <p>Also, this will be populated with the LE Local Name if the data is for LE.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * @return byte array representing the oob data length which is the length
+ * of all of the data including these octets.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getClassicLength() {
+ return mClassicLength;
+ }
+
+ /**
+ * @return byte array representing the class of device for UI display.
+ *
+ * <p>Does not indicate services available; for display only.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getClassOfDevice() {
+ return mClassOfDevice;
+ }
+
+ /**
+ * @return Temporary Key used for LE pairing.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getLeTemporaryKey() {
+ return mLeTemporaryKey;
+ }
+
+ /**
+ * @return Appearance used for LE pairing. For use in UI situations
+ * when determining what sort of icons or text to display regarding
+ * the device.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getLeAppearance() {
+ return mLeTemporaryKey;
+ }
+
+ /**
+ * @return Flags used to determing discoverable mode to use, BR/EDR Support, and Capability.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller).
+ * Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * <b>0x05- 0x07 Reserved</b>
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @LeFlag
+ public int getLeFlags() {
+ return mLeFlags;
+ }
+
+ /**
+ * @return the supported and preferred roles of the LE device.
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @LeRole
+ public int getLeDeviceRole() {
+ return mLeDeviceRole;
+ }
+
+ /**
+ * Classic Security Constructor
+ */
+ private OobData(@NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType,
+ @NonNull byte[] confirmationHash) {
+ mClassicLength = classicLength;
+ mDeviceAddressWithType = deviceAddressWithType;
+ mConfirmationHash = confirmationHash;
+ mLeDeviceRole = -1; // Satisfy final
+ }
+
+ /**
+ * LE Security Constructor
+ */
+ private OobData(@NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole,
+ @NonNull byte[] confirmationHash) {
+ mDeviceAddressWithType = deviceAddressWithType;
+ mLeDeviceRole = leDeviceRole;
+ mConfirmationHash = confirmationHash;
+ mClassicLength = new byte[OOB_LENGTH_OCTETS]; // Satisfy final
}
private OobData(Parcel in) {
- mLeBluetoothDeviceAddress = in.createByteArray();
- mSecurityManagerTk = in.createByteArray();
- mLeSecureConnectionsConfirmation = in.createByteArray();
- mLeSecureConnectionsRandom = in.createByteArray();
+ // Both
+ mDeviceAddressWithType = in.createByteArray();
+ mConfirmationHash = in.createByteArray();
+ mRandomizerHash = in.createByteArray();
+ mDeviceName = in.createByteArray();
+
+ // Classic
+ mClassicLength = in.createByteArray();
+ mClassOfDevice = in.createByteArray();
+
+ // LE
+ mLeDeviceRole = in.readInt();
+ mLeTemporaryKey = in.createByteArray();
+ mLeAppearance = in.createByteArray();
+ mLeFlags = in.readInt();
}
+ /**
+ * @hide
+ */
@Override
public int describeContents() {
return 0;
}
+ /**
+ * @hide
+ */
@Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeByteArray(mLeBluetoothDeviceAddress);
- out.writeByteArray(mSecurityManagerTk);
- out.writeByteArray(mLeSecureConnectionsConfirmation);
- out.writeByteArray(mLeSecureConnectionsRandom);
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ // Both
+ // Required
+ out.writeByteArray(mDeviceAddressWithType);
+ // Required
+ out.writeByteArray(mConfirmationHash);
+ // Optional
+ out.writeByteArray(mRandomizerHash);
+ // Optional
+ out.writeByteArray(mDeviceName);
+
+ // Classic
+ // Required
+ out.writeByteArray(mClassicLength);
+ // Optional
+ out.writeByteArray(mClassOfDevice);
+
+ // LE
+ // Required
+ out.writeInt(mLeDeviceRole);
+ // Required
+ out.writeByteArray(mLeTemporaryKey);
+ // Optional
+ out.writeByteArray(mLeAppearance);
+ // Optional
+ out.writeInt(mLeFlags);
}
+ // For Parcelable
public static final @android.annotation.NonNull Parcelable.Creator<OobData> CREATOR =
new Parcelable.Creator<OobData>() {
public OobData createFromParcel(Parcel in) {
@@ -108,4 +969,47 @@
return new OobData[size];
}
};
+
+ /**
+ * @return a {@link String} representation of the OobData object.
+ *
+ * @hide
+ */
+ @Override
+ @NonNull
+ public String toString() {
+ return "OobData: \n\t"
+ // Both
+ + "Device Address With Type: " + toHexString(mDeviceAddressWithType) + "\n\t"
+ + "Confirmation: " + toHexString(mConfirmationHash) + "\n\t"
+ + "Randomizer: " + toHexString(mRandomizerHash) + "\n\t"
+ + "Device Name: " + toHexString(mDeviceName) + "\n\t"
+ // Classic
+ + "OobData Length: " + toHexString(mClassicLength) + "\n\t"
+ + "Class of Device: " + toHexString(mClassOfDevice) + "\n\t"
+ // LE
+ + "LE Device Role: " + toHexString(mLeDeviceRole) + "\n\t"
+ + "LE Temporary Key: " + toHexString(mLeTemporaryKey) + "\n\t"
+ + "LE Appearance: " + toHexString(mLeAppearance) + "\n\t"
+ + "LE Flags: " + toHexString(mLeFlags) + "\n\t";
+ }
+
+ @NonNull
+ private String toHexString(@NonNull int b) {
+ return toHexString(new byte[] {(byte) b});
+ }
+
+ @NonNull
+ private String toHexString(@NonNull byte b) {
+ return toHexString(new byte[] {b});
+ }
+
+ @NonNull
+ private String toHexString(@NonNull byte[] array) {
+ StringBuilder builder = new StringBuilder(array.length * 2);
+ for (byte b: array) {
+ builder.append(String.format("%02x", b));
+ }
+ return builder.toString();
+ }
}
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index c0c1aa1..794b512 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -29,6 +29,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
/**
* Represents a scan record from Bluetooth LE scan.
@@ -168,6 +169,27 @@
return mBytes;
}
+ /**
+ * Test if any fields contained inside this scan record are matched by the
+ * given matcher.
+ *
+ * @hide
+ */
+ public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) {
+ int pos = 0;
+ while (pos < mBytes.length) {
+ final int length = mBytes[pos] & 0xFF;
+ if (length == 0) {
+ break;
+ }
+ if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) {
+ return true;
+ }
+ pos += length + 1;
+ }
+ return false;
+ }
+
private ScanRecord(List<ParcelUuid> serviceUuids,
List<ParcelUuid> serviceSolicitationUuids,
SparseArray<byte[]> manufacturerData,
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index f3ecbf6..6ab1975 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,6 +21,7 @@
import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentResolver.SCHEME_FILE;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.AssetFileDescriptor;
@@ -38,6 +39,7 @@
import android.text.style.URLSpan;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextLinks;
import com.android.internal.util.ArrayUtils;
@@ -204,6 +206,7 @@
Uri mUri;
// Additional activity info resolved by the system
ActivityInfo mActivityInfo;
+ private TextLinks mTextLinks;
/** @hide */
public Item(Item other) {
@@ -332,6 +335,29 @@
}
/**
+ * Returns the results of text classification run on the raw text contained in this item,
+ * if it was performed, and if any entities were found in the text. Classification is
+ * generally only performed on the first item in clip data, and only if the text is below a
+ * certain length.
+ *
+ * <p>Returns {@code null} if classification was not performed, or if no entities were
+ * found in the text.
+ *
+ * @see ClipDescription#getConfidenceScore(String)
+ */
+ @Nullable
+ public TextLinks getTextLinks() {
+ return mTextLinks;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTextLinks(TextLinks textLinks) {
+ mTextLinks = textLinks;
+ }
+
+ /**
* Turn this item into text, regardless of the type of data it
* actually contains.
*
@@ -1183,6 +1209,7 @@
dest.writeTypedObject(item.mIntent, flags);
dest.writeTypedObject(item.mUri, flags);
dest.writeTypedObject(item.mActivityInfo, flags);
+ dest.writeTypedObject(item.mTextLinks, flags);
}
}
@@ -1201,8 +1228,10 @@
Intent intent = in.readTypedObject(Intent.CREATOR);
Uri uri = in.readTypedObject(Uri.CREATOR);
ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
+ TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
Item item = new Item(text, htmlText, intent, uri);
item.setActivityInfo(info);
+ item.setTextLinks(textLinks);
mItems.add(item);
}
}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index d48f832..f49362e 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -16,16 +16,25 @@
package android.content;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Map;
/**
* Meta-data describing the contents of a {@link ClipData}. Provides enough
@@ -115,12 +124,39 @@
*/
public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value =
+ { CLASSIFICATION_NOT_COMPLETE, CLASSIFICATION_NOT_PERFORMED, CLASSIFICATION_COMPLETE})
+ @interface ClassificationStatus {}
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification has not been
+ * completed on the associated clip. This will be always be the case if the clip has not been
+ * copied to clipboard, or if there is no associated clip.
+ */
+ public static final int CLASSIFICATION_NOT_COMPLETE = 1;
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification was not and will
+ * not be performed on the associated clip. This may be the case if the clip does not contain
+ * text in its first item, or if the text is too long.
+ */
+ public static final int CLASSIFICATION_NOT_PERFORMED = 2;
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification has been
+ * completed.
+ */
+ public static final int CLASSIFICATION_COMPLETE = 3;
final CharSequence mLabel;
private final ArrayList<String> mMimeTypes;
private PersistableBundle mExtras;
private long mTimeStamp;
private boolean mIsStyledText;
+ private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>();
+ private int mClassificationStatus = CLASSIFICATION_NOT_COMPLETE;
/**
* Create a new clip.
@@ -346,6 +382,61 @@
mIsStyledText = isStyledText;
}
+ /**
+ * Sets the current status of text classification for the associated clip.
+ *
+ * @hide
+ */
+ public void setClassificationStatus(@ClassificationStatus int status) {
+ mClassificationStatus = status;
+ }
+
+ /**
+ * Returns a score indicating confidence that an instance of the given entity is present in the
+ * first item of the clip data, if that item is plain text and text classification has been
+ * performed. The value ranges from 0 (low confidence) to 1 (high confidence). 0 indicates that
+ * the entity was not found in the classified text.
+ *
+ * <p>Entities should be as defined in the {@link TextClassifier} class, such as
+ * {@link TextClassifier#TYPE_ADDRESS}, {@link TextClassifier#TYPE_URL}, or
+ * {@link TextClassifier#TYPE_EMAIL}.
+ *
+ * <p>If the result is positive for any entity, the full classification result as a
+ * {@link TextLinks} object may be obtained using the {@link ClipData.Item#getTextLinks()}
+ * method.
+ *
+ * @throws IllegalStateException if {@link #getClassificationStatus()} is not
+ * {@link #CLASSIFICATION_COMPLETE}
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@NonNull @TextClassifier.EntityType String entity) {
+ if (mClassificationStatus != CLASSIFICATION_COMPLETE) {
+ throw new IllegalStateException("Classification not complete");
+ }
+ return mEntityConfidence.getOrDefault(entity, 0f);
+ }
+
+ /**
+ * Returns {@link #CLASSIFICATION_COMPLETE} if text classification has been performed on the
+ * associated {@link ClipData}. If this is the case then {@link #getConfidenceScore} may be used
+ * to retrieve information about entities within the text. Otherwise, returns
+ * {@link #CLASSIFICATION_NOT_COMPLETE} if classification has not yet returned results, or
+ * {@link #CLASSIFICATION_NOT_PERFORMED} if classification was not attempted (e.g. because the
+ * text was too long).
+ */
+ public @ClassificationStatus int getClassificationStatus() {
+ return mClassificationStatus;
+ }
+
+ /**
+ * @hide
+ */
+ public void setConfidenceScores(Map<String, Float> confidences) {
+ mEntityConfidence.clear();
+ mEntityConfidence.putAll(confidences);
+ mClassificationStatus = CLASSIFICATION_COMPLETE;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
@@ -451,6 +542,23 @@
dest.writePersistableBundle(mExtras);
dest.writeLong(mTimeStamp);
dest.writeBoolean(mIsStyledText);
+ dest.writeInt(mClassificationStatus);
+ dest.writeBundle(confidencesToBundle());
+ }
+
+ private Bundle confidencesToBundle() {
+ Bundle bundle = new Bundle();
+ int size = mEntityConfidence.size();
+ for (int i = 0; i < size; i++) {
+ bundle.putFloat(mEntityConfidence.keyAt(i), mEntityConfidence.valueAt(i));
+ }
+ return bundle;
+ }
+
+ private void readBundleToConfidences(Bundle bundle) {
+ for (String key : bundle.keySet()) {
+ mEntityConfidence.put(key, bundle.getFloat(key));
+ }
}
ClipDescription(Parcel in) {
@@ -459,6 +567,8 @@
mExtras = in.readPersistableBundle();
mTimeStamp = in.readLong();
mIsStyledText = in.readBoolean();
+ mClassificationStatus = in.readInt();
+ readBundleToConfidences(in.readBundle());
}
public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 49248b5..73b4f62 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -2621,6 +2621,48 @@
return !TextUtils.isEmpty(uri.getUserInfo());
}
+ /**
+ * Returns the given content URI explicitly associated with the given {@link UserHandle}.
+ *
+ * @param contentUri The content URI to be associated with a user handle.
+ * @param userHandle The user handle with which to associate the URI.
+ *
+ * @throws IllegalArgumentException if
+ * <ul>
+ * <li>the given URI is not content URI (a content URI has {@link Uri#getScheme} equal to
+ * {@link ContentResolver.SCHEME_CONTENT}) or</li>
+ * <li>the given URI is already explicitly associated with a {@link UserHandle}, which is
+ * different than the given one.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static Uri createContentUriAsUser(
+ @NonNull Uri contentUri, @NonNull UserHandle userHandle) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
+ throw new IllegalArgumentException(String.format(
+ "Given URI [%s] is not a content URI: ", contentUri));
+ }
+
+ int userId = userHandle.getIdentifier();
+ if (uriHasUserId(contentUri)) {
+ if (String.valueOf(userId).equals(contentUri.getUserInfo())) {
+ return contentUri;
+ }
+ throw new IllegalArgumentException(String.format(
+ "Given URI [%s] already has a user ID, different from given user handle [%s]",
+ contentUri,
+ userId));
+ }
+
+ Uri.Builder builder = contentUri.buildUpon();
+ builder.encodedAuthority(
+ "" + userHandle.getIdentifier() + "@" + contentUri.getEncodedAuthority());
+ return builder.build();
+ }
+
/** @hide */
@UnsupportedAppUsage
public static Uri maybeAddUserId(Uri uri, int userId) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0509e3f..64ca92f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3531,6 +3531,7 @@
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
+ PAC_PROXY_SERVICE,
VCN_MANAGEMENT_SERVICE,
//@hide: IP_MEMORY_STORE_SERVICE,
IPSEC_SERVICE,
@@ -3713,6 +3714,9 @@
* usage statistics.
* <dt> {@link #HARDWARE_PROPERTIES_SERVICE} ("hardware_properties")
* <dd> A {@link android.os.HardwarePropertiesManager} for accessing hardware properties.
+ * <dt> {@link #DOMAIN_VERIFICATION_SERVICE} ("domain_verification")
+ * <dd> A {@link android.content.pm.verify.domain.DomainVerificationManager} for accessing
+ * web domain approval state.
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -3794,6 +3798,8 @@
* @see android.app.usage.NetworkStatsManager
* @see android.os.HardwarePropertiesManager
* @see #HARDWARE_PROPERTIES_SERVICE
+ * @see #DOMAIN_VERIFICATION_SERVICE
+ * @see android.content.pm.verify.domain.DomainVerificationManager
*/
public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
@@ -3813,7 +3819,8 @@
* {@link android.view.inputmethod.InputMethodManager},
* {@link android.app.UiModeManager}, {@link android.app.DownloadManager},
* {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler},
- * {@link android.app.usage.NetworkStatsManager}.
+ * {@link android.app.usage.NetworkStatsManager},
+ * {@link android.content.pm.verify.domain.DomainVerificationManager}.
* </p>
*
* <p>
@@ -3869,7 +3876,7 @@
* @see #getSystemService(String)
* @hide
*/
- public static final String POWER_STATS_SERVICE = "power_stats";
+ public static final String POWER_STATS_SERVICE = "powerstats";
/**
* Use with {@link #getSystemService(String)} to retrieve a
@@ -4131,6 +4138,17 @@
public static final String CONNECTIVITY_SERVICE = "connectivity";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.PacProxyManager} for handling management of
+ * pac proxy information.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.PacProxyManager
+ * @hide
+ */
+ public static final String PAC_PROXY_SERVICE = "pac_proxy";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link android.net.vcn.VcnManager}
* for managing Virtual Carrier Networks
*
@@ -4201,7 +4219,8 @@
* @see #getSystemService(String)
* @hide
*/
- @TestApi public static final String TEST_NETWORK_SERVICE = "test_network";
+ @TestApi @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final String TEST_NETWORK_SERVICE = "test_network";
/**
* Use with {@link #getSystemService(String)} to retrieve a {@link
@@ -4833,7 +4852,8 @@
* @hide
*/
@TestApi
- @SuppressLint("ServiceName") // TODO: This should be renamed to POWER_WHITELIST_SERVICE
+ @Deprecated
+ @SuppressLint("ServiceName")
public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
/**
@@ -4842,7 +4862,7 @@
* @see #getSystemService(String)
* @hide
*/
- @SystemApi
+ @TestApi
public static final String POWER_EXEMPTION_SERVICE = "power_exemption";
/**
@@ -5544,12 +5564,13 @@
public static final String GAME_SERVICE = "game";
/**
- * Use with {@link #getSystemService(String)} to access domain verification service.
+ * Use with {@link #getSystemService(String)} to access
+ * {@link android.content.pm.verify.domain.DomainVerificationManager} to retrieve approval and
+ * user state for declared web domains.
*
* @see #getSystemService(String)
- * @hide
+ * @see android.content.pm.verify.domain.DomainVerificationManager
*/
- @SystemApi
public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 04f93ca..96b8fbe 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -28,6 +28,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.AppGlobals;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
@@ -1901,6 +1902,20 @@
"android.intent.action.AUTO_REVOKE_PERMISSIONS";
/**
+ * Activity action: Launch UI to manage unused apps (hibernated apps).
+ *
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_UNUSED_APPS =
+ "android.intent.action.MANAGE_UNUSED_APPS";
+
+ /**
* Activity action: Launch UI to review permissions for an app.
* The system uses this intent if permission review for apps not
* supporting the new runtime permissions model is enabled. In
@@ -1940,8 +1955,8 @@
/**
* Activity action: Launch UI to show information about the usage
- * of a given permission. This action would be handled by apps that
- * want to show details about how and why given permission is being
+ * of a given permission group. This action would be handled by apps that
+ * want to show details about how and why given permission group is being
* used.
* <p>
* <strong>Important:</strong>You must protect the activity that handles
@@ -1951,7 +1966,7 @@
* activities that are not properly protected.
*
* <p>
- * Input: {@code android.intent.extra.PERMISSION_NAME} specifies the permission
+ * Input: {@link android.Manifest.permission_group} specifies the permission group
* for which the launched UI would be targeted.
* </p>
* <p>
@@ -2172,6 +2187,29 @@
"android.intent.action.REVIEW_PERMISSION_USAGE";
/**
+ * Activity action: Launch UI to review the timeline history of permissions.
+ * <p>
+ * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group name
+ * that will be displayed by the launched UI.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ * <p class="note">
+ * This requires {@link android.Manifest.permission#GRANT_RUNTIME_PERMISSIONS} permission.
+ * </p>
+ *
+ * @see #EXTRA_PERMISSION_GROUP_NAME
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_PERMISSION_HISTORY =
+ "android.intent.action.REVIEW_PERMISSION_HISTORY";
+
+ /**
* Activity action: Launch UI to review ongoing app uses of permissions.
* <p>
* Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent
@@ -3734,6 +3772,7 @@
* has just been stopped (which is no longer running).
* @hide
*/
+ @TestApi
public static final String ACTION_USER_STOPPED =
"android.intent.action.USER_STOPPED";
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 58f83a7..feb58a30 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -21,6 +21,7 @@
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
@@ -254,7 +255,7 @@
* @See {@link android.R.attr#maxAspectRatio}.
* @hide
*/
- public float maxAspectRatio;
+ private float mMaxAspectRatio;
/**
* Value indicating the minimum aspect ratio the activity supports.
@@ -263,7 +264,7 @@
* @See {@link android.R.attr#minAspectRatio}.
* @hide
*/
- public float minAspectRatio;
+ private float mMinAspectRatio;
/**
* Indicates that the activity works well with size changes like display changing size.
@@ -948,6 +949,57 @@
public @interface SizeChangesSupportMode {}
/**
+ * This change id is the gatekeeper for all treatments that force a given min aspect ratio.
+ * Enabling this change will allow the following min aspect ratio treatments to be applied:
+ * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
+ * OVERRIDE_MIN_ASPECT_RATIO_LARGE
+ *
+ * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's
+ * manifest will be overridden to the largest enabled aspect ratio treatment unless the app's
+ * manifest value is higher.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id
+
+ /**
+ * This change id sets the activity's min aspect ratio to a medium value as defined by
+ * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE.
+ *
+ * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // buganizer id
+
+ /** @hide Medium override aspect ratio, currently 3:2. */
+ @TestApi
+ public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 3 / 2f;
+
+ /**
+ * This change id sets the activity's min aspect ratio to a large value as defined by
+ * OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE.
+ *
+ * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_MIN_ASPECT_RATIO_LARGE = 180326787L; // buganizer id
+
+ /** @hide Large override aspect ratio, currently 16:9 */
+ @TestApi
+ public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 16 / 9f;
+
+ /**
* Convert Java change bits to native.
*
* @hide
@@ -1118,8 +1170,8 @@
requestedVrComponent = orig.requestedVrComponent;
rotationAnimation = orig.rotationAnimation;
colorMode = orig.colorMode;
- maxAspectRatio = orig.maxAspectRatio;
- minAspectRatio = orig.minAspectRatio;
+ mMaxAspectRatio = orig.mMaxAspectRatio;
+ mMinAspectRatio = orig.mMinAspectRatio;
supportsSizeChanges = orig.supportsSizeChanges;
attributionTags = orig.attributionTags;
}
@@ -1149,7 +1201,7 @@
* @hide
*/
public boolean hasFixedAspectRatio() {
- return maxAspectRatio != 0 || minAspectRatio != 0;
+ return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
}
/**
@@ -1262,6 +1314,58 @@
}
/** @hide */
+ public void setMaxAspectRatio(float maxAspectRatio) {
+ this.mMaxAspectRatio = maxAspectRatio;
+ }
+
+ /** @hide */
+ public float getMaxAspectRatio() {
+ return mMaxAspectRatio;
+ }
+
+ /** @hide */
+ public void setMinAspectRatio(float minAspectRatio) {
+ this.mMinAspectRatio = minAspectRatio;
+ }
+
+ /**
+ * Returns the min aspect ratio of this activity.
+ *
+ * This takes into account the minimum aspect ratio as defined in the app's manifest and
+ * possible overrides as per OVERRIDE_MIN_ASPECT_RATIO.
+ *
+ * In the rare cases where the manifest minimum aspect ratio is required, use
+ * {@code getManifestMinAspectRatio}.
+ * @hide
+ */
+ public float getMinAspectRatio() {
+ if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return mMinAspectRatio;
+ }
+
+ if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio);
+ }
+
+ if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio);
+ }
+
+ return mMinAspectRatio;
+ }
+
+ /** @hide */
+ public float getManifestMinAspectRatio() {
+ return mMinAspectRatio;
+ }
+
+ /** @hide */
@UnsupportedAppUsage
public static boolean isResizeableMode(int mode) {
return mode == RESIZE_MODE_RESIZEABLE
@@ -1360,11 +1464,14 @@
if (requestedVrComponent != null) {
pw.println(prefix + "requestedVrComponent=" + requestedVrComponent);
}
- if (maxAspectRatio != 0) {
- pw.println(prefix + "maxAspectRatio=" + maxAspectRatio);
+ if (getMaxAspectRatio() != 0) {
+ pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
}
- if (minAspectRatio != 0) {
- pw.println(prefix + "minAspectRatio=" + minAspectRatio);
+ if (getMinAspectRatio() != 0) {
+ pw.println(prefix + "minAspectRatio=" + getMinAspectRatio());
+ if (getManifestMinAspectRatio() != getMinAspectRatio()) {
+ pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio());
+ }
}
if (supportsSizeChanges) {
pw.println(prefix + "supportsSizeChanges=true");
@@ -1420,8 +1527,8 @@
dest.writeString8(requestedVrComponent);
dest.writeInt(rotationAnimation);
dest.writeInt(colorMode);
- dest.writeFloat(maxAspectRatio);
- dest.writeFloat(minAspectRatio);
+ dest.writeFloat(mMaxAspectRatio);
+ dest.writeFloat(mMinAspectRatio);
dest.writeBoolean(supportsSizeChanges);
dest.writeString8Array(attributionTags);
}
@@ -1540,8 +1647,8 @@
requestedVrComponent = source.readString8();
rotationAnimation = source.readInt();
colorMode = source.readInt();
- maxAspectRatio = source.readFloat();
- minAspectRatio = source.readFloat();
+ mMaxAspectRatio = source.readFloat();
+ mMinAspectRatio = source.readFloat();
supportsSizeChanges = source.readBoolean();
attributionTags = source.createString8Array();
}
diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
index 745c39b..79b70f2 100644
--- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl
+++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
@@ -23,32 +23,34 @@
oneway interface IDataLoaderStatusListener {
/** The DataLoader process died, binder disconnected or class destroyed. */
const int DATA_LOADER_DESTROYED = 0;
+ /** The system is in process of binding to the DataLoader. */
+ const int DATA_LOADER_BINDING = 1;
/** DataLoader process is running and bound to. */
- const int DATA_LOADER_BOUND = 1;
+ const int DATA_LOADER_BOUND = 2;
/** DataLoader has handled onCreate(). */
- const int DATA_LOADER_CREATED = 2;
+ const int DATA_LOADER_CREATED = 3;
/** DataLoader can receive missing pages and read pages notifications,
* and ready to provide data. */
- const int DATA_LOADER_STARTED = 3;
+ const int DATA_LOADER_STARTED = 4;
/** DataLoader no longer ready to provide data and is not receiving
* any notifications from IncFS. */
- const int DATA_LOADER_STOPPED = 4;
+ const int DATA_LOADER_STOPPED = 5;
/** DataLoader streamed everything necessary to continue installation. */
- const int DATA_LOADER_IMAGE_READY = 5;
+ const int DATA_LOADER_IMAGE_READY = 6;
/** Installation can't continue as DataLoader failed to stream necessary data. */
- const int DATA_LOADER_IMAGE_NOT_READY = 6;
+ const int DATA_LOADER_IMAGE_NOT_READY = 7;
/** DataLoader instance can't run at the moment, but might recover later.
* It's up to system to decide if the app is still usable. */
- const int DATA_LOADER_UNAVAILABLE = 7;
+ const int DATA_LOADER_UNAVAILABLE = 8;
/** DataLoader reports that this instance is invalid and can never be restored.
* Warning: this is a terminal status that data loader should use carefully and
* the system should almost never use - e.g. only if all recovery attempts
* fail and all retry limits are exceeded. */
- const int DATA_LOADER_UNRECOVERABLE = 8;
+ const int DATA_LOADER_UNRECOVERABLE = 9;
/** There are no known issues with the data stream. */
const int STREAM_HEALTHY = 0;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5e08399..5ff1124 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4890,8 +4890,8 @@
info.maxRecents = target.info.maxRecents;
info.windowLayout = target.info.windowLayout;
info.resizeMode = target.info.resizeMode;
- info.maxAspectRatio = target.info.maxAspectRatio;
- info.minAspectRatio = target.info.minAspectRatio;
+ info.setMaxAspectRatio(target.info.getMaxAspectRatio());
+ info.setMinAspectRatio(target.info.getManifestMinAspectRatio());
info.supportsSizeChanges = target.info.supportsSizeChanges;
info.requestedVrComponent = target.info.requestedVrComponent;
@@ -8157,7 +8157,7 @@
return;
}
- info.maxAspectRatio = maxAspectRatio;
+ info.setMaxAspectRatio(maxAspectRatio);
mHasMaxAspectRatio = true;
}
@@ -8173,7 +8173,7 @@
return;
}
- info.minAspectRatio = minAspectRatio;
+ info.setMinAspectRatio(minAspectRatio);
mHasMinAspectRatio = true;
}
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index fe8e4d7..6f07dd7 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -19,6 +19,7 @@
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -183,6 +184,17 @@
@SystemApi
public boolean handleAllWebDataURI;
+ /**
+ * Whether the resolved {@link IntentFilter} declares {@link Intent#CATEGORY_BROWSABLE} and is
+ * thus allowed to automatically resolve an {@link Intent} as it's assumed the action is safe
+ * for the user.
+ *
+ * Note that the above doesn't apply when this is the only result is returned in the candidate
+ * set, as the system will not prompt before opening the result. It only applies when there are
+ * multiple candidates.
+ */
+ private final boolean mAutoResolutionAllowed;
+
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public ComponentInfo getComponentInfo() {
@@ -364,8 +376,26 @@
&& INTENT_FORWARDER_ACTIVITY.equals(activityInfo.targetActivity);
}
+ /**
+ * @see #mAutoResolutionAllowed
+ * @hide
+ */
+ public boolean isAutoResolutionAllowed() {
+ return mAutoResolutionAllowed;
+ }
+
public ResolveInfo() {
targetUserId = UserHandle.USER_CURRENT;
+
+ // It's safer to assume that an unaware caller that constructs a ResolveInfo doesn't
+ // accidentally mark a result as auto resolveable.
+ mAutoResolutionAllowed = false;
+ }
+
+ /** @hide */
+ public ResolveInfo(boolean autoResolutionAllowed) {
+ targetUserId = UserHandle.USER_CURRENT;
+ mAutoResolutionAllowed = autoResolutionAllowed;
}
public ResolveInfo(ResolveInfo orig) {
@@ -386,6 +416,7 @@
system = orig.system;
targetUserId = orig.targetUserId;
handleAllWebDataURI = orig.handleAllWebDataURI;
+ mAutoResolutionAllowed = orig.mAutoResolutionAllowed;
isInstantAppAvailable = orig.isInstantAppAvailable;
}
@@ -450,6 +481,7 @@
dest.writeInt(noResourceId ? 1 : 0);
dest.writeInt(iconResourceId);
dest.writeInt(handleAllWebDataURI ? 1 : 0);
+ dest.writeInt(mAutoResolutionAllowed ? 1 : 0);
dest.writeInt(isInstantAppAvailable ? 1 : 0);
}
@@ -498,6 +530,7 @@
noResourceId = source.readInt() != 0;
iconResourceId = source.readInt();
handleAllWebDataURI = source.readInt() != 0;
+ mAutoResolutionAllowed = source.readInt() != 0;
isInstantAppAvailable = source.readInt() != 0;
}
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index b660a00..fdd2c2a 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -474,9 +474,9 @@
ai.screenOrientation = a.getScreenOrientation();
ai.resizeMode = a.getResizeMode();
Float maxAspectRatio = a.getMaxAspectRatio();
- ai.maxAspectRatio = maxAspectRatio != null ? maxAspectRatio : 0f;
+ ai.setMaxAspectRatio(maxAspectRatio != null ? maxAspectRatio : 0f);
Float minAspectRatio = a.getMinAspectRatio();
- ai.minAspectRatio = minAspectRatio != null ? minAspectRatio : 0f;
+ ai.setMinAspectRatio(minAspectRatio != null ? minAspectRatio : 0f);
ai.supportsSizeChanges = a.getSupportsSizeChanges();
ai.requestedVrComponent = a.getRequestedVrComponent();
ai.rotationAnimation = a.getRotationAnimation();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 29edd40..ba6416d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -34,6 +34,7 @@
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.os.Bundle;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -89,7 +90,7 @@
ParsingPackage addReqFeature(FeatureInfo reqFeature);
- ParsingPackage addRequestedPermission(String permission);
+ ParsingPackage addUsesPermission(ParsedUsesPermission parsedUsesPermission);
ParsingPackage addService(ParsedService parsedService);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 067787d..b3c26ab 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -44,6 +44,7 @@
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.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
@@ -71,6 +72,7 @@
import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
import java.security.PublicKey;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -227,8 +229,8 @@
protected List<String> adoptPermissions = emptyList();
@NonNull
- @DataClass.ParcelWith(ForInternedStringList.class)
- private List<String> requestedPermissions = emptyList();
+ private List<ParsedUsesPermission> usesPermissions = emptyList();
+
@NonNull
@DataClass.ParcelWith(ForInternedStringList.class)
private List<String> implicitPermissions = emptyList();
@@ -691,9 +693,8 @@
}
@Override
- public ParsingPackageImpl addRequestedPermission(String permission) {
- this.requestedPermissions = CollectionUtils.add(this.requestedPermissions,
- TextUtils.safeIntern(permission));
+ public ParsingPackageImpl addUsesPermission(ParsedUsesPermission permission) {
+ this.usesPermissions = CollectionUtils.add(this.usesPermissions, permission);
return this;
}
@@ -1134,7 +1135,7 @@
dest.writeByteArray(this.restrictUpdateHash);
dest.writeStringList(this.originalPackages);
sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
- sForInternedStringList.parcel(this.requestedPermissions, dest, flags);
+ dest.writeTypedList(this.usesPermissions);
sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
sForStringSet.parcel(this.upgradeKeySets, dest, flags);
dest.writeMap(this.keySetMapping);
@@ -1255,7 +1256,7 @@
this.restrictUpdateHash = in.createByteArray();
this.originalPackages = in.createStringArrayList();
this.adoptPermissions = sForInternedStringList.unparcel(in);
- this.requestedPermissions = sForInternedStringList.unparcel(in);
+ this.usesPermissions = in.createTypedArrayList(ParsedUsesPermission.CREATOR);
this.implicitPermissions = sForInternedStringList.unparcel(in);
this.upgradeKeySets = sForStringSet.unparcel(in);
this.keySetMapping = in.readHashMap(boot);
@@ -1551,11 +1552,23 @@
@NonNull
@Override
public List<String> getRequestedPermissions() {
+ final List<ParsedUsesPermission> usesPermissions = getUsesPermissions();
+ final int size = usesPermissions.size();
+ final List<String> requestedPermissions = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ requestedPermissions.add(usesPermissions.get(i).name);
+ }
return requestedPermissions;
}
@NonNull
@Override
+ public List<ParsedUsesPermission> getUsesPermissions() {
+ return usesPermissions;
+ }
+
+ @NonNull
+ @Override
public List<String> getImplicitPermissions() {
return implicitPermissions;
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index f7f3e19..9f52183 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -37,6 +37,7 @@
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.os.Bundle;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -45,6 +46,7 @@
import android.util.SparseIntArray;
import java.security.PublicKey;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -193,6 +195,14 @@
List<FeatureInfo> getReqFeatures();
/**
+ * @deprecated consider migrating to {@link #getUsesPermissions} which has
+ * more parsed details, such as flags
+ */
+ @NonNull
+ @Deprecated
+ List<String> getRequestedPermissions();
+
+ /**
* All the permissions declared. This is an effective set, and may include permissions
* transformed from split/migrated permissions from previous versions, so may not be exactly
* what the package declares in its manifest.
@@ -200,7 +210,7 @@
* @see R.styleable#AndroidManifestUsesPermission
*/
@NonNull
- List<String> getRequestedPermissions();
+ List<ParsedUsesPermission> getUsesPermissions();
/**
* Returns the properties set on the application
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 0c033fd..2be0157 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -67,6 +67,7 @@
import android.content.pm.parsing.component.ParsedProviderUtils;
import android.content.pm.parsing.component.ParsedService;
import android.content.pm.parsing.component.ParsedServiceUtils;
+import android.content.pm.parsing.component.ParsedUsesPermission;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseInput.DeferredError;
import android.content.pm.parsing.result.ParseResult;
@@ -119,6 +120,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
@@ -1206,6 +1208,10 @@
requiredNotFeatures.add(feature);
}
+ final int usesPermissionFlags = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_usesPermissionFlags,
+ 0);
+
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1270,14 +1276,31 @@
}
}
- if (!pkg.getRequestedPermissions().contains(name)) {
- pkg.addRequestedPermission(name.intern());
- } else {
- Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
- + name + " in package: " + pkg.getPackageName() + " at: "
- + parser.getPositionDescription());
+ // Quietly ignore duplicate permission requests, but fail loudly if
+ // the two requests have conflicting flags
+ boolean found = false;
+ final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+ final int size = usesPermissions.size();
+ for (int i = 0; i < size; i++) {
+ final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+ if (Objects.equals(usesPermission.name, name)) {
+ if (usesPermission.usesPermissionFlags != usesPermissionFlags) {
+ return input.error("Conflicting uses-permissions flags: "
+ + name + " in package: " + pkg.getPackageName() + " at: "
+ + parser.getPositionDescription());
+ } else {
+ Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ + name + " in package: " + pkg.getPackageName() + " at: "
+ + parser.getPositionDescription());
+ }
+ found = true;
+ break;
+ }
}
+ if (!found) {
+ pkg.addUsesPermission(new ParsedUsesPermission(name, usesPermissionFlags));
+ }
return success;
} finally {
sa.recycle();
@@ -2755,7 +2778,7 @@
newPermsMsg.append(' ');
}
newPermsMsg.append(npi.name);
- pkg.addRequestedPermission(npi.name)
+ pkg.addUsesPermission(new ParsedUsesPermission(npi.name, 0))
.addImplicitPermission(npi.name);
}
}
@@ -2777,7 +2800,7 @@
for (int in = 0; in < newPerms.size(); in++) {
final String perm = newPerms.get(in);
if (!requestedPermissions.contains(perm)) {
- pkg.addRequestedPermission(perm)
+ pkg.addUsesPermission(new ParsedUsesPermission(perm, 0))
.addImplicitPermission(perm);
}
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java b/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java
new file mode 100644
index 0000000..b9c2e36
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java
@@ -0,0 +1,90 @@
+/*
+ * 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.pm.parsing.component;
+
+import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A {@link android.R.styleable#AndroidManifestUsesPermission
+ * <uses-permission>} tag parsed from the manifest.
+ *
+ * @hide
+ */
+public class ParsedUsesPermission implements Parcelable {
+ /** Name of the permission requested */
+ public @NonNull String name;
+
+ /** Set of flags that should apply to this permission request. */
+ public @UsesPermissionFlags int usesPermissionFlags;
+
+ /**
+ * Strong assertion by a developer that they will never use this permission
+ * to derive the physical location of the device, regardless of
+ * ACCESS_FINE_LOCATION and/or ACCESS_COARSE_LOCATION being granted.
+ */
+ public static final int FLAG_NEVER_FOR_LOCATION = 0x1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_NEVER_FOR_LOCATION
+ })
+ public @interface UsesPermissionFlags {}
+
+ public ParsedUsesPermission(@NonNull String name,
+ @UsesPermissionFlags int usesPermissionFlags) {
+ this.name = name.intern();
+ this.usesPermissionFlags = usesPermissionFlags;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ sForInternedString.parcel(this.name, dest, flags);
+ dest.writeInt(usesPermissionFlags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ protected ParsedUsesPermission(@NonNull Parcel in) {
+ this.name = sForInternedString.unparcel(in);
+ this.usesPermissionFlags = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<ParsedUsesPermission> CREATOR
+ = new Parcelable.Creator<ParsedUsesPermission>() {
+ @Override
+ public ParsedUsesPermission[] newArray(int size) {
+ return new ParsedUsesPermission[size];
+ }
+
+ @Override
+ public ParsedUsesPermission createFromParcel(@NonNull Parcel in) {
+ return new ParsedUsesPermission(in);
+ }
+ };
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
index 7c335b1..62277ef 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -43,9 +43,50 @@
*/
@SystemApi
@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
- genEqualsHashCode = true)
+ genEqualsHashCode = true, genHiddenConstDefs = true)
public final class DomainVerificationInfo implements Parcelable {
+ // Implementation note: the following states are OUTPUT only. Any value that is synonymous with
+ // a value in DomainVerificationState must be the EXACT same integer, so that state
+ // transformation does not have to occur when sending input into the system, assuming that the
+ // system only accepts those synonymous values. The public API values declared here are only
+ // used when exiting the system server to prepare this data object for consumption by the
+ // verification agent. These constants should only be referenced inside public API classes.
+ // The server must use DomainVerificationState.
+
+ /**
+ * No response has been recorded by either the system or any verification agent.
+ */
+ public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
+
+ /**
+ * The domain has been explicitly verified.
+ */
+ public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+
+ /**
+ * Indicates the host cannot be modified by the verification agent.
+ */
+ public static final int STATE_UNMODIFIABLE = 2;
+
+ /**
+ * Indicates the host can be modified by the verification agent and is not considered verified.
+ */
+ public static final int STATE_MODIFIABLE_UNVERIFIED = 3;
+
+ /**
+ * Indicates the host can be modified by the verification agent and is considered verified.
+ */
+ public static final int STATE_MODIFIABLE_VERIFIED = 4;
+
+ /**
+ * The first available custom response code. This and any greater integer, along with {@link
+ * #STATE_SUCCESS} are the only values settable by the verification agent. All custom values
+ * will be treated as if the domain is unverified.
+ */
+ public static final int STATE_FIRST_VERIFIER_DEFINED =
+ DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+
/**
* A domain verification ID for use in later API calls. This represents the snapshot of the
* domains for a package on device, and will be invalidated whenever the package changes.
@@ -74,16 +115,12 @@
/**
* Map of host names to their current state. State is an integer, which defaults to {@link
- * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
- * verification agent (the intended consumer of this API), which can be equal to {@link
- * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
- * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
+ * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
+ * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
+ * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
- * Any value non-inclusive between those 2 values are reserved for use by the system. The domain
- * verification agent may be able to act on these reserved values, and this ability can be
- * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
- * the agent attempt to verify all domains that it can modify the state of, even if it does not
- * understand the meaning of those values.
+ * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
+ * that the agent attempt to verify all domains that it can modify the state of.
*/
@NonNull
private final Map<String, Integer> mHostToStateMap;
@@ -112,6 +149,39 @@
//@formatter:off
+ /** @hide */
+ @android.annotation.IntDef(prefix = "STATE_", value = {
+ STATE_NO_RESPONSE,
+ STATE_SUCCESS,
+ STATE_UNMODIFIABLE,
+ STATE_MODIFIABLE_UNVERIFIED,
+ STATE_MODIFIABLE_VERIFIED,
+ STATE_FIRST_VERIFIER_DEFINED
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface State {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String stateToString(@State int value) {
+ switch (value) {
+ case STATE_NO_RESPONSE:
+ return "STATE_NO_RESPONSE";
+ case STATE_SUCCESS:
+ return "STATE_SUCCESS";
+ case STATE_UNMODIFIABLE:
+ return "STATE_UNMODIFIABLE";
+ case STATE_MODIFIABLE_UNVERIFIED:
+ return "STATE_MODIFIABLE_UNVERIFIED";
+ case STATE_MODIFIABLE_VERIFIED:
+ return "STATE_MODIFIABLE_VERIFIED";
+ case STATE_FIRST_VERIFIER_DEFINED:
+ return "STATE_FIRST_VERIFIER_DEFINED";
+ default: return Integer.toHexString(value);
+ }
+ }
+
/**
* Creates a new DomainVerificationInfo.
*
@@ -134,16 +204,12 @@
* The package name that this data corresponds to.
* @param hostToStateMap
* Map of host names to their current state. State is an integer, which defaults to {@link
- * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
- * verification agent (the intended consumer of this API), which can be equal to {@link
- * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
- * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
+ * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
+ * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
+ * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
- * Any value non-inclusive between those 2 values are reserved for use by the system. The domain
- * verification agent may be able to act on these reserved values, and this ability can be
- * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
- * the agent attempt to verify all domains that it can modify the state of, even if it does not
- * understand the meaning of those values.
+ * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
+ * that the agent attempt to verify all domains that it can modify the state of.
* @hide
*/
@DataClass.Generated.Member
@@ -195,16 +261,12 @@
/**
* Map of host names to their current state. State is an integer, which defaults to {@link
- * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
- * verification agent (the intended consumer of this API), which can be equal to {@link
- * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
- * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
+ * #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
+ * consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
+ * to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
- * Any value non-inclusive between those 2 values are reserved for use by the system. The domain
- * verification agent may be able to act on these reserved values, and this ability can be
- * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
- * the agent attempt to verify all domains that it can modify the state of, even if it does not
- * understand the meaning of those values.
+ * Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
+ * that the agent attempt to verify all domains that it can modify the state of.
*/
@DataClass.Generated.Member
public @NonNull Map<String,Integer> getHostToStateMap() {
@@ -320,10 +382,10 @@
};
@DataClass.Generated(
- time = 1614721812023L,
+ time = 1615317187669L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
- inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final int STATE_NO_RESPONSE\npublic static final int STATE_SUCCESS\npublic static final int STATE_UNMODIFIABLE\npublic static final int STATE_MODIFIABLE_UNVERIFIED\npublic static final int STATE_MODIFIABLE_VERIFIED\npublic static final int STATE_FIRST_VERIFIER_DEFINED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index f7c81bcf..55e19f2 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -60,154 +60,97 @@
"android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
/**
- * No response has been recorded by either the system or any verification agent.
+ * Default return code for when a method has succeeded.
*
* @hide
*/
@SystemApi
- public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
+ public static final int STATUS_OK = 0;
/**
- * The verification agent has explicitly verified the domain at some point.
+ * The provided domain set ID was invalid, probably due to the package being updated between
+ * the initial request that provided the ID and the method call that used it. 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
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
*
* @hide
*/
@SystemApi
- public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+ public static final int ERROR_DOMAIN_SET_ID_INVALID = 1;
/**
- * The first available custom response code. This and any greater integer, along with {@link
- * #STATE_SUCCESS} are the only values settable by the verification agent. All values will be
- * treated as if the domain is unverified.
+ * The provided domain set ID was null. This is a developer error.
*
* @hide
*/
@SystemApi
- public static final int STATE_FIRST_VERIFIER_DEFINED =
- DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ public static final int ERROR_DOMAIN_SET_ID_NULL = 2;
/**
- * @hide
- */
- @NonNull
- public static String stateToDebugString(@DomainVerificationState.State int state) {
- switch (state) {
- case DomainVerificationState.STATE_NO_RESPONSE:
- return "none";
- case DomainVerificationState.STATE_SUCCESS:
- return "verified";
- case DomainVerificationState.STATE_APPROVED:
- return "approved";
- case DomainVerificationState.STATE_DENIED:
- return "denied";
- case DomainVerificationState.STATE_MIGRATED:
- return "migrated";
- case DomainVerificationState.STATE_RESTORED:
- return "restored";
- case DomainVerificationState.STATE_LEGACY_FAILURE:
- return "legacy_failure";
- case DomainVerificationState.STATE_SYS_CONFIG:
- return "system_configured";
- default:
- return String.valueOf(state);
- }
- }
-
- /**
- * Checks if a state considers the corresponding domain to be successfully verified. The domain
- * verification agent may use this to determine whether or not to re-verify a domain.
+ * The provided set of domains was null or empty. This is a developer error.
*
* @hide
*/
@SystemApi
- public static boolean isStateVerified(@DomainVerificationState.State int state) {
- switch (state) {
- case DomainVerificationState.STATE_SUCCESS:
- case DomainVerificationState.STATE_APPROVED:
- case DomainVerificationState.STATE_MIGRATED:
- case DomainVerificationState.STATE_RESTORED:
- case DomainVerificationState.STATE_SYS_CONFIG:
- return true;
- case DomainVerificationState.STATE_NO_RESPONSE:
- case DomainVerificationState.STATE_DENIED:
- case DomainVerificationState.STATE_LEGACY_FAILURE:
- default:
- return false;
- }
- }
+ public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3;
/**
- * Checks if a state is modifiable by the domain verification agent. This is useful as the
- * platform may add new state codes in newer versions, and older verification agents can use
- * this method to determine if a state can be changed without having to be aware of what the new
- * state means.
+ * 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
+ * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
*
* @hide
*/
@SystemApi
- public static boolean isStateModifiable(@DomainVerificationState.State int state) {
- switch (state) {
- case DomainVerificationState.STATE_NO_RESPONSE:
- case DomainVerificationState.STATE_SUCCESS:
- case DomainVerificationState.STATE_MIGRATED:
- case DomainVerificationState.STATE_RESTORED:
- case DomainVerificationState.STATE_LEGACY_FAILURE:
- return true;
- case DomainVerificationState.STATE_APPROVED:
- case DomainVerificationState.STATE_DENIED:
- case DomainVerificationState.STATE_SYS_CONFIG:
- return false;
- default:
- return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
- }
- }
+ public static final int ERROR_UNKNOWN_DOMAIN = 4;
/**
- * For determine re-verify policy. This is hidden from the domain verification agent so that no
- * behavior is made based on the result.
+ * The system was unable to select the domain for approval. This indicates another application
+ * has been granted a higher approval, usually through domain verification, and the target
+ * package is unable to override it.
*
* @hide
*/
- public static boolean isStateDefault(@DomainVerificationState.State int state) {
- switch (state) {
- case DomainVerificationState.STATE_NO_RESPONSE:
- case DomainVerificationState.STATE_MIGRATED:
- case DomainVerificationState.STATE_RESTORED:
- return true;
- case DomainVerificationState.STATE_SUCCESS:
- case DomainVerificationState.STATE_APPROVED:
- case DomainVerificationState.STATE_DENIED:
- case DomainVerificationState.STATE_LEGACY_FAILURE:
- case DomainVerificationState.STATE_SYS_CONFIG:
- default:
- return false;
- }
- }
+ @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
*/
- public static final int ERROR_INVALID_DOMAIN_SET = 1;
+ @SystemApi
+ public static final int ERROR_INVALID_STATE_CODE = 6;
+
/**
+ * Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API.
+ *
* @hide
*/
- public static final int ERROR_NAME_NOT_FOUND = 2;
+ public static final int INTERNAL_ERROR_NAME_NOT_FOUND = 1;
/**
* @hide
*/
@IntDef(prefix = {"ERROR_"}, value = {
- ERROR_INVALID_DOMAIN_SET,
- ERROR_NAME_NOT_FOUND,
+ 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
})
- private @interface Error {
+ public @interface Error {
}
private final Context mContext;
private final IDomainVerificationManager mDomainVerificationManager;
-
/**
* System service to access the domain verification APIs.
* <p>
@@ -289,27 +232,24 @@
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains List of host names to change the state of.
* @param state See {@link DomainVerificationInfo#getHostToStateMap()}.
- * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
- * invalid. This usually means the work being processed by the
- * verification agent is outdated and a new request should be
- * scheduled, if one has not already been done as part of the
- * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
* @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
+ *
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
- public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
- @DomainVerificationState.State int state) throws NameNotFoundException {
+ public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ int state) throws NameNotFoundException {
try {
- mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
+ return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
new DomainSet(domains), state);
} catch (Exception e) {
- Exception converted = rethrow(e, domainSetId);
+ Exception converted = rethrow(e, null);
if (converted instanceof NameNotFoundException) {
throw (NameNotFoundException) converted;
} else if (converted instanceof RuntimeException) {
@@ -338,7 +278,7 @@
mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
allowed, mContext.getUserId());
} catch (Exception e) {
- Exception converted = rethrow(e, packageName);
+ Exception converted = rethrow(e, null);
if (converted instanceof NameNotFoundException) {
throw (NameNotFoundException) converted;
} else if (converted instanceof RuntimeException) {
@@ -372,24 +312,24 @@
* @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 IllegalArgumentException If the ID is invalidated or the {@param domains} are
- * invalid.
* @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
+ *
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
- public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled) throws NameNotFoundException {
try {
- mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
- new DomainSet(domains), enabled, mContext.getUserId());
+ return mDomainVerificationManager.setDomainVerificationUserSelection(
+ domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId());
} catch (Exception e) {
- Exception converted = rethrow(e, domainSetId);
+ Exception converted = rethrow(e, null);
if (converted instanceof NameNotFoundException) {
throw (NameNotFoundException) converted;
} else if (converted instanceof RuntimeException) {
@@ -447,123 +387,22 @@
}
}
- private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
- return rethrow(exception, domainSetId, null);
- }
-
private Exception rethrow(Exception exception, @Nullable String packageName) {
- return rethrow(exception, null, packageName);
- }
-
- private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
- @Nullable String packageName) {
if (exception instanceof ServiceSpecificException) {
- int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
+ int serviceSpecificErrorCode = ((ServiceSpecificException) exception).errorCode;
if (packageName == null) {
packageName = exception.getMessage();
}
- @Error int managerErrorCode = packedErrorCode & 0xFFFF;
- switch (managerErrorCode) {
- case ERROR_INVALID_DOMAIN_SET:
- int errorSpecificCode = packedErrorCode >> 16;
- return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
- domainSetId, packageName, errorSpecificCode));
- case ERROR_NAME_NOT_FOUND:
- return new NameNotFoundException(packageName);
- default:
- return exception;
+ if (serviceSpecificErrorCode == INTERNAL_ERROR_NAME_NOT_FOUND) {
+ return new NameNotFoundException(packageName);
}
+
+ return exception;
} else if (exception instanceof RemoteException) {
return ((RemoteException) exception).rethrowFromSystemServer();
} else {
return exception;
}
}
-
- /**
- * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
- * provided by the caller is no longer valid. This may be recoverable, and the caller should
- * re-query the package name associated with the ID using
- * {@link #getDomainVerificationInfo(String)}
- * in order to check. If that also fails, then the package is no longer known to the device and
- * thus all pending work for it should be dropped.
- *
- * @hide
- */
- public static class InvalidDomainSetException extends IllegalArgumentException {
-
- public static final int REASON_ID_NULL = 1;
- public static final int REASON_ID_INVALID = 2;
- public static final int REASON_SET_NULL_OR_EMPTY = 3;
- public static final int REASON_UNKNOWN_DOMAIN = 4;
- public static final int REASON_UNABLE_TO_APPROVE = 5;
-
- /**
- * @hide
- */
- @IntDef({
- REASON_ID_NULL,
- REASON_ID_INVALID,
- REASON_SET_NULL_OR_EMPTY,
- REASON_UNKNOWN_DOMAIN,
- REASON_UNABLE_TO_APPROVE
- })
- public @interface Reason {
- }
-
- public static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName,
- @Reason int reason) {
- switch (reason) {
- case REASON_ID_NULL:
- return "Domain set ID cannot be null";
- case REASON_ID_INVALID:
- return "Domain set ID " + domainSetId + " has been invalidated";
- case REASON_SET_NULL_OR_EMPTY:
- return "Domain set cannot be null or empty";
- case REASON_UNKNOWN_DOMAIN:
- return "Domain set contains value that was not declared by the target package "
- + packageName;
- case REASON_UNABLE_TO_APPROVE:
- return "Domain set contains value that was owned by another package";
- default:
- return "Unknown failure";
- }
- }
-
- @Reason
- private final int mReason;
-
- @Nullable
- private final UUID mDomainSetId;
-
- @Nullable
- private final String mPackageName;
-
- /**
- * @hide
- */
- public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName,
- @Reason int reason) {
- super(buildMessage(domainSetId, packageName, reason));
- mDomainSetId = domainSetId;
- mPackageName = packageName;
- mReason = reason;
- }
-
- @Nullable
- public UUID getDomainSetId() {
- return mDomainSetId;
- }
-
- @Nullable
- public String getPackageName() {
- return mPackageName;
- }
-
- @Reason
- public int getReason() {
- return mReason;
- }
- }
}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
index 17593ef..8e28042 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationState.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
@@ -17,15 +17,13 @@
package android.content.pm.verify.domain;
import android.annotation.IntDef;
+import android.annotation.NonNull;
/**
* @hide
*/
public interface DomainVerificationState {
- /**
- * @hide
- */
@IntDef({
STATE_NO_RESPONSE,
STATE_SUCCESS,
@@ -42,12 +40,12 @@
// TODO(b/159952358): Document all the places that states need to be updated when one is added
/**
- * @see DomainVerificationManager#STATE_NO_RESPONSE
+ * @see DomainVerificationInfo#STATE_NO_RESPONSE
*/
int STATE_NO_RESPONSE = 0;
/**
- * @see DomainVerificationManager#STATE_SUCCESS
+ * @see DomainVerificationInfo#STATE_SUCCESS
*/
int STATE_SUCCESS = 1;
@@ -94,7 +92,132 @@
int STATE_SYS_CONFIG = 7;
/**
- * @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED
+ * @see DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED
*/
int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
+
+ @NonNull
+ static String stateToDebugString(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ return "none";
+ case DomainVerificationState.STATE_SUCCESS:
+ return "verified";
+ case DomainVerificationState.STATE_APPROVED:
+ return "approved";
+ case DomainVerificationState.STATE_DENIED:
+ return "denied";
+ case DomainVerificationState.STATE_MIGRATED:
+ return "migrated";
+ case DomainVerificationState.STATE_RESTORED:
+ return "restored";
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return "legacy_failure";
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return "system_configured";
+ default:
+ return String.valueOf(state);
+ }
+ }
+
+ /**
+ * For determining re-verify policy. This is hidden from the domain verification agent so that
+ * no behavior is made based on the result.
+ */
+ static boolean isDefault(@State int state) {
+ switch (state) {
+ case STATE_NO_RESPONSE:
+ case STATE_MIGRATED:
+ case STATE_RESTORED:
+ return true;
+ case STATE_SUCCESS:
+ case STATE_APPROVED:
+ case STATE_DENIED:
+ case STATE_LEGACY_FAILURE:
+ case STATE_SYS_CONFIG:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a state considers the corresponding domain to be successfully verified. The domain
+ * verification agent may use this to determine whether or not to re-verify a domain.
+ */
+ static boolean isVerified(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return true;
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a state is modifiable by the domain verification agent. This is useful as the
+ * platform may add new state codes in newer versions, and older verification agents can use
+ * this method to determine if a state can be changed without having to be aware of what the new
+ * state means.
+ */
+ static boolean isModifiable(@DomainVerificationState.State int state) {
+ switch (state) {
+ case DomainVerificationState.STATE_NO_RESPONSE:
+ case DomainVerificationState.STATE_SUCCESS:
+ case DomainVerificationState.STATE_MIGRATED:
+ case DomainVerificationState.STATE_RESTORED:
+ case DomainVerificationState.STATE_LEGACY_FAILURE:
+ return true;
+ case DomainVerificationState.STATE_APPROVED:
+ case DomainVerificationState.STATE_DENIED:
+ case DomainVerificationState.STATE_SYS_CONFIG:
+ return false;
+ default:
+ return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+ }
+ }
+
+ /**
+ * Whether the state is migrated when updating a package. Generally this is only for states
+ * that maintain verification state or were set by an explicit user or developer action.
+ */
+ static boolean shouldMigrate(@State int state) {
+ switch (state) {
+ case STATE_SUCCESS:
+ case STATE_MIGRATED:
+ case STATE_RESTORED:
+ case STATE_APPROVED:
+ case STATE_DENIED:
+ return true;
+ case STATE_NO_RESPONSE:
+ case STATE_LEGACY_FAILURE:
+ case STATE_SYS_CONFIG:
+ case STATE_FIRST_VERIFIER_DEFINED:
+ default:
+ return false;
+ }
+ }
+
+ @DomainVerificationInfo.State
+ static int convertToInfoState(@State int internalState) {
+ if (internalState >= STATE_FIRST_VERIFIER_DEFINED) {
+ return internalState;
+ } else if (internalState == STATE_NO_RESPONSE) {
+ return DomainVerificationInfo.STATE_NO_RESPONSE;
+ } else if (internalState == STATE_SUCCESS) {
+ return DomainVerificationInfo.STATE_SUCCESS;
+ } else if (!isModifiable(internalState)) {
+ return DomainVerificationInfo.STATE_UNMODIFIABLE;
+ } else if (isVerified(internalState)) {
+ return DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED;
+ } else {
+ return DomainVerificationInfo.STATE_MODIFIABLE_UNVERIFIED;
+ }
+ }
}
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
index 332b925..53205f3 100644
--- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -40,10 +40,10 @@
@nullable
List<DomainOwner> getOwnersForDomain(String domain, int userId);
- void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state);
+ int setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state);
void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId);
- void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains,
+ int setDomainVerificationUserSelection(String domainSetId, in DomainSet domains,
boolean enabled, int userId);
}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index d7225cc..439c639 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -20,6 +20,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -28,6 +29,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
+import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -423,13 +425,38 @@
}
/**
- * Translate an InsetsState in screen coordinates into the app window's coordinates.
+ * Translate an {@link InsetsState} in screen coordinates into the app window's coordinates.
*/
public void translateInsetsStateInScreenToAppWindow(InsetsState state) {
state.scale(applicationInvertedScale);
}
/**
+ * Translate {@link InsetsSourceControl}s in screen coordinates into the app window's
+ * coordinates.
+ */
+ public void translateSourceControlsInScreenToAppWindow(InsetsSourceControl[] controls) {
+ if (controls == null) {
+ return;
+ }
+ final float scale = applicationInvertedScale;
+ if (scale == 1f) {
+ return;
+ }
+ for (InsetsSourceControl control : controls) {
+ if (control == null) {
+ continue;
+ }
+ final Insets hint = control.getInsetsHint();
+ control.setInsetsHint(
+ (int) (scale * hint.left),
+ (int) (scale * hint.top),
+ (int) (scale * hint.right),
+ (int) (scale * hint.bottom));
+ }
+ }
+
+ /**
* Translate a Point in screen coordinates into the app window's coordinates.
*/
public void translatePointInScreenToAppWindow(PointF point) {
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 1fdce5e..304b2af 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -29,6 +29,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import android.os.IBinder;
import android.os.RemoteException;
import android.security.keystore.KeyProperties;
import android.util.Slog;
@@ -47,13 +48,6 @@
private static final String TAG = "BiometricManager";
/**
- * An ID that should match any biometric sensor on the device.
- *
- * @hide
- */
- public static final int SENSOR_ID_ANY = -1;
-
- /**
* No error detected.
*/
public static final int BIOMETRIC_SUCCESS =
@@ -410,6 +404,36 @@
}
/**
+ * Requests all other biometric sensors to resetLockout. Note that this is a "time bound"
+ * See the {@link android.hardware.biometrics.fingerprint.ISession#resetLockout(int,
+ * HardwareAuthToken)} and {@link android.hardware.biometrics.face.ISession#resetLockout(int,
+ * HardwareAuthToken)} documentation for complete details.
+ *
+ * @param token A binder from the caller, for the service to linkToDeath
+ * @param opPackageName Caller's package name
+ * @param fromSensorId The originating sensor that just authenticated. Note that this MUST
+ * be a sensor that meets {@link Authenticators#BIOMETRIC_STRONG} strength.
+ * The strength will also be enforced on the BiometricService side.
+ * @param userId The user that authentication succeeded for, and also the user that resetLockout
+ * should be applied to.
+ * @param hardwareAuthToken A valid HAT generated upon successful biometric authentication. Note
+ * that it is not necessary for the HAT to contain a challenge.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId,
+ int userId, byte[] hardwareAuthToken) {
+ if (mService != null) {
+ try {
+ mService.resetLockoutTimeBound(token, opPackageName, fromSensorId, userId,
+ hardwareAuthToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Provides a localized string that may be used as the label for a button that invokes
* {@link BiometricPrompt}.
*
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 4f6a7c7..1258247 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,6 +16,7 @@
package android.hardware.biometrics;
+import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -25,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
import android.content.Context;
import android.content.DialogInterface;
import android.hardware.face.FaceManager;
@@ -45,6 +47,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.Signature;
+import java.util.List;
import java.util.concurrent.Executor;
import javax.crypto.Cipher;
@@ -339,6 +342,32 @@
}
/**
+ * If non-empty, requests authentication to be performed only if the sensor is contained
+ * within the list. Note that the actual sensor presented to the user/test will meet all
+ * constraints specified within this builder. For example, on a device with the below
+ * configuration:
+ *
+ * SensorId: 1, Strength: BIOMETRIC_STRONG
+ * SensorId: 2, Strength: BIOMETRIC_WEAK
+ *
+ * If authentication is invoked with setAllowedAuthenticators(BIOMETRIC_STRONG) and
+ * setAllowedSensorIds(2), then no sensor will be eligible for authentication.
+ *
+ * @see {@link BiometricManager#getSensorProperties()}
+ *
+ * @param sensorIds Sensor IDs to constrain this authentication to.
+ * @return This builder
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
+ public Builder setAllowedSensorIds(@NonNull List<Integer> sensorIds) {
+ mPromptInfo.setAllowedSensorIds(sensorIds);
+ return this;
+ }
+
+ /**
* If set check the Device Policy Manager for disabled biometrics.
*
* @param checkDevicePolicyManager
@@ -364,21 +393,6 @@
}
/**
- * If set, authenticate using the biometric sensor with the given ID.
- *
- * @param sensorId The ID of a biometric sensor, or -1 to allow any sensor (default).
- * @return This builder.
- *
- * @hide
- */
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- @NonNull
- public Builder setSensorId(int sensorId) {
- mPromptInfo.setSensorId(sensorId);
- return this;
- }
-
- /**
* Creates a {@link BiometricPrompt}.
*
* @return An instance of {@link BiometricPrompt}.
@@ -596,6 +610,16 @@
}
/**
+ * @return The values set by {@link Builder#setAllowedSensorIds(List)}
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public List<Integer> getAllowedSensorIds() {
+ return mPromptInfo.getAllowedSensorIds();
+ }
+
+ /**
* A wrapper class for the cryptographic operations supported by BiometricPrompt.
*
* <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/core/java/android/hardware/biometrics/ComponentInfoInternal.aidl
similarity index 81%
copy from telephony/java/android/telephony/data/SliceInfo.aidl
copy to core/java/android/hardware/biometrics/ComponentInfoInternal.aidl
index 286ea5e..0c780cc 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/core/java/android/hardware/biometrics/ComponentInfoInternal.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * 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.
@@ -13,8 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.biometrics;
-/** @hide */
-package android.telephony.data;
-
-parcelable SliceInfo;
+parcelable ComponentInfoInternal;
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/ComponentInfoInternal.java b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
new file mode 100644
index 0000000..fa34e0b
--- /dev/null
+++ b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
@@ -0,0 +1,100 @@
+/*
+ * 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.hardware.biometrics;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The internal class for storing the component info for a subsystem of the biometric sensor,
+ * as defined in {@link android.hardware.biometrics.common.ComponentInfo}.
+ * @hide
+ */
+public class ComponentInfoInternal implements Parcelable {
+
+ public final String componentId;
+ public final String hardwareVersion;
+ public final String firmwareVersion;
+ public final String serialNumber;
+ public final String softwareVersion;
+
+ /**
+ * Constructs a {@link ComponentInfoInternal} from another instance.
+ * @hide
+ */
+ public static ComponentInfoInternal from(@NonNull ComponentInfoInternal comp) {
+ return new ComponentInfoInternal(comp.componentId, comp.hardwareVersion,
+ comp.firmwareVersion, comp.serialNumber, comp.softwareVersion);
+ }
+
+ /**
+ * @hide
+ */
+ public ComponentInfoInternal(String componentId, String hardwareVersion,
+ String firmwareVersion, String serialNumber, String softwareVersion) {
+ this.componentId = componentId;
+ this.hardwareVersion = hardwareVersion;
+ this.firmwareVersion = firmwareVersion;
+ this.serialNumber = serialNumber;
+ this.softwareVersion = softwareVersion;
+ }
+
+ protected ComponentInfoInternal(Parcel in) {
+ componentId = in.readString();
+ hardwareVersion = in.readString();
+ firmwareVersion = in.readString();
+ serialNumber = in.readString();
+ softwareVersion = in.readString();
+ }
+
+ public static final Creator<ComponentInfoInternal> CREATOR =
+ new Creator<ComponentInfoInternal>() {
+ @Override
+ public ComponentInfoInternal createFromParcel(Parcel in) {
+ return new ComponentInfoInternal(in);
+ }
+
+ @Override
+ public ComponentInfoInternal[] newArray(int size) {
+ return new ComponentInfoInternal[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(componentId);
+ dest.writeString(hardwareVersion);
+ dest.writeString(firmwareVersion);
+ dest.writeString(serialNumber);
+ dest.writeString(softwareVersion);
+ }
+
+ @Override
+ public String toString() {
+ return "ComponentId: " + componentId
+ + ", HardwareVersion: " + hardwareVersion
+ + ", FirmwareVersion: " + firmwareVersion
+ + ", SerialNumber " + serialNumber
+ + ", SoftwareVersion: " + softwareVersion;
+ }
+}
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 1472bb9..86df099 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -69,6 +69,10 @@
// land as SIDs, and are used during key generation.
long[] getAuthenticatorIds();
+ // See documentation in BiometricManager.
+ void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId,
+ in byte[] hardwareAuthToken);
+
// Provides a localized string that may be used as the label for a button that invokes
// BiometricPrompt.
CharSequence getButtonLabel(int userId, String opPackageName, int authenticators);
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 7639c5d..059bf26 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -70,4 +70,8 @@
// Gets the authenticator ID representing the current set of enrolled templates
long getAuthenticatorId(int callingUserId);
+
+ // Requests the sensor to reset its lockout state
+ void resetLockout(IBinder token, String opPackageName, int userId,
+ in byte[] hardwareAuthToken);
}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 6d8bf0f..64b5118 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -74,6 +74,10 @@
// land as SIDs, and are used during key generation.
long[] getAuthenticatorIds(int callingUserId);
+ // See documentation in BiometricManager.
+ void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId,
+ in byte[] hardwareAuthToken);
+
int getCurrentStrength(int sensorId);
// Returns a bit field of the modality (or modalities) that are will be used for authentication.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 0e99f31..20c25fb 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -21,6 +21,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Contains the information set/requested by the caller of the {@link BiometricPrompt}
* @hide
@@ -40,7 +43,7 @@
private @BiometricManager.Authenticators.Types int mAuthenticators;
private boolean mDisallowBiometricsIfPolicyExists;
private boolean mReceiveSystemEvents;
- private int mSensorId = -1;
+ @NonNull private List<Integer> mAllowedSensorIds = new ArrayList<>();
public PromptInfo() {
@@ -60,7 +63,7 @@
mAuthenticators = in.readInt();
mDisallowBiometricsIfPolicyExists = in.readBoolean();
mReceiveSystemEvents = in.readBoolean();
- mSensorId = in.readInt();
+ mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader());
}
public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -95,7 +98,14 @@
dest.writeInt(mAuthenticators);
dest.writeBoolean(mDisallowBiometricsIfPolicyExists);
dest.writeBoolean(mReceiveSystemEvents);
- dest.writeInt(mSensorId);
+ dest.writeList(mAllowedSensorIds);
+ }
+
+ public boolean containsTestConfigurations() {
+ if (!mAllowedSensorIds.isEmpty()) {
+ return true;
+ }
+ return false;
}
public boolean containsPrivateApiConfigurations() {
@@ -169,8 +179,8 @@
mReceiveSystemEvents = receiveSystemEvents;
}
- public void setSensorId(int sensorId) {
- mSensorId = sensorId;
+ public void setAllowedSensorIds(@NonNull List<Integer> sensorIds) {
+ mAllowedSensorIds = sensorIds;
}
// Getters
@@ -234,7 +244,8 @@
return mReceiveSystemEvents;
}
- public int getSensorId() {
- return mSensorId;
+ @NonNull
+ public List<Integer> getAllowedSensorIds() {
+ return mAllowedSensorIds;
}
}
diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java
index 360f138..3b9cad4 100644
--- a/core/java/android/hardware/biometrics/SensorProperties.java
+++ b/core/java/android/hardware/biometrics/SensorProperties.java
@@ -17,10 +17,13 @@
package android.hardware.biometrics;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.TestApi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* The base class containing all modality-agnostic information.
@@ -56,15 +59,93 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Strength {}
+ /**
+ * A class storing the component info for a subsystem of the sensor.
+ */
+ public static final class ComponentInfo {
+ @NonNull private final String mComponentId;
+ @NonNull private final String mHardwareVersion;
+ @NonNull private final String mFirmwareVersion;
+ @NonNull private final String mSerialNumber;
+ @NonNull private final String mSoftwareVersion;
+
+ /**
+ * @hide
+ */
+ public ComponentInfo(@NonNull String componentId, @NonNull String hardwareVersion,
+ @NonNull String firmwareVersion, @NonNull String serialNumber,
+ @NonNull String softwareVersion) {
+ mComponentId = componentId;
+ mHardwareVersion = hardwareVersion;
+ mFirmwareVersion = firmwareVersion;
+ mSerialNumber = serialNumber;
+ mSoftwareVersion = softwareVersion;
+ }
+
+ /**
+ * @return The unique identifier for the subsystem.
+ */
+ @NonNull
+ public String getComponentId() {
+ return mComponentId;
+ }
+
+ /**
+ * @return The hardware version for the subsystem. For example, <vendor>/<model>/<revision>.
+ */
+ @NonNull
+ public String getHardwareVersion() {
+ return mHardwareVersion;
+ }
+
+ /**
+ * @return The firmware version for the subsystem.
+ */
+ @NonNull
+ public String getFirmwareVersion() {
+ return mFirmwareVersion;
+ }
+
+ /**
+ * @return The serial number for the subsystem.
+ */
+ @NonNull
+ public String getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ /**
+ * @return The software version for the subsystem.
+ * For example, <vendor>/<version>/<revision>.
+ */
+ @NonNull
+ public String getSoftwareVersion() {
+ return mSoftwareVersion;
+ }
+
+ /**
+ * Constructs a {@link ComponentInfo} from the internal parcelable representation.
+ * @hide
+ */
+ public static ComponentInfo from(ComponentInfoInternal internalComp) {
+ return new ComponentInfo(internalComp.componentId, internalComp.hardwareVersion,
+ internalComp.firmwareVersion, internalComp.serialNumber,
+ internalComp.softwareVersion);
+ }
+ }
+
private final int mSensorId;
@Strength private final int mSensorStrength;
+ private final List<ComponentInfo> mComponentInfo;
/**
* @hide
*/
- public SensorProperties(int sensorId, @Strength int sensorStrength) {
+ public SensorProperties(int sensorId, @Strength int sensorStrength,
+ List<ComponentInfo> componentInfo) {
mSensorId = sensorId;
mSensorStrength = sensorStrength;
+ mComponentInfo = componentInfo;
}
/**
@@ -83,10 +164,23 @@
}
/**
+ * @return The sensor's component info.
+ */
+ @NonNull
+ public List<ComponentInfo> getComponentInfo() {
+ return mComponentInfo;
+ }
+
+ /**
* Constructs a {@link SensorProperties} from the internal parcelable representation.
* @hide
*/
public static SensorProperties from(SensorPropertiesInternal internalProp) {
- return new SensorProperties(internalProp.sensorId, internalProp.sensorStrength);
+ final List<ComponentInfo> componentInfo = new ArrayList<>();
+ for (ComponentInfoInternal internalComp : internalProp.componentInfo) {
+ componentInfo.add(ComponentInfo.from(internalComp));
+ }
+ return new SensorProperties(internalProp.sensorId, internalProp.sensorStrength,
+ componentInfo);
}
}
diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
index 0b81c6c..eda0ded 100644
--- a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
+++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
@@ -20,6 +20,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* The base class containing all modality-agnostic information. This is a superset of the
* {@link android.hardware.biometrics.common.CommonProps}, and provides backwards-compatible
@@ -31,23 +34,35 @@
public final int sensorId;
@SensorProperties.Strength public final int sensorStrength;
public final int maxEnrollmentsPerUser;
+ public final List<ComponentInfoInternal> componentInfo;
+ public final boolean resetLockoutRequiresHardwareAuthToken;
+ public final boolean resetLockoutRequiresChallenge;
public static SensorPropertiesInternal from(@NonNull SensorPropertiesInternal prop) {
return new SensorPropertiesInternal(prop.sensorId, prop.sensorStrength,
- prop.maxEnrollmentsPerUser);
+ prop.maxEnrollmentsPerUser, prop.componentInfo,
+ prop.resetLockoutRequiresHardwareAuthToken, prop.resetLockoutRequiresChallenge);
}
protected SensorPropertiesInternal(int sensorId, @SensorProperties.Strength int sensorStrength,
- int maxEnrollmentsPerUser) {
+ int maxEnrollmentsPerUser, @NonNull List<ComponentInfoInternal> componentInfo,
+ boolean resetLockoutRequiresHardwareAuthToken, boolean resetLockoutRequiresChallenge) {
this.sensorId = sensorId;
this.sensorStrength = sensorStrength;
this.maxEnrollmentsPerUser = maxEnrollmentsPerUser;
+ this.componentInfo = componentInfo;
+ this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+ this.resetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
}
protected SensorPropertiesInternal(Parcel in) {
sensorId = in.readInt();
sensorStrength = in.readInt();
maxEnrollmentsPerUser = in.readInt();
+ componentInfo = new ArrayList<>();
+ in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader());
+ resetLockoutRequiresHardwareAuthToken = in.readBoolean();
+ resetLockoutRequiresChallenge = in.readBoolean();
}
public static final Creator<SensorPropertiesInternal> CREATOR =
@@ -73,11 +88,23 @@
dest.writeInt(sensorId);
dest.writeInt(sensorStrength);
dest.writeInt(maxEnrollmentsPerUser);
+ dest.writeList(componentInfo);
+ dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
+ dest.writeBoolean(resetLockoutRequiresChallenge);
}
@Override
public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("[ ");
+ for (ComponentInfoInternal info : componentInfo) {
+ sb.append("[").append(info.toString());
+ sb.append("] ");
+ }
+ sb.append("]");
+
return "ID: " + sensorId + ", Strength: " + sensorStrength
- + ", MaxEnrollmentsPerUser: " + maxEnrollmentsPerUser;
+ + ", MaxEnrollmentsPerUser: " + maxEnrollmentsPerUser
+ + ", ComponentInfo: " + sb.toString();
}
}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 157e333..9ac2ff5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3345,7 +3345,7 @@
* <p>A control for selecting whether optical stabilization (OIS) position
* information is included in output result metadata.</p>
* <p>Since optical image stabilization generally involves motion much faster than the duration
- * of individualq image exposure, multiple OIS samples can be included for a single capture
+ * of individual image exposure, multiple OIS samples can be included for a single capture
* result. For example, if the OIS reporting operates at 200 Hz, a typical camera operating
* at 30fps may have 6-7 OIS samples per capture result. This information can be combined
* with the rolling shutter skew to account for lens motion during image exposure in
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index c0eb068..e7457e7 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -4635,7 +4635,7 @@
* <p>A control for selecting whether optical stabilization (OIS) position
* information is included in output result metadata.</p>
* <p>Since optical image stabilization generally involves motion much faster than the duration
- * of individualq image exposure, multiple OIS samples can be included for a single capture
+ * of individual image exposure, multiple OIS samples can be included for a single capture
* result. For example, if the OIS reporting operates at 200 Hz, a typical camera operating
* at 30fps may have 6-7 OIS samples per capture result. This information can be combined
* with the rolling shutter skew to account for lens motion during image exposure in
diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
index c592f19..bb3d91d 100644
--- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java
+++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
@@ -131,18 +131,10 @@
* @see
* android.hardware.camera2.params.MultiResolutionStreamConfigurationMap
*/
- public static @NonNull MultiResolutionImageReader newInstance(
+ public MultiResolutionImageReader(
@NonNull Collection<MultiResolutionStreamInfo> streams,
@Format int format,
@IntRange(from = 1) int maxImages) {
- return new MultiResolutionImageReader(streams, format, maxImages);
- }
-
- /**
- * @hide
- */
- protected MultiResolutionImageReader(Collection<MultiResolutionStreamInfo> streams,
- int format, int maxImages) {
mFormat = format;
mMaxImages = maxImages;
diff --git a/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java
index aa1d1d4..e2e61ad 100644
--- a/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java
+++ b/core/java/android/hardware/camera2/params/MultiResolutionStreamInfo.java
@@ -17,6 +17,7 @@
package android.hardware.camera2.params;
import android.annotation.NonNull;
+import android.annotation.IntRange;
import java.util.Objects;
@@ -50,9 +51,22 @@
* MultiResolutionStreamConfigurationMap#getOutputInfo} or {@link
* MultiResolutionStreamConfigurationMap#getInputInfo} to obtain them for a particular format
* instead.</p>
+ *
+ * @param streamWidth The width in pixels of the camera stream
+ * @param streamHeight The height in pixels of the camera stream
+ * @param physicalCameraId The physical camera Id the camera stream is associated with
+ * @throws IllegalArgumentException if the streamWidth or streamHeight is invalid (either zero
+ * or negative).
*/
- public MultiResolutionStreamInfo(int streamWidth, int streamHeight,
+ public MultiResolutionStreamInfo(@IntRange(from = 1) int streamWidth,
+ @IntRange(from = 1) int streamHeight,
@NonNull String physicalCameraId) {
+ if (streamWidth <= 0) {
+ throw new IllegalArgumentException("Invalid stream width " + streamWidth);
+ }
+ if (streamHeight <= 0) {
+ throw new IllegalArgumentException("Invalid stream height " + streamHeight);
+ }
mStreamWidth = streamWidth;
mStreamHeight = streamHeight;
mPhysicalCameraId = physicalCameraId;
@@ -61,14 +75,14 @@
/**
* The width of this particular image buffer stream in pixels.
*/
- public int getWidth() {
+ public @IntRange(from = 1) int getWidth() {
return mStreamWidth;
}
/**
* The height of this particular image buffer stream in pixels.
*/
- public int getHeight() {
+ public @IntRange(from = 1) int getHeight() {
return mStreamHeight;
}
diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java
index a6c6b46..6b7d8c3 100644
--- a/core/java/android/hardware/display/BrightnessChangeEvent.java
+++ b/core/java/android/hardware/display/BrightnessChangeEvent.java
@@ -47,6 +47,10 @@
* @hide */
public final int userId;
+ /** The unique id of the screen on which the brightness was changed */
+ @NonNull
+ public final String uniqueDisplayId;
+
/** Lux values of recent sensor data */
public final float[] luxValues;
@@ -120,15 +124,16 @@
/** @hide */
private BrightnessChangeEvent(float brightness, long timeStamp, String packageName,
- int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel,
- float powerBrightnessFactor, boolean nightMode, int colorTemperature,
- boolean reduceBrightColors, int reduceBrightColorsStrength,
+ int userId, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps,
+ float batteryLevel, float powerBrightnessFactor, boolean nightMode,
+ int colorTemperature, boolean reduceBrightColors, int reduceBrightColorsStrength,
float reduceBrightColorsOffset, float lastBrightness, boolean isDefaultBrightnessConfig,
boolean isUserSetBrightness, long[] colorValueBuckets, long colorSampleDuration) {
this.brightness = brightness;
this.timeStamp = timeStamp;
this.packageName = packageName;
this.userId = userId;
+ this.uniqueDisplayId = uniqueDisplayId;
this.luxValues = luxValues;
this.luxTimestamps = luxTimestamps;
this.batteryLevel = batteryLevel;
@@ -151,6 +156,7 @@
this.timeStamp = other.timeStamp;
this.packageName = redactPackage ? null : other.packageName;
this.userId = other.userId;
+ this.uniqueDisplayId = other.uniqueDisplayId;
this.luxValues = other.luxValues;
this.luxTimestamps = other.luxTimestamps;
this.batteryLevel = other.batteryLevel;
@@ -172,6 +178,7 @@
timeStamp = source.readLong();
packageName = source.readString();
userId = source.readInt();
+ uniqueDisplayId = source.readString();
luxValues = source.createFloatArray();
luxTimestamps = source.createLongArray();
batteryLevel = source.readFloat();
@@ -209,6 +216,7 @@
dest.writeLong(timeStamp);
dest.writeString(packageName);
dest.writeInt(userId);
+ dest.writeString(uniqueDisplayId);
dest.writeFloatArray(luxValues);
dest.writeLongArray(luxTimestamps);
dest.writeFloat(batteryLevel);
@@ -231,6 +239,7 @@
private long mTimeStamp;
private String mPackageName;
private int mUserId;
+ private String mUniqueDisplayId;
private float[] mLuxValues;
private long[] mLuxTimestamps;
private float mBatteryLevel;
@@ -270,6 +279,12 @@
return this;
}
+ /** {@see BrightnessChangeEvent#uniqueScreenId} */
+ public Builder setUniqueDisplayId(String uniqueId) {
+ mUniqueDisplayId = uniqueId;
+ return this;
+ }
+
/** {@see BrightnessChangeEvent#luxValues} */
public Builder setLuxValues(float[] luxValues) {
mLuxValues = luxValues;
@@ -354,11 +369,11 @@
/** Builds a BrightnessChangeEvent */
public BrightnessChangeEvent build() {
return new BrightnessChangeEvent(mBrightness, mTimeStamp,
- mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel,
- mPowerBrightnessFactor, mNightMode, mColorTemperature, mReduceBrightColors,
- mReduceBrightColorsStrength, mReduceBrightColorsOffset, mLastBrightness,
- mIsDefaultBrightnessConfig, mIsUserSetBrightness, mColorValueBuckets,
- mColorSampleDuration);
+ mPackageName, mUserId, mUniqueDisplayId, mLuxValues, mLuxTimestamps,
+ mBatteryLevel, mPowerBrightnessFactor, mNightMode, mColorTemperature,
+ mReduceBrightColors, mReduceBrightColorsStrength, mReduceBrightColorsOffset,
+ mLastBrightness, mIsDefaultBrightnessConfig, mIsUserSetBrightness,
+ mColorValueBuckets, mColorSampleDuration);
}
}
}
diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java
index e61d931..6ddea50 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.java
+++ b/core/java/android/hardware/face/FaceSensorProperties.java
@@ -16,25 +16,75 @@
package android.hardware.face;
+import android.annotation.IntDef;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Container for face sensor properties.
* @hide
*/
public class FaceSensorProperties extends SensorProperties {
+ /**
+ * @hide
+ */
+ public static final int TYPE_UNKNOWN = 0;
+
+ /**
+ * @hide
+ */
+ public static final int TYPE_RGB = 1;
+
+ /**
+ * @hide
+ */
+ public static final int TYPE_IR = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef({TYPE_UNKNOWN,
+ TYPE_RGB,
+ TYPE_IR})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SensorType {}
+
+ @FaceSensorProperties.SensorType
+ final int mSensorType;
/**
* @hide
*/
public static FaceSensorProperties from(FaceSensorPropertiesInternal internalProp) {
- return new FaceSensorProperties(internalProp.sensorId, internalProp.sensorStrength);
+ final List<ComponentInfo> componentInfo = new ArrayList<>();
+ for (ComponentInfoInternal internalComp : internalProp.componentInfo) {
+ componentInfo.add(ComponentInfo.from(internalComp));
+ }
+ return new FaceSensorProperties(internalProp.sensorId,
+ internalProp.sensorStrength,
+ componentInfo,
+ internalProp.sensorType);
}
/**
* @hide
*/
- public FaceSensorProperties(int sensorId, int sensorStrength) {
- super(sensorId, sensorStrength);
+ public FaceSensorProperties(int sensorId, int sensorStrength,
+ List<ComponentInfo> componentInfo, @FaceSensorProperties.SensorType int sensorType) {
+ super(sensorId, sensorStrength, componentInfo);
+ mSensorType = sensorType;
}
+ /**
+ * @hide
+ * @return The sensor's type.
+ */
+ @FaceSensorProperties.SensorType
+ public int getSensorType() {
+ return mSensorType;
+ }
}
diff --git a/core/java/android/hardware/face/FaceSensorPropertiesInternal.java b/core/java/android/hardware/face/FaceSensorPropertiesInternal.java
index b9c0d12..50ea60a2 100644
--- a/core/java/android/hardware/face/FaceSensorPropertiesInternal.java
+++ b/core/java/android/hardware/face/FaceSensorPropertiesInternal.java
@@ -16,16 +16,24 @@
package android.hardware.face;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.Parcel;
+import java.util.List;
+
/**
* Container for face sensor properties.
* @hide
*/
public class FaceSensorPropertiesInternal extends SensorPropertiesInternal {
/**
+ * See {@link FaceSensorProperties.SensorType}.
+ */
+ public final @FaceSensorProperties.SensorType int sensorType;
+
+ /**
* True if the sensor is able to perform generic face detection, without running the
* matching algorithm, and without affecting the lockout counter.
*/
@@ -40,15 +48,21 @@
* Initializes SensorProperties with specified values
*/
public FaceSensorPropertiesInternal(int sensorId, @SensorProperties.Strength int strength,
- int maxEnrollmentsPerUser, boolean supportsFaceDetection,
- boolean supportsSelfIllumination) {
- super(sensorId, strength, maxEnrollmentsPerUser);
+ int maxEnrollmentsPerUser, List<ComponentInfoInternal> componentInfo,
+ @FaceSensorProperties.SensorType int sensorType, boolean supportsFaceDetection,
+ boolean supportsSelfIllumination, boolean resetLockoutRequiresChallenge) {
+ // resetLockout is managed by the HAL and requires a HardwareAuthToken for all face
+ // HAL interfaces (IBiometricsFace@1.0 HIDL and IFace@1.0 AIDL).
+ super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
+ true /* resetLockoutRequiresHardwareAuthToken */, resetLockoutRequiresChallenge);
+ this.sensorType = sensorType;
this.supportsFaceDetection = supportsFaceDetection;
this.supportsSelfIllumination = supportsSelfIllumination;
}
protected FaceSensorPropertiesInternal(Parcel in) {
super(in);
+ sensorType = in.readInt();
supportsFaceDetection = in.readBoolean();
supportsSelfIllumination = in.readBoolean();
}
@@ -74,12 +88,13 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
+ dest.writeInt(sensorType);
dest.writeBoolean(supportsFaceDetection);
dest.writeBoolean(supportsSelfIllumination);
}
@Override
public String toString() {
- return "ID: " + sensorId + ", Strength: " + sensorStrength;
+ return "ID: " + sensorId + ", Strength: " + sensorStrength + ", Type: " + sensorType;
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
index 684d7d9..a338575 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
@@ -17,10 +17,13 @@
package android.hardware.fingerprint;
import android.annotation.IntDef;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Container for fingerprint sensor properties.
@@ -77,8 +80,13 @@
*/
public static FingerprintSensorProperties from(
FingerprintSensorPropertiesInternal internalProp) {
+ final List<ComponentInfo> componentInfo = new ArrayList<>();
+ for (ComponentInfoInternal internalComp : internalProp.componentInfo) {
+ componentInfo.add(ComponentInfo.from(internalComp));
+ }
return new FingerprintSensorProperties(internalProp.sensorId,
internalProp.sensorStrength,
+ componentInfo,
internalProp.sensorType);
}
@@ -86,8 +94,8 @@
* @hide
*/
public FingerprintSensorProperties(int sensorId, int sensorStrength,
- @SensorType int sensorType) {
- super(sensorId, sensorStrength);
+ List<ComponentInfo> componentInfo, @SensorType int sensorType) {
+ super(sensorId, sensorStrength, componentInfo);
mSensorType = sensorType;
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 51addc9..1b13370 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -21,10 +21,13 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.Parcel;
+import java.util.List;
+
/**
* Container for fingerprint sensor properties.
* @hide
@@ -36,12 +39,6 @@
public final @FingerprintSensorProperties.SensorType int sensorType;
/**
- * IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT
- * cannot be checked
- */
- public final boolean resetLockoutRequiresHardwareAuthToken;
-
- /**
* The location of the center of the sensor if applicable. For example, sensors of type
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
* distance in pixels, measured from the left edge of the screen.
@@ -65,12 +62,17 @@
public FingerprintSensorPropertiesInternal(int sensorId,
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
+ List<ComponentInfoInternal> componentInfo,
@FingerprintSensorProperties.SensorType int sensorType,
boolean resetLockoutRequiresHardwareAuthToken, int sensorLocationX, int sensorLocationY,
int sensorRadius) {
- super(sensorId, strength, maxEnrollmentsPerUser);
+ // IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not
+ // required as it can only be generated/attested/verified by TEE components.
+ // IFingerprint@1.0 handles lockout below the HAL, but does not require a challenge. See
+ // the HAL interface for more details.
+ super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
+ resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
this.sensorType = sensorType;
- this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
this.sensorLocationX = sensorLocationX;
this.sensorLocationY = sensorLocationY;
this.sensorRadius = sensorRadius;
@@ -81,10 +83,11 @@
*/
public FingerprintSensorPropertiesInternal(int sensorId,
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
+ List<ComponentInfoInternal> componentInfo,
@FingerprintSensorProperties.SensorType int sensorType,
boolean resetLockoutRequiresHardwareAuthToken) {
// TODO(b/179175438): Value should be provided from the HAL
- this(sensorId, strength, maxEnrollmentsPerUser, sensorType,
+ this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType,
resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
1636 /* sensorLocationY */, 130 /* sensorRadius */);
}
@@ -96,11 +99,12 @@
// TODO(b/179175438): Remove this constructor once all HALs move to AIDL.
public FingerprintSensorPropertiesInternal(@NonNull Context context, int sensorId,
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
+ List<ComponentInfoInternal> componentInfo,
@FingerprintSensorProperties.SensorType int sensorType,
boolean resetLockoutRequiresHardwareAuthToken) {
- super(sensorId, strength, maxEnrollmentsPerUser);
+ super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
+ resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
this.sensorType = sensorType;
- this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
int[] props = context.getResources().getIntArray(
com.android.internal.R.array.config_udfps_sensor_props);
@@ -119,7 +123,6 @@
protected FingerprintSensorPropertiesInternal(Parcel in) {
super(in);
sensorType = in.readInt();
- resetLockoutRequiresHardwareAuthToken = in.readBoolean();
sensorLocationX = in.readInt();
sensorLocationY = in.readInt();
sensorRadius = in.readInt();
@@ -147,7 +150,6 @@
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(sensorType);
- dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
dest.writeInt(sensorLocationX);
dest.writeInt(sensorLocationY);
dest.writeInt(sensorRadius);
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
new file mode 100644
index 0000000..b79d6e0
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.hardware.fingerprint;
+
+/**
+ * A listener for the high-brightness mode (HBM) transitions. This allows other components to
+ * perform certain actions when the HBM is toggled on or off. For example, a display manager
+ * implementation can subscribe to these events from UdfpsController and adjust the display's
+ * refresh rate when the HBM is enabled.
+ *
+ * @hide
+ */
+oneway interface IUdfpsHbmListener {
+ /**
+ * UdfpsController will call this method when the HBM is enabled.
+ *
+ * @param hbmType The type of HBM that was enabled. See
+ * {@link com.android.systemui.biometrics.HbmTypes}.
+ * @param displayId The displayId for which the HBM is enabled. See
+ * {@link android.view.Display#getDisplayId()}.
+ */
+ void onHbmEnabled(int hbmType, int displayId);
+
+ /**
+ * UdfpsController will call this method when the HBM is disabled.
+ *
+ * @param hbmType The type of HBM that was disabled. See
+ * {@link com.android.systemui.biometrics.HbmTypes}.
+ * @param displayId The displayId for which the HBM is disabled. See
+ * {@link android.view.Display#getDisplayId()}.
+ */
+ void onHbmDisabled(int hbmType, int displayId);
+}
+
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 49beeb3..21ac71c 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -146,6 +146,8 @@
* @return the result of sending the message defined as in ContextHubTransaction.Result
*
* @throws NullPointerException if NanoAppMessage is null
+ * @throws SecurityException if this client doesn't have permissions to send a message to the
+ * nanoapp.
*
* @see NanoAppMessage
* @see ContextHubTransaction.Result
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index 7e484dd..35d00f0 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -117,11 +117,10 @@
* 4) {@link ContextHubClient} performs any cleanup required with the nanoapp
* 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}.
* At this point, any further attempts of communication between the nanoapp and the
- * {@link ContextHubClient} will be dropped by the contexthub and a return value of
- * {@link ContextHubTransaction#RESULT_FAILED_PERMISSION_DENIED} will be used when calling
- * {@link ContextHubClient#sendMessageToNanoApp}. The {@link ContextHubClient} should assume
- * no communciation can happen again until {@link ContextHubManager#AUTHORIZATION_GRANTED} is
- * received.
+ * {@link ContextHubClient} will be dropped by the contexthub and a security exception will
+ * be thrown when calling {@link ContextHubClient#sendMessageToNanoApp}. The
+ * {@link ContextHubClient} should assume no communciation can happen again until
+ * {@link ContextHubManager#AUTHORIZATION_GRANTED} is received.
*
* @param client the client that is associated with this callback
* @param nanoAppId the ID of the nanoapp associated with the new
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index 86f77c0..d11e0a9 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -81,8 +81,7 @@
RESULT_FAILED_AT_HUB,
RESULT_FAILED_TIMEOUT,
RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
- RESULT_FAILED_HAL_UNAVAILABLE,
- RESULT_FAILED_PERMISSION_DENIED
+ RESULT_FAILED_HAL_UNAVAILABLE
})
public @interface Result {}
public static final int RESULT_SUCCESS = 0;
@@ -118,11 +117,6 @@
* Failure mode when the Context Hub HAL was not available.
*/
public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
- /**
- * Failure mode when the user of the API doesn't have the required permissions to perform the
- * operation.
- */
- public static final int RESULT_FAILED_PERMISSION_DENIED = 9;
/**
* A class describing the response for a ContextHubTransaction.
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 9198eb7..5cfcd66 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -171,7 +171,7 @@
SomeArgs args = (SomeArgs) msg.obj;
try {
inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1,
- (IInputMethodPrivilegedOperations) args.arg2, (int) args.arg3);
+ (IInputMethodPrivilegedOperations) args.arg2);
} finally {
args.recycle();
}
@@ -280,10 +280,9 @@
@BinderThread
@Override
public void initializeInternal(IBinder token, int displayId,
- IInputMethodPrivilegedOperations privOps, int configChanges) {
+ IInputMethodPrivilegedOperations privOps) {
mCaller.executeOrSendMessage(
- mCaller.obtainMessageIOOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps,
- configChanges));
+ mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 40a0fc4..7e2be01 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -70,7 +70,6 @@
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -132,7 +131,6 @@
import android.window.WindowMetricsHelper;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
@@ -515,8 +513,6 @@
private boolean mIsAutomotive;
private Handler mHandler;
private boolean mImeSurfaceScheduledForRemoval;
- private Configuration mLastKnownConfig;
- private int mHandledConfigChanges;
/**
* An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput}
@@ -592,13 +588,12 @@
@MainThread
@Override
public final void initializeInternal(@NonNull IBinder token, int displayId,
- IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
+ IInputMethodPrivilegedOperations privilegedOperations) {
if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) {
Log.w(TAG, "The token has already registered, ignore this initialization.");
return;
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
- mHandledConfigChanges = configChanges;
mPrivOps.set(privilegedOperations);
InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
updateInputMethodDisplay(displayId);
@@ -826,9 +821,6 @@
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
}
final boolean isVisible = isInputViewShown();
- if (isVisible && getResources() != null) {
- mLastKnownConfig = new Configuration(getResources().getConfiguration());
- }
final boolean visibilityChanged = isVisible != wasVisible;
if (resultReceiver != null) {
resultReceiver.send(visibilityChanged
@@ -1436,30 +1428,10 @@
* state: {@link #onStartInput} if input is active, and
* {@link #onCreateInputView} and {@link #onStartInputView} and related
* appropriate functions if the UI is displayed.
- * <p>Starting with {@link Build.VERSION_CODES#S}, IMEs can opt into handling configuration
- * changes themselves instead of being restarted with
- * {@link android.R.styleable#InputMethod_configChanges}.
*/
@Override public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- if (shouldImeRestartForConfig(newConfig)) {
- resetStateForNewConfiguration();
- }
- }
-
- /**
- * @return {@code true} if {@link InputMethodService} needs to restart to handle
- * .{@link #onConfigurationChanged(Configuration)}
- */
- @VisibleForTesting
- boolean shouldImeRestartForConfig(@NonNull Configuration newConfig) {
- if (mLastKnownConfig == null) {
- return true;
- }
- // If the new config is the same as the config this Service is already running with,
- // then don't bother calling resetStateForNewConfiguration.
- int unhandledDiff = (mLastKnownConfig.diffPublicOnly(newConfig) & ~mHandledConfigChanges);
- return unhandledDiff != 0;
+ resetStateForNewConfiguration();
}
private void resetStateForNewConfiguration() {
@@ -3209,17 +3181,7 @@
requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS);
}
}
-
- @VisibleForTesting
- void setLastKnownConfig(@NonNull Configuration config) {
- mLastKnownConfig = config;
- }
-
- @VisibleForTesting
- void setHandledConfigChanges(int configChanges) {
- mHandledConfigChanges = configChanges;
- }
-
+
void startExtractingText(boolean inputChanged) {
final ExtractEditText eet = mExtractEditText;
if (eet != null && getCurrentInputStarted()
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index dfb1e99..00c6913 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -25,4 +25,5 @@
void onUidPoliciesChanged(int uid, int uidPolicies);
void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes);
void onSubscriptionPlansChanged(int subId, in SubscriptionPlan[] plans);
+ void onBlockedReasonChanged(int uid, int oldBlockedReason, int newBlockedReason);
}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 9bf791b..171c6a2 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -62,6 +62,7 @@
3 - enabled
*/
int getRestrictBackgroundByCaller();
+ int getRestrictBackgroundStatus(int uid);
void setDeviceIdleMode(boolean enabled);
void setWifiMeteredOverride(String networkId, int meteredOverride);
diff --git a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/core/java/android/net/IPacProxyInstalledListener.aidl
similarity index 70%
copy from packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
copy to core/java/android/net/IPacProxyInstalledListener.aidl
index 7979afc..b1f946e 100644
--- a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
+++ b/core/java/android/net/IPacProxyInstalledListener.aidl
@@ -1,12 +1,11 @@
-/**
- *
+/*
* 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,7 +16,10 @@
package android.net;
-/** @hide */
-oneway interface IOnSetOemNetworkPreferenceListener {
- void onComplete();
+import android.net.Network;
+import android.net.ProxyInfo;
+
+/** {@hide} */
+oneway interface IPacProxyInstalledListener {
+ void onPacProxyInstalled(in Network network, in ProxyInfo proxy);
}
diff --git a/core/java/android/net/IPacProxyManager.aidl b/core/java/android/net/IPacProxyManager.aidl
new file mode 100644
index 0000000..8f65c56
--- /dev/null
+++ b/core/java/android/net/IPacProxyManager.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.IPacProxyInstalledListener;
+import android.net.ProxyInfo;
+
+/** {@hide} */
+interface IPacProxyManager
+{
+ void addListener(IPacProxyInstalledListener listener);
+ void removeListener(IPacProxyInstalledListener listener);
+ void setCurrentProxyScriptUrl(in ProxyInfo proxyInfo);
+}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 6641206..3da1227 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.ActivityManager;
@@ -44,6 +45,8 @@
import android.util.Pair;
import android.util.Range;
+import com.android.internal.util.function.pooled.PooledLambda;
+
import com.google.android.collect.Sets;
import java.lang.annotation.Retention;
@@ -53,6 +56,7 @@
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
/**
* Manager for creating and modifying network policy rules.
@@ -60,6 +64,7 @@
* @hide
*/
@TestApi
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SystemService(Context.NETWORK_POLICY_SERVICE)
public class NetworkPolicyManager {
@@ -198,12 +203,157 @@
})
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;
+
+ /**
+ * Flag to indicate that app is not exempt from any network restrictions.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_NONE = 0;
+ /**
+ * Flag to indicate that app is exempt from certain network restrictions because of it being a
+ * system component.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_SYSTEM = 1 << 0;
+ /**
+ * Flag to indicate that app is exempt from certain network restrictions because of it being
+ * in the foreground.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_FOREGROUND = 1 << 1;
+ /**
+ * Flag to indicate that app is exempt from certain network restrictions because of it being
+ * in the {@code allow-in-power-save} list.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_POWER_SAVE_ALLOWLIST = 1 << 2;
+ /**
+ * Flag to indicate that app is exempt from certain network restrictions because of it being
+ * in the {@code allow-in-power-save-except-idle} list.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST = 1 << 3;
+ /**
+ * Flag to indicate that app is exempt from certain network restrictions because of it holding
+ * certain privileged permissions.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS = 1 << 4;
+ /**
+ * Flag to indicate that app is exempt from certain metered network restrictions because user
+ * explicitly exempted it.
+ *
+ * @hide
+ */
+ public static final int ALLOWED_METERED_REASON_USER_EXEMPTED = 1 << 16;
+
+ /** @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;
private final Map<SubscriptionCallback, SubscriptionCallbackProxy>
- mCallbackMap = new ConcurrentHashMap<>();
+ mSubscriptionCallbackMap = new ConcurrentHashMap<>();
+ private final Map<NetworkPolicyCallback, NetworkPolicyCallbackProxy>
+ mNetworkPolicyCallbackMap = new ConcurrentHashMap<>();
/** @hide */
public NetworkPolicyManager(Context context, INetworkPolicyManager service) {
@@ -318,7 +468,7 @@
}
final SubscriptionCallbackProxy callbackProxy = new SubscriptionCallbackProxy(callback);
- if (null != mCallbackMap.putIfAbsent(callback, callbackProxy)) {
+ if (null != mSubscriptionCallbackMap.putIfAbsent(callback, callbackProxy)) {
throw new IllegalArgumentException("Callback is already registered.");
}
registerListener(callbackProxy);
@@ -331,7 +481,7 @@
throw new NullPointerException("Callback cannot be null.");
}
- final SubscriptionCallbackProxy callbackProxy = mCallbackMap.remove(callback);
+ final SubscriptionCallbackProxy callbackProxy = mSubscriptionCallbackMap.remove(callback);
if (callbackProxy == null) return;
unregisterListener(callbackProxy);
@@ -379,6 +529,26 @@
}
/**
+ * Determines if an UID is subject to metered network restrictions while running in background.
+ *
+ * @param uid The UID whose status needs to be checked.
+ * @return {@link ConnectivityManager#RESTRICT_BACKGROUND_STATUS_DISABLED},
+ * {@link ConnectivityManager##RESTRICT_BACKGROUND_STATUS_ENABLED},
+ * or {@link ConnectivityManager##RESTRICT_BACKGROUND_STATUS_WHITELISTED} to denote
+ * the current status of the UID.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+ public int getRestrictBackgroundStatus(int uid) {
+ try {
+ return mService.getRestrictBackgroundStatus(uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Override connections to be temporarily marked as either unmetered or congested,
* along with automatic timeouts if desired.
*
@@ -460,9 +630,8 @@
* @param meteredNetwork True if the network is metered.
* @return true if networking is blocked for the given uid according to current networking
* policies.
- *
- * @hide
*/
+ @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
public boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
try {
return mService.isUidNetworkingBlocked(uid, meteredNetwork);
@@ -501,9 +670,8 @@
*
* @param uid The target uid.
* @return true if the given uid is restricted from doing networking on metered networks.
- *
- * @hide
*/
+ @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
public boolean isUidRestrictedOnMeteredNetworks(int uid) {
try {
return mService.isUidRestrictedOnMeteredNetworks(uid);
@@ -513,11 +681,15 @@
}
/**
- * Get multipath preference for the given network.
+ * Gets a hint on whether it is desirable to use multipath data transfer on the given network.
+ *
+ * @return One of the ConnectivityManager.MULTIPATH_PREFERENCE_* constants.
*
* @hide
*/
- public int getMultipathPreference(Network network) {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+ public int getMultipathPreference(@NonNull Network network) {
try {
return mService.getMultipathPreference(network);
} catch (RemoteException e) {
@@ -689,6 +861,142 @@
return WifiInfo.sanitizeSsid(ssid);
}
+ /**
+ * Returns whether network access of an UID is blocked or not based on {@code blockedReasons}
+ * corresponding to it.
+ *
+ * {@code blockedReasons} would be a bitwise {@code OR} combination of the
+ * {@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
+ * 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) {
+ if (blockedReasons == BLOCKED_REASON_NONE) {
+ return false;
+ }
+ final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK);
+ if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) {
+ return true;
+ }
+ if (meteredNetwork) {
+ return blockedReasons != BLOCKED_REASON_NONE;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the {@code string} representation of {@code blockedReasons} argument.
+ *
+ * @param blockedReasons Value indicating the reasons for why the network access of an UID is
+ * blocked.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
+ public static String blockedReasonsToString(@BlockedReason int blockedReasons) {
+ return DebugUtils.flagsToString(NetworkPolicyManager.class, "BLOCKED_", blockedReasons);
+ }
+
+ /**
+ * Register a {@link NetworkPolicyCallback} to listen for changes to network blocked status
+ * of apps.
+ *
+ * Note that when a caller tries to register a new callback, it might replace a previously
+ * registered callback if it is considered equal to the new one, based on the
+ * {@link Object#equals(Object)} check.
+ *
+ * @param executor The {@link Executor} to run the callback on.
+ * @param callback The {@link NetworkPolicyCallback} to be registered.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
+ public void registerNetworkPolicyCallback(@Nullable Executor executor,
+ @NonNull NetworkPolicyCallback callback) {
+ if (callback == null) {
+ throw new NullPointerException("Callback cannot be null.");
+ }
+
+ final NetworkPolicyCallbackProxy callbackProxy = new NetworkPolicyCallbackProxy(
+ executor, callback);
+ registerListener(callbackProxy);
+ mNetworkPolicyCallbackMap.put(callback, callbackProxy);
+ }
+
+ /**
+ * Unregister a previously registered {@link NetworkPolicyCallback}.
+ *
+ * @param callback The {@link NetworkPolicyCallback} to be unregistered.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
+ public void unregisterNetworkPolicyCallback(@NonNull NetworkPolicyCallback callback) {
+ if (callback == null) {
+ throw new NullPointerException("Callback cannot be null.");
+ }
+
+ final NetworkPolicyCallbackProxy callbackProxy = mNetworkPolicyCallbackMap.remove(callback);
+ if (callbackProxy == null) return;
+ unregisterListener(callbackProxy);
+ }
+
+ /**
+ * Interface for the callback to listen for changes to network blocked status of apps.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public interface NetworkPolicyCallback {
+ /**
+ * Called when the reason for why the network access of an UID is blocked changes.
+ *
+ * @param uid The UID for which the blocked status changed.
+ * @param blockedReasons Value indicating the reasons for why the network access of an
+ * UID is blocked.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ default void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {}
+ }
+
+ /** @hide */
+ public static class NetworkPolicyCallbackProxy extends Listener {
+ private final Executor mExecutor;
+ private final NetworkPolicyCallback mCallback;
+
+ NetworkPolicyCallbackProxy(@Nullable Executor executor,
+ @NonNull NetworkPolicyCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onBlockedReasonChanged(int uid, @BlockedReason int oldBlockedReasons,
+ @BlockedReason int newBlockedReasons) {
+ if (oldBlockedReasons != newBlockedReasons) {
+ dispatchOnUidBlockedReasonChanged(mExecutor, mCallback, uid, newBlockedReasons);
+ }
+ }
+ }
+
+ private static void dispatchOnUidBlockedReasonChanged(@Nullable Executor executor,
+ @NonNull NetworkPolicyCallback callback, int uid, @BlockedReason int blockedReasons) {
+ if (executor == null) {
+ callback.onUidBlockedReasonChanged(uid, blockedReasons);
+ } else {
+ executor.execute(PooledLambda.obtainRunnable(
+ NetworkPolicyCallback::onUidBlockedReasonChanged,
+ callback, uid, blockedReasons).recycleOnUse());
+ }
+ }
+
/** @hide */
public static class SubscriptionCallback {
/**
@@ -743,5 +1051,7 @@
@Override public void onSubscriptionOverride(int subId, int overrideMask,
int overrideValue, int[] networkTypes) { }
@Override public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { }
+ @Override public void onBlockedReasonChanged(int uid,
+ int oldBlockedReasons, int newBlockedReasons) { }
}
}
diff --git a/core/java/android/net/PacProxyManager.java b/core/java/android/net/PacProxyManager.java
new file mode 100644
index 0000000..8f7ad8c
--- /dev/null
+++ b/core/java/android/net/PacProxyManager.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@SystemService(Context.PAC_PROXY_SERVICE)
+public class PacProxyManager {
+ private final Context mContext;
+ private final IPacProxyManager mService;
+ @GuardedBy("mListenerMap")
+ private final HashMap<PacProxyInstalledListener, PacProxyInstalledListenerProxy>
+ mListenerMap = new HashMap<>();
+
+ /** @hide */
+ public PacProxyManager(Context context, IPacProxyManager service) {
+ Objects.requireNonNull(service, "missing IPacProxyManager");
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Add a listener to start monitoring events reported by PacProxyService.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void addPacProxyInstalledListener(@NonNull Executor executor,
+ @NonNull PacProxyInstalledListener listener) {
+ try {
+ synchronized (mListenerMap) {
+ final PacProxyInstalledListenerProxy listenerProxy =
+ new PacProxyInstalledListenerProxy(executor, listener);
+
+ if (null != mListenerMap.putIfAbsent(listener, listenerProxy)) {
+ throw new IllegalStateException("Listener is already added.");
+ }
+ mService.addListener(listenerProxy);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove the listener to stop monitoring the event of PacProxyInstalledListener.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void removePacProxyInstalledListener(@NonNull PacProxyInstalledListener listener) {
+ try {
+ synchronized (mListenerMap) {
+ final PacProxyInstalledListenerProxy listenerProxy = mListenerMap.remove(listener);
+ if (listenerProxy == null) return;
+ mService.removeListener(listenerProxy);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Updates the PAC Proxy Installer with current Proxy information.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void setCurrentProxyScriptUrl(@Nullable ProxyInfo proxy) {
+ try {
+ mService.setCurrentProxyScriptUrl(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * A callback interface for monitoring changes of PAC proxy information.
+ */
+ public interface PacProxyInstalledListener {
+ /**
+ * Notify that the PAC proxy has been installed. Note that this method will be called with
+ * a ProxyInfo with an empty PAC URL when the PAC proxy is removed.
+ *
+ * This method supports different PAC proxies per-network but not all devices might support
+ * per-network proxies. In that case it will be applied globally.
+ *
+ * @param network the network for which this proxy installed.
+ * @param proxy the installed proxy.
+ */
+ void onPacProxyInstalled(@Nullable Network network, @NonNull ProxyInfo proxy);
+ }
+
+ /**
+ * PacProxyInstalledListener proxy for PacProxyInstalledListener object.
+ * @hide
+ */
+ public class PacProxyInstalledListenerProxy extends IPacProxyInstalledListener.Stub {
+ private final Executor mExecutor;
+ private final PacProxyInstalledListener mListener;
+
+ PacProxyInstalledListenerProxy(Executor executor, PacProxyInstalledListener listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onPacProxyInstalled(Network network, ProxyInfo proxy) {
+ Binder.withCleanCallingIdentity(() -> {
+ mExecutor.execute(() -> {
+ mListener.onPacProxyInstalled(network, proxy);
+ });
+ });
+ }
+ }
+}
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index 326943a..84b7eec 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -51,7 +51,7 @@
ServiceManager.getService(PROXY_SERVICE));
if (mProxyService == null) {
// Added because of b10267814 where mako is restarting.
- Log.e(TAG, "PacProxyInstaller: no proxy service");
+ Log.e(TAG, "PacProxyService: no proxy service");
}
mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY);
}
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 77754d1..5f65d46 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -86,13 +86,21 @@
public static final int TYPE_VPN_LEGACY = 3;
/**
+ * An VPN created by OEM code through other means than {@link VpnService} or {@link VpnManager}.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int TYPE_VPN_OEM = 4;
+
+ /**
* Channel for VPN notifications.
* @hide
*/
public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
/** @hide */
- @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY})
+ @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY,
+ TYPE_VPN_OEM})
@Retention(RetentionPolicy.SOURCE)
public @interface VpnType {}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 9f83b21..d4e8e2d 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -52,13 +52,12 @@
* Network}s.
*
* <p>A VCN connection based on this configuration will be brought up dynamically based on device
- * settings, and filed NetworkRequests. Underlying networks will be selected based on the services
- * required by this configuration (as represented by network capabilities), and must be part of the
- * subscription group under which this configuration is registered (see {@link
+ * settings, and filed NetworkRequests. Underlying Networks must provide INTERNET connectivity, and
+ * must be part of the subscription group under which this configuration is registered (see {@link
* VcnManager#setVcnConfig}).
*
- * <p>As an abstraction of a cellular network, services that can be provided by a VCN network, or
- * required for underlying networks are limited to services provided by cellular networks:
+ * <p>As an abstraction of a cellular network, services that can be provided by a VCN network are
+ * limited to services provided by cellular networks:
*
* <ul>
* <li>{@link NetworkCapabilities#NET_CAPABILITY_MMS}
@@ -214,13 +213,6 @@
checkValidCapability(cap);
}
- Preconditions.checkArgument(
- mUnderlyingCapabilities != null && !mUnderlyingCapabilities.isEmpty(),
- "underlyingCapabilities was null or empty");
- for (Integer cap : getAllUnderlyingCapabilities()) {
- checkValidCapability(cap);
- }
-
Objects.requireNonNull(mRetryIntervalsMs, "retryIntervalsMs was null");
validateRetryInterval(mRetryIntervalsMs);
@@ -295,7 +287,9 @@
*
* @see Builder#addRequiredUnderlyingCapability(int)
* @see Builder#removeRequiredUnderlyingCapability(int)
+ * @hide
*/
+ // TODO(b/182219992): Remove, and add when per-transport capabilities are supported
@NonNull
public int[] getRequiredUnderlyingCapabilities() {
// Sorted set guarantees ordering
@@ -470,7 +464,9 @@
* @return this {@link Builder} instance, for chaining
* @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
* networks
+ * @hide
*/
+ // TODO(b/182219992): Remove, and add when per-transport capabilities are supported
@NonNull
public Builder addRequiredUnderlyingCapability(
@VcnSupportedCapability int underlyingCapability) {
@@ -492,7 +488,9 @@
* @return this {@link Builder} instance, for chaining
* @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
* networks
+ * @hide
*/
+ // TODO(b/182219992): Remove, and add when per-transport capabilities are supported
@NonNull
@SuppressLint("BuilderSetStyle") // For consistency with NetCaps.Builder add/removeCap
public Builder removeRequiredUnderlyingCapability(
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 8ebf757..b73fdbf 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -73,7 +73,8 @@
public class VcnManager {
@NonNull private static final String TAG = VcnManager.class.getSimpleName();
- private static final Map<VcnNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
+ private static final Map<
+ VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder>
REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>();
@NonNull private final Context mContext;
@@ -93,13 +94,13 @@
}
/**
- * Get all currently registered VcnNetworkPolicyListeners for testing purposes.
+ * Get all currently registered VcnNetworkPolicyChangeListeners for testing purposes.
*
* @hide
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
@NonNull
- public static Map<VcnNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
+ public static Map<VcnNetworkPolicyChangeListener, VcnUnderlyingNetworkPolicyListenerBinder>
getAllPolicyListeners() {
return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
}
@@ -162,14 +163,14 @@
}
// TODO(b/180537630): remove all VcnUnderlyingNetworkPolicyListener refs once Telephony is using
- // the new VcnNetworkPolicyListener API
+ // the new VcnNetworkPolicyChangeListener API
/**
* VcnUnderlyingNetworkPolicyListener is the interface through which internal system components
* can register to receive updates for VCN-underlying Network policies from the System Server.
*
* @hide
*/
- public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyListener {}
+ public interface VcnUnderlyingNetworkPolicyListener extends VcnNetworkPolicyChangeListener {}
/**
* Add a listener for VCN-underlying network policy updates.
@@ -185,7 +186,7 @@
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
public void addVcnUnderlyingNetworkPolicyListener(
@NonNull Executor executor, @NonNull VcnUnderlyingNetworkPolicyListener listener) {
- addVcnNetworkPolicyListener(executor, listener);
+ addVcnNetworkPolicyChangeListener(executor, listener);
}
/**
@@ -198,7 +199,7 @@
*/
public void removeVcnUnderlyingNetworkPolicyListener(
@NonNull VcnUnderlyingNetworkPolicyListener listener) {
- removeVcnNetworkPolicyListener(listener);
+ removeVcnNetworkPolicyChangeListener(listener);
}
/**
@@ -233,20 +234,20 @@
}
/**
- * VcnNetworkPolicyListener is the interface through which internal system components (e.g.
- * Network Factories) can register to receive updates for VCN-underlying Network policies from
- * the System Server.
+ * VcnNetworkPolicyChangeListener is the interface through which internal system components
+ * (e.g. Network Factories) can register to receive updates for VCN-underlying Network policies
+ * from the System Server.
*
* <p>Any Network Factory that brings up Networks capable of being VCN-underlying Networks
- * should register a VcnNetworkPolicyListener. VcnManager will then use this listener to notify
- * the registrant when VCN Network policies change. Upon receiving this signal, the listener
- * must check {@link VcnManager} for the current Network policy result for each of its Networks
- * via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}.
+ * should register a VcnNetworkPolicyChangeListener. VcnManager will then use this listener to
+ * notify the registrant when VCN Network policies change. Upon receiving this signal, the
+ * listener must check {@link VcnManager} for the current Network policy result for each of its
+ * Networks via {@link #applyVcnNetworkPolicy(NetworkCapabilities, LinkProperties)}.
*
* @hide
*/
@SystemApi
- public interface VcnNetworkPolicyListener {
+ public interface VcnNetworkPolicyChangeListener {
/**
* Notifies the implementation that the VCN's underlying Network policy has changed.
*
@@ -260,20 +261,21 @@
/**
* Add a listener for VCN-underlying Network policy updates.
*
- * <p>A {@link VcnNetworkPolicyListener} is eligible to begin receiving callbacks once it is
- * registered. No callbacks are guaranteed upon registration.
+ * <p>A {@link VcnNetworkPolicyChangeListener} is eligible to begin receiving callbacks once it
+ * is registered. No callbacks are guaranteed upon registration.
*
* @param executor the Executor that will be used for invoking all calls to the specified
* Listener
- * @param listener the VcnNetworkPolicyListener to be added
+ * @param listener the VcnNetworkPolicyChangeListener to be added
* @throws SecurityException if the caller does not have permission NETWORK_FACTORY
- * @throws IllegalStateException if the specified VcnNetworkPolicyListener is already registered
+ * @throws IllegalStateException if the specified VcnNetworkPolicyChangeListener is already
+ * registered
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
- public void addVcnNetworkPolicyListener(
- @NonNull Executor executor, @NonNull VcnNetworkPolicyListener listener) {
+ public void addVcnNetworkPolicyChangeListener(
+ @NonNull Executor executor, @NonNull VcnNetworkPolicyChangeListener listener) {
requireNonNull(executor, "executor must not be null");
requireNonNull(listener, "listener must not be null");
@@ -292,15 +294,18 @@
}
/**
- * Remove the specified VcnNetworkPolicyListener from VcnManager.
+ * Remove the specified VcnNetworkPolicyChangeListener from VcnManager.
*
* <p>If the specified listener is not currently registered, this is a no-op.
*
- * @param listener the VcnNetworkPolicyListener that will be removed
+ * @param listener the VcnNetworkPolicyChangeListener that will be removed
+ * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
* @hide
*/
@SystemApi
- public void removeVcnNetworkPolicyListener(@NonNull VcnNetworkPolicyListener listener) {
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public void removeVcnNetworkPolicyChangeListener(
+ @NonNull VcnNetworkPolicyChangeListener listener) {
requireNonNull(listener, "listener must not be null");
VcnUnderlyingNetworkPolicyListenerBinder binder =
@@ -320,8 +325,9 @@
* Applies the network policy for a {@link android.net.Network} with the given parameters.
*
* <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy
- * may have changed via {@link VcnNetworkPolicyListener#onPolicyChanged()}, a Network Provider
- * MUST poll for the updated Network policy based on that Network's capabilities and properties.
+ * may have changed via {@link VcnNetworkPolicyChangeListener#onPolicyChanged()}, a Network
+ * Provider MUST poll for the updated Network policy based on that Network's capabilities and
+ * properties.
*
* @param networkCapabilities the NetworkCapabilities to be used in determining the Network
* policy result for this Network.
@@ -442,7 +448,7 @@
* @param networkCapabilities an array of NetworkCapabilities.NET_CAPABILITY_* capabilities
* for the Gateway Connection that encountered the error, for identification purposes.
* These will be a sorted list with no duplicates and will match {@link
- * VcnGatewayConnectionConfig#getRequiredUnderlyingCapabilities()} for one of the {@link
+ * VcnGatewayConnectionConfig#getExposedCapabilities()} for one of the {@link
* VcnGatewayConnectionConfig}s set in the {@link VcnConfig} for this subscription
* group.
* @param errorCode the code to indicate the error that occurred
@@ -532,17 +538,18 @@
}
/**
- * Binder wrapper for added VcnNetworkPolicyListeners to receive signals from System Server.
+ * Binder wrapper for added VcnNetworkPolicyChangeListeners to receive signals from System
+ * Server.
*
* @hide
*/
private static class VcnUnderlyingNetworkPolicyListenerBinder
extends IVcnUnderlyingNetworkPolicyListener.Stub {
@NonNull private final Executor mExecutor;
- @NonNull private final VcnNetworkPolicyListener mListener;
+ @NonNull private final VcnNetworkPolicyChangeListener mListener;
private VcnUnderlyingNetworkPolicyListenerBinder(
- Executor executor, VcnNetworkPolicyListener listener) {
+ Executor executor, VcnNetworkPolicyChangeListener listener) {
mExecutor = executor;
mListener = listener;
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 66f7bd9..fa6472e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -986,6 +986,24 @@
public abstract void getDeferredJobsLineLocked(StringBuilder sb, int which);
/**
+ * Returns the battery consumption (in microcoulombs) of bluetooth for this uid,
+ * derived from on device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getBluetoothMeasuredBatteryConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from
+ * on device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getCpuMeasuredBatteryConsumptionUC();
+
+ /**
* 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.
@@ -995,6 +1013,15 @@
public abstract long getScreenOnMeasuredBatteryConsumptionUC();
/**
+ * Returns the battery consumption (in microcoulombs) of wifi for this uid,
+ * derived from on device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiMeasuredBatteryConsumptionUC();
+
+ /**
* Returns the battery consumption (in microcoulombs) used by this uid for each
* {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
* type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
@@ -2496,11 +2523,29 @@
};
/**
- * Returned value if power data is unavailable
+ * Returned value if power data is unavailable.
*
* {@hide}
*/
- public static final long POWER_DATA_UNAVAILABLE = -1;
+ public static final long POWER_DATA_UNAVAILABLE = -1L;
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of bluetooth, derived from on
+ * device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getBluetoothMeasuredBatteryConsumptionUC();
+
+ /**
+ * Returns the battery consumption (in microcoulombs) of the cpu, derived from on device power
+ * measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getCpuMeasuredBatteryConsumptionUC();
/**
* Returns the battery consumption (in microcoulombs) of the screen while on, derived from on
@@ -2521,6 +2566,15 @@
public abstract long getScreenDozeMeasuredBatteryConsumptionUC();
/**
+ * Returns the battery consumption (in microcoulombs) of wifi, derived from on
+ * device power measurement data.
+ * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiMeasuredBatteryConsumptionUC();
+
+ /**
* Returns the battery consumption (in microcoulombs) that each
* {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
* type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}) consumed.
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 0d9f715..a2edc93 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -140,7 +140,7 @@
*/
@UnsupportedAppUsage
@TestApi
- public static final boolean IS_EMULATOR = getString("ro.kernel.qemu").equals("1");
+ public static final boolean IS_EMULATOR = getString("ro.boot.qemu").equals("1");
/**
* A hardware serial number, if available. Alphanumeric only, case-insensitive.
diff --git a/core/java/android/os/BytesMatcher.java b/core/java/android/os/BytesMatcher.java
index 8537f47..8974c5e 100644
--- a/core/java/android/os/BytesMatcher.java
+++ b/core/java/android/os/BytesMatcher.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.bluetooth.BluetoothUuid;
import android.net.MacAddress;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.util.HexDump;
@@ -42,207 +43,273 @@
* @hide
*/
public class BytesMatcher implements Predicate<byte[]> {
- private static final String TAG = "BytesMatcher";
+ private static final String TAG = "BytesMatcher";
- private static final char TYPE_ACCEPT = '+';
- private static final char TYPE_REJECT = '-';
+ private static final char TYPE_EXACT_ACCEPT = '+';
+ private static final char TYPE_EXACT_REJECT = '-';
+ private static final char TYPE_PREFIX_ACCEPT = '⊆';
+ private static final char TYPE_PREFIX_REJECT = '⊈';
- private final ArrayList<Rule> mRules = new ArrayList<>();
+ private final ArrayList<Rule> mRules = new ArrayList<>();
- private static class Rule {
- public final char type;
- public final @NonNull byte[] value;
- public final @Nullable byte[] mask;
+ private static class Rule {
+ public final char type;
+ public final @NonNull byte[] value;
+ public final @Nullable byte[] mask;
- public Rule(char type, @NonNull byte[] value, @Nullable byte[] mask) {
- if (mask != null && value.length != mask.length) {
- throw new IllegalArgumentException(
- "Expected length " + value.length + " but found " + mask.length);
- }
- this.type = type;
- this.value = value;
- this.mask = mask;
- }
+ public Rule(char type, @NonNull byte[] value, @Nullable byte[] mask) {
+ if (mask != null && value.length != mask.length) {
+ throw new IllegalArgumentException(
+ "Expected length " + value.length + " but found " + mask.length);
+ }
+ this.type = type;
+ this.value = value;
+ this.mask = mask;
+ }
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- encode(builder);
- return builder.toString();
- }
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ encode(builder);
+ return builder.toString();
+ }
- public void encode(@NonNull StringBuilder builder) {
- builder.append(type);
- builder.append(HexDump.toHexString(value));
- if (mask != null) {
- builder.append('/');
- builder.append(HexDump.toHexString(mask));
- }
- }
+ public void encode(@NonNull StringBuilder builder) {
+ builder.append(type);
+ builder.append(HexDump.toHexString(value));
+ if (mask != null) {
+ builder.append('/');
+ builder.append(HexDump.toHexString(mask));
+ }
+ }
- public boolean test(@NonNull byte[] value) {
- if (value.length != this.value.length) {
- return false;
- }
- for (int i = 0; i < this.value.length; i++) {
- byte local = this.value[i];
- byte remote = value[i];
- if (this.mask != null) {
- local &= this.mask[i];
- remote &= this.mask[i];
- }
- if (local != remote) {
- return false;
- }
- }
- return true;
- }
- }
+ public boolean test(@NonNull byte[] value) {
+ switch (type) {
+ case TYPE_EXACT_ACCEPT:
+ case TYPE_EXACT_REJECT:
+ if (value.length != this.value.length) {
+ return false;
+ }
+ break;
+ case TYPE_PREFIX_ACCEPT:
+ case TYPE_PREFIX_REJECT:
+ if (value.length < this.value.length) {
+ return false;
+ }
+ break;
+ }
+ for (int i = 0; i < this.value.length; i++) {
+ byte local = this.value[i];
+ byte remote = value[i];
+ if (this.mask != null) {
+ local &= this.mask[i];
+ remote &= this.mask[i];
+ }
+ if (local != remote) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
- /**
- * Add a rule that will result in {@link #test(byte[])} returning
- * {@code true} when a value being tested matches it.
- * <p>
- * Rules are tested in the order in which they were originally added, which
- * means a narrow rule can reject a specific value before a later broader
- * rule might accept that same value, or vice versa.
- *
- * @param value to be matched
- * @param mask to be applied to both values before testing for equality; if
- * {@code null} then both values must match exactly
- */
- public void addAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
- mRules.add(new Rule(TYPE_ACCEPT, value, mask));
- }
+ /**
+ * Add a rule that will result in {@link #test(byte[])} returning
+ * {@code true} when a value being tested matches it. This rule will only
+ * match values of the exact same length.
+ * <p>
+ * Rules are tested in the order in which they were originally added, which
+ * means a narrow rule can reject a specific value before a later broader
+ * rule might accept that same value, or vice versa.
+ *
+ * @param value to be matched
+ * @param mask to be applied to both values before testing for equality; if
+ * {@code null} then both values must match exactly
+ */
+ public void addExactAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
+ mRules.add(new Rule(TYPE_EXACT_ACCEPT, value, mask));
+ }
- /**
- * Add a rule that will result in {@link #test(byte[])} returning
- * {@code false} when a value being tested matches it.
- * <p>
- * Rules are tested in the order in which they were originally added, which
- * means a narrow rule can reject a specific value before a later broader
- * rule might accept that same value, or vice versa.
- *
- * @param value to be matched
- * @param mask to be applied to both values before testing for equality; if
- * {@code null} then both values must match exactly
- */
- public void addRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
- mRules.add(new Rule(TYPE_REJECT, value, mask));
- }
+ /**
+ * Add a rule that will result in {@link #test(byte[])} returning
+ * {@code false} when a value being tested matches it. This rule will only
+ * match values of the exact same length.
+ * <p>
+ * Rules are tested in the order in which they were originally added, which
+ * means a narrow rule can reject a specific value before a later broader
+ * rule might accept that same value, or vice versa.
+ *
+ * @param value to be matched
+ * @param mask to be applied to both values before testing for equality; if
+ * {@code null} then both values must match exactly
+ */
+ public void addExactRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
+ mRules.add(new Rule(TYPE_EXACT_REJECT, value, mask));
+ }
- /**
- * Test if the given {@code ParcelUuid} value matches the set of rules
- * configured in this matcher.
- */
- public boolean testBluetoothUuid(@NonNull ParcelUuid value) {
- return test(BluetoothUuid.uuidToBytes(value));
- }
+ /**
+ * Add a rule that will result in {@link #test(byte[])} returning
+ * {@code true} when a value being tested matches it. This rule will match
+ * values of the exact same length or longer.
+ * <p>
+ * Rules are tested in the order in which they were originally added, which
+ * means a narrow rule can reject a specific value before a later broader
+ * rule might accept that same value, or vice versa.
+ *
+ * @param value to be matched
+ * @param mask to be applied to both values before testing for equality; if
+ * {@code null} then both values must match exactly
+ */
+ public void addPrefixAcceptRule(@NonNull byte[] value, @Nullable byte[] mask) {
+ mRules.add(new Rule(TYPE_PREFIX_ACCEPT, value, mask));
+ }
- /**
- * Test if the given {@code MacAddress} value matches the set of rules
- * configured in this matcher.
- */
- public boolean testMacAddress(@NonNull MacAddress value) {
- return test(value.toByteArray());
- }
+ /**
+ * Add a rule that will result in {@link #test(byte[])} returning
+ * {@code false} when a value being tested matches it. This rule will match
+ * values of the exact same length or longer.
+ * <p>
+ * Rules are tested in the order in which they were originally added, which
+ * means a narrow rule can reject a specific value before a later broader
+ * rule might accept that same value, or vice versa.
+ *
+ * @param value to be matched
+ * @param mask to be applied to both values before testing for equality; if
+ * {@code null} then both values must match exactly
+ */
+ public void addPrefixRejectRule(@NonNull byte[] value, @Nullable byte[] mask) {
+ mRules.add(new Rule(TYPE_PREFIX_REJECT, value, mask));
+ }
- /**
- * Test if the given {@code byte[]} value matches the set of rules
- * configured in this matcher.
- */
- @Override
- public boolean test(@NonNull byte[] value) {
- return test(value, false);
- }
+ /**
+ * Test if the given {@code ParcelUuid} value matches the set of rules
+ * configured in this matcher.
+ */
+ public boolean testBluetoothUuid(@NonNull ParcelUuid value) {
+ return test(BluetoothUuid.uuidToBytes(value));
+ }
- /**
- * Test if the given {@code byte[]} value matches the set of rules
- * configured in this matcher.
- */
- public boolean test(@NonNull byte[] value, boolean defaultValue) {
- final int size = mRules.size();
- for (int i = 0; i < size; i++) {
- final Rule rule = mRules.get(i);
- if (rule.test(value)) {
- return (rule.type == TYPE_ACCEPT);
- }
- }
- return defaultValue;
- }
+ /**
+ * Test if the given {@code MacAddress} value matches the set of rules
+ * configured in this matcher.
+ */
+ public boolean testMacAddress(@NonNull MacAddress value) {
+ return test(value.toByteArray());
+ }
- /**
- * Encode the given matcher into a human-readable {@link String} which can
- * be used to transport matchers across device boundaries.
- * <p>
- * The human-readable format is an ordered list separated by commas, where
- * each rule is a {@code +} or {@code -} symbol indicating if the match
- * should be accepted or rejected, then followed by a hex value and an
- * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
- * encoded matcher.
- *
- * @see #decode(String)
- */
- public static @NonNull String encode(@NonNull BytesMatcher matcher) {
- final StringBuilder builder = new StringBuilder();
- final int size = matcher.mRules.size();
- for (int i = 0; i < size; i++) {
- final Rule rule = matcher.mRules.get(i);
- rule.encode(builder);
- builder.append(',');
- }
- builder.deleteCharAt(builder.length() - 1);
- return builder.toString();
- }
+ /**
+ * Test if the given {@code byte[]} value matches the set of rules
+ * configured in this matcher.
+ */
+ @Override
+ public boolean test(@NonNull byte[] value) {
+ return test(value, false);
+ }
- /**
- * Decode the given human-readable {@link String} used to transport matchers
- * across device boundaries.
- * <p>
- * The human-readable format is an ordered list separated by commas, where
- * each rule is a {@code +} or {@code -} symbol indicating if the match
- * should be accepted or rejected, then followed by a hex value and an
- * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
- * encoded matcher.
- *
- * @see #encode(BytesMatcher)
- */
- public static @NonNull BytesMatcher decode(@NonNull String value) {
- final BytesMatcher matcher = new BytesMatcher();
- final int length = value.length();
- for (int i = 0; i < length;) {
- final char type = value.charAt(i);
+ /**
+ * Test if the given {@code byte[]} value matches the set of rules
+ * configured in this matcher.
+ */
+ public boolean test(@NonNull byte[] value, boolean defaultValue) {
+ final int size = mRules.size();
+ for (int i = 0; i < size; i++) {
+ final Rule rule = mRules.get(i);
+ if (rule.test(value)) {
+ switch (rule.type) {
+ case TYPE_EXACT_ACCEPT:
+ case TYPE_PREFIX_ACCEPT:
+ return true;
+ case TYPE_EXACT_REJECT:
+ case TYPE_PREFIX_REJECT:
+ return false;
+ }
+ }
+ }
+ return defaultValue;
+ }
- int nextRule = value.indexOf(',', i);
- int nextMask = value.indexOf('/', i);
+ /**
+ * Encode the given matcher into a human-readable {@link String} which can
+ * be used to transport matchers across device boundaries.
+ * <p>
+ * The human-readable format is an ordered list separated by commas, where
+ * each rule is a {@code +} or {@code -} symbol indicating if the match
+ * should be accepted or rejected, then followed by a hex value and an
+ * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
+ * encoded matcher.
+ *
+ * @see #decode(String)
+ */
+ public static @NonNull String encode(@NonNull BytesMatcher matcher) {
+ final StringBuilder builder = new StringBuilder();
+ final int size = matcher.mRules.size();
+ for (int i = 0; i < size; i++) {
+ final Rule rule = matcher.mRules.get(i);
+ rule.encode(builder);
+ builder.append(',');
+ }
+ if (builder.length() > 0) {
+ builder.deleteCharAt(builder.length() - 1);
+ }
+ return builder.toString();
+ }
- if (nextRule == -1) nextRule = length;
- if (nextMask > nextRule) nextMask = -1;
+ /**
+ * Decode the given human-readable {@link String} used to transport matchers
+ * across device boundaries.
+ * <p>
+ * The human-readable format is an ordered list separated by commas, where
+ * each rule is a {@code +} or {@code -} symbol indicating if the match
+ * should be accepted or rejected, then followed by a hex value and an
+ * optional hex mask. For example, {@code -caff,+cafe/ff00} is a valid
+ * encoded matcher.
+ *
+ * @see #encode(BytesMatcher)
+ */
+ public static @NonNull BytesMatcher decode(@Nullable String value) {
+ final BytesMatcher matcher = new BytesMatcher();
+ if (TextUtils.isEmpty(value)) return matcher;
- final byte[] ruleValue;
- final byte[] ruleMask;
- if (nextMask >= 0) {
- ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextMask));
- ruleMask = HexDump.hexStringToByteArray(value.substring(nextMask + 1, nextRule));
- } else {
- ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextRule));
- ruleMask = null;
- }
+ final int length = value.length();
+ for (int i = 0; i < length;) {
+ final char type = value.charAt(i);
- switch (type) {
- case TYPE_ACCEPT:
- matcher.addAcceptRule(ruleValue, ruleMask);
- break;
- case TYPE_REJECT:
- matcher.addRejectRule(ruleValue, ruleMask);
- break;
- default:
- Log.w(TAG, "Ignoring unknown type " + type);
- break;
- }
+ int nextRule = value.indexOf(',', i);
+ int nextMask = value.indexOf('/', i);
- i = nextRule + 1;
- }
- return matcher;
- }
+ if (nextRule == -1) nextRule = length;
+ if (nextMask > nextRule) nextMask = -1;
+
+ final byte[] ruleValue;
+ final byte[] ruleMask;
+ if (nextMask >= 0) {
+ ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextMask));
+ ruleMask = HexDump.hexStringToByteArray(value.substring(nextMask + 1, nextRule));
+ } else {
+ ruleValue = HexDump.hexStringToByteArray(value.substring(i + 1, nextRule));
+ ruleMask = null;
+ }
+
+ switch (type) {
+ case TYPE_EXACT_ACCEPT:
+ matcher.addExactAcceptRule(ruleValue, ruleMask);
+ break;
+ case TYPE_EXACT_REJECT:
+ matcher.addExactRejectRule(ruleValue, ruleMask);
+ break;
+ case TYPE_PREFIX_ACCEPT:
+ matcher.addPrefixAcceptRule(ruleValue, ruleMask);
+ break;
+ case TYPE_PREFIX_REJECT:
+ matcher.addPrefixRejectRule(ruleValue, ruleMask);
+ break;
+ default:
+ Log.w(TAG, "Ignoring unknown type " + type);
+ break;
+ }
+
+ i = nextRule + 1;
+ }
+ return matcher;
+ }
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a19728c..2c4d130 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -197,6 +197,9 @@
* application as the normal behavior. Notifications that pop up and want
* the device to be on are the exception; use this flag to be like them.
* </p><p>
+ * Android TV playback devices attempt to turn on the HDMI-connected TV via HDMI-CEC on any
+ * wake-up, including wake-ups triggered by wake locks.
+ * </p><p>
* Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
* </p>
*/
@@ -1299,6 +1302,9 @@
* device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
* default display group} is already off then nothing will happen.
*
+ * <p>If the device is an Android TV playback device and the current active source on the
+ * HDMI-connected TV, it will attempt to turn off that TV via HDMI-CEC.
+ *
* <p>
* Overrides all the wake locks that are held.
* This is what happens when the power key is pressed to turn off the screen.
@@ -1430,6 +1436,10 @@
* be turned on. Additionally, if the device is asleep it will be awoken. If the {@link
* android.view.Display#DEFAULT_DISPLAY default display} is already on then nothing will happen.
*
+ * <p>If the device is an Android TV playback device, it will attempt to turn on the
+ * HDMI-connected TV and become the current active source via the HDMI-CEC One Touch Play
+ * feature.
+ *
* <p>
* This is what happens when the power key is pressed to turn on the screen.
* </p><p>
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e75e224..bf859d4 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -16,8 +16,11 @@
package android.os;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
@@ -108,6 +111,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int VPN_UID = 1016;
/**
@@ -225,6 +229,12 @@
public static final int FSVERITY_CERT_UID = 1075;
/**
+ * GID that gives access to USB OTG (unreliable) volumes on /mnt/media_rw/<vol name>
+ * @hide
+ */
+ public static final int EXTERNAL_STORAGE_GID = 1077;
+
+ /**
* GID that gives write access to app-private data directories on external
* storage (used on devices without sdcardfs only).
* @hide
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index df4ade0..d89c3d5 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -21,6 +21,7 @@
import android.util.Slog;
import java.io.PrintWriter;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -354,6 +355,23 @@
}
/**
+ * Performs {@code action} on each callback and associated cookie, calling {@link
+ * #beginBroadcast()}/{@link #finishBroadcast()} before/after looping.
+ *
+ * @hide
+ */
+ public <C> void broadcast(BiConsumer<E, C> action) {
+ int itemCount = beginBroadcast();
+ try {
+ for (int i = 0; i < itemCount; i++) {
+ action.accept(getBroadcastItem(i), (C) getBroadcastCookie(i));
+ }
+ } finally {
+ finishBroadcast();
+ }
+ }
+
+ /**
* Returns the number of registered callbacks. Note that the number of registered
* callbacks may differ from the value returned by {@link #beginBroadcast()} since
* the former returns the number of callbacks registered at the time of the call
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b003d23..b90d438 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -208,6 +208,28 @@
public abstract boolean hasAmplitudeControl();
/**
+ * Gets the resonant frequency of the vibrator.
+ *
+ * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
+ * this vibrator is a composite of multiple physical devices.
+ * @hide
+ */
+ public float getResonantFrequency() {
+ return Float.NaN;
+ }
+
+ /**
+ * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
+ *
+ * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
+ * this vibrator is a composite of multiple physical devices.
+ * @hide
+ */
+ public float getQFactor() {
+ return Float.NaN;
+ }
+
+ /**
* Configure an always-on haptics effect.
*
* @param alwaysOnId The board-specific always-on ID to configure.
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 50d2de3..3121b95 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -42,21 +42,27 @@
private final SparseBooleanArray mSupportedEffects;
@Nullable
private final SparseBooleanArray mSupportedPrimitives;
+ private final float mResonantFrequency;
+ private final float mQFactor;
VibratorInfo(Parcel in) {
mId = in.readInt();
mCapabilities = in.readLong();
mSupportedEffects = in.readSparseBooleanArray();
mSupportedPrimitives = in.readSparseBooleanArray();
+ mResonantFrequency = in.readFloat();
+ mQFactor = in.readFloat();
}
/** @hide */
public VibratorInfo(int id, long capabilities, int[] supportedEffects,
- int[] supportedPrimitives) {
+ int[] supportedPrimitives, float resonantFrequency, float qFactor) {
mId = id;
mCapabilities = capabilities;
mSupportedEffects = toSparseBooleanArray(supportedEffects);
mSupportedPrimitives = toSparseBooleanArray(supportedPrimitives);
+ mResonantFrequency = resonantFrequency;
+ mQFactor = qFactor;
}
@Override
@@ -65,6 +71,8 @@
dest.writeLong(mCapabilities);
dest.writeSparseBooleanArray(mSupportedEffects);
dest.writeSparseBooleanArray(mSupportedPrimitives);
+ dest.writeFloat(mResonantFrequency);
+ dest.writeFloat(mQFactor);
}
@Override
@@ -83,12 +91,15 @@
VibratorInfo that = (VibratorInfo) o;
return mId == that.mId && mCapabilities == that.mCapabilities
&& Objects.equals(mSupportedEffects, that.mSupportedEffects)
- && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives);
+ && Objects.equals(mSupportedPrimitives, that.mSupportedPrimitives)
+ && Objects.equals(mResonantFrequency, that.mResonantFrequency)
+ && Objects.equals(mQFactor, that.mQFactor);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives);
+ return Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
+ mResonantFrequency, mQFactor);
}
@Override
@@ -99,6 +110,8 @@
+ ", mCapabilities flags=" + Long.toBinaryString(mCapabilities)
+ ", mSupportedEffects=" + Arrays.toString(getSupportedEffectsNames())
+ ", mSupportedPrimitives=" + Arrays.toString(getSupportedPrimitivesNames())
+ + ", mResonantFrequency=" + mResonantFrequency
+ + ", mQFactor=" + mQFactor
+ '}';
}
@@ -156,6 +169,26 @@
return (mCapabilities & capability) == capability;
}
+ /**
+ * Gets the resonant frequency of the vibrator.
+ *
+ * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
+ * this vibrator is a composite of multiple physical devices.
+ */
+ public float getResonantFrequency() {
+ return mResonantFrequency;
+ }
+
+ /**
+ * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
+ *
+ * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
+ * this vibrator is a composite of multiple physical devices.
+ */
+ public float getQFactor() {
+ return mQFactor;
+ }
+
private String[] getCapabilitiesNames() {
List<String> names = new ArrayList<>();
if (hasCapability(IVibrator.CAP_ON_CALLBACK)) {
diff --git a/core/java/android/os/connectivity/WifiActivityEnergyInfo.java b/core/java/android/os/connectivity/WifiActivityEnergyInfo.java
index 016cc2f..ad74a9f 100644
--- a/core/java/android/os/connectivity/WifiActivityEnergyInfo.java
+++ b/core/java/android/os/connectivity/WifiActivityEnergyInfo.java
@@ -110,7 +110,7 @@
}
// Calculate energy used using PowerProfile.
PowerProfile powerProfile = new PowerProfile(context);
- final double rxIdleCurrent = powerProfile.getAveragePower(
+ final double idleCurrent = powerProfile.getAveragePower(
PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
final double rxCurrent = powerProfile.getAveragePower(
PowerProfile.POWER_WIFI_CONTROLLER_RX);
@@ -121,7 +121,7 @@
return (long) ((txDurationMillis * txCurrent
+ rxDurationMillis * rxCurrent
- + idleDurationMillis * rxIdleCurrent)
+ + idleDurationMillis * idleCurrent)
* voltage);
}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 82c4c71..54905ec 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -39,6 +39,17 @@
public abstract int getExternalStorageMountMode(int uid, String packageName);
/**
+ * Checks whether the {@code packageName} with {@code uid} has full external storage access via
+ * the {@link MANAGE_EXTERNAL_STORAGE} permission.
+ *
+ * @param uid the UID for which to check access.
+ * @param packageName the package in the UID for making the call.
+ * @return whether the {@code packageName} has full external storage access.
+ * Returns {@code true} if it has access, {@code false} otherwise.
+ */
+ public abstract boolean hasExternalStorageAccess(int uid, String packageName);
+
+ /**
* A listener for reset events in the StorageManagerService.
*/
public interface ResetListener {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index bae36b29..177e422 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -41,6 +41,7 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.location.LocationManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
@@ -50,12 +51,14 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.Immutable;
import com.android.internal.util.CollectionUtils;
@@ -82,6 +85,8 @@
public static final String KILL_APP_REASON_GIDS_CHANGED =
"permission grant or revoke changed gids";
+ private static final String SYSTEM_PKG = "android";
+
/**
* Refuse to install package if groups of permissions are bad
* - Permission groups should only be shared between apps sharing a certificate
@@ -857,6 +862,23 @@
}
/**
+ * Check if this package/op combination is exempted from indicators
+ * @return
+ * @hide
+ */
+ public static boolean isSpecialCaseShownIndicator(@NonNull Context context,
+ @NonNull String packageName) {
+
+ if (packageName.equals(SYSTEM_PKG)) {
+ return false;
+ }
+
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled",
+ false)
+ || packageName.equals(context.getString(R.string.config_systemSpeechRecognizer))
+ || context.getSystemService(LocationManager.class).isProviderPackage(packageName);
+ }
+ /**
* Gets the list of packages that have permissions that specified
* {@code requestDontAutoRevokePermissions=true} in their
* {@code application} manifest declaration.
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 4c9e77c..80a3e16 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -31,40 +31,25 @@
import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
-import android.Manifest;
import android.annotation.NonNull;
import android.app.AppOpsManager;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.Attribution;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.icu.text.ListFormatter;
-import android.location.LocationManager;
import android.media.AudioManager;
import android.os.Process;
import android.os.UserHandle;
import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.speech.RecognitionService;
-import android.speech.RecognizerIntent;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-
-import com.android.internal.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
/**
* A helper which gets all apps which have used microphone, camera, and possible location
@@ -111,8 +96,7 @@
private static boolean shouldShowLocationIndicator() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_INDICATORS_ENABLED, false)
- || shouldShowPermissionsHub();
+ PROPERTY_LOCATION_INDICATORS_ENABLED, false);
}
private static long getRecentThreshold(Long now) {
@@ -166,7 +150,7 @@
* Constructor for PermissionUsageHelper
* @param context The context from which to derive the package information
*/
- public PermissionUsageHelper(Context context) {
+ public PermissionUsageHelper(@NonNull Context context) {
mContext = context;
mPkgManager = context.getPackageManager();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
@@ -181,26 +165,10 @@
return mUserContexts.get(user);
}
- // TODO ntmyren: Replace this with better check if this moves beyond teamfood
- private boolean isAppPredictor(String packageName, UserHandle user) {
- return shouldShowPermissionsHub() && getUserContext(user).getPackageManager()
- .checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, packageName)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private boolean isSpeechRecognizerUsage(String op, String packageName) {
- if (!OPSTR_RECORD_AUDIO.equals(op)) {
- return false;
- }
-
- return packageName.equals(
- mContext.getString(R.string.config_systemSpeechRecognizer));
- }
-
/**
* @see PermissionManager.getIndicatorAppOpUsageData
*/
- public List<PermGroupUsage> getOpUsageData(boolean isMicMuted) {
+ public @NonNull List<PermGroupUsage> getOpUsageData(boolean isMicMuted) {
List<PermGroupUsage> usages = new ArrayList<>();
if (!shouldShowIndicators()) {
@@ -216,9 +184,6 @@
}
Map<String, List<OpUsage>> rawUsages = getOpUsages(ops);
- Set<List<PackageAttribution>> proxyChains = getProxyChains(rawUsages.get(MICROPHONE));
- Map<PackageAttribution, CharSequence> packagesWithAttributionLabels =
- getTrustedAttributions(rawUsages.get(MICROPHONE), proxyChains);
ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
@@ -246,15 +211,8 @@
boolean isPhone = false;
String permGroup = usedPermGroups.get(permGroupNum);
- Map<PackageAttribution, CharSequence> pkgAttrLabels = packagesWithAttributionLabels;
- Set<List<PackageAttribution>> proxies = proxyChains;
- if (!MICROPHONE.equals(permGroup)) {
- pkgAttrLabels = new ArrayMap<>();
- proxies = new ArraySet<>();
- }
-
- List<OpUsage> permUsages = removeDuplicatesAndProxies(rawUsages.get(permGroup),
- pkgAttrLabels.keySet(), proxies);
+ ArrayMap<OpUsage, CharSequence> usagesWithLabels =
+ getUniqueUsagesWithLabels(rawUsages.get(permGroup));
if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) {
isPhone = true;
@@ -264,11 +222,11 @@
permGroup = CAMERA;
}
- for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) {
- OpUsage usage = permUsages.get(usageNum);
+ for (int usageNum = 0; usageNum < usagesWithLabels.size(); usageNum++) {
+ OpUsage usage = usagesWithLabels.keyAt(usageNum);
usages.add(new PermGroupUsage(usage.packageName, usage.uid, permGroup,
usage.lastAccessTime, usage.isRunning, isPhone,
- packagesWithAttributionLabels.get(usage.toPackageAttr())));
+ usagesWithLabels.valueAt(usageNum)));
}
}
@@ -298,7 +256,7 @@
long recentThreshold = getRecentThreshold(now);
long runningThreshold = getRunningThreshold(now);
int opFlags = OP_FLAGS_ALL_TRUSTED;
- Map<String, Map<PackageAttribution, OpUsage>> usages = new ArrayMap<>();
+ Map<String, Map<Integer, OpUsage>> usages = new ArrayMap<>();
int numPkgOps = ops.size();
for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) {
@@ -326,10 +284,8 @@
}
if (packageName.equals(SYSTEM_PKG)
- || (!isUserSensitive(packageName, user, op)
- && !isLocationProvider(packageName, user)
- && !isAppPredictor(packageName, user))
- && !isSpeechRecognizerUsage(op, packageName)) {
+ || (!shouldShowPermissionsHub()
+ && !isUserSensitive(packageName, user, op))) {
continue;
}
@@ -340,20 +296,20 @@
AppOpsManager.OpEventProxyInfo proxy = attrOpEntry.getLastProxyInfo(opFlags);
if (proxy != null && proxy.getPackageName() != null) {
proxyUsage = new OpUsage(proxy.getPackageName(), proxy.getAttributionTag(),
- proxy.getUid(), lastAccessTime, isRunning, null);
+ op, proxy.getUid(), lastAccessTime, isRunning, null);
}
String permGroupName = getGroupForOp(op);
- OpUsage usage = new OpUsage(packageName, attributionTag, uid,
+ OpUsage usage = new OpUsage(packageName, attributionTag, op, uid,
lastAccessTime, isRunning, proxyUsage);
- PackageAttribution packageAttr = usage.toPackageAttr();
+ Integer packageAttr = usage.getPackageAttrHash();
if (!usages.containsKey(permGroupName)) {
- ArrayMap<PackageAttribution, OpUsage> map = new ArrayMap<>();
+ ArrayMap<Integer, OpUsage> map = new ArrayMap<>();
map.put(packageAttr, usage);
usages.put(permGroupName, map);
} else {
- Map<PackageAttribution, OpUsage> permGroupUsages =
+ Map<Integer, OpUsage> permGroupUsages =
usages.get(permGroupName);
if (!permGroupUsages.containsKey(packageAttr)) {
permGroupUsages.put(packageAttr, usage);
@@ -375,380 +331,119 @@
return flattenedUsages;
}
- /**
- * Take the list of all usages, figure out any proxy chains, get all possible special
- * attribution labels, and figure out which usages need to show a special label, if any.
- *
- * @param usages The raw permission usages
- *
- * @return A map of package + attribution (in the form of a PackageAttribution object) to
- * trusted attribution label, if there is one
- */
- private ArrayMap<PackageAttribution, CharSequence> getTrustedAttributions(
- List<OpUsage> usages, Set<List<PackageAttribution>> proxyChains) {
- ArrayMap<PackageAttribution, CharSequence> attributions = new ArrayMap<>();
- if (usages == null) {
- return attributions;
- }
-
- Map<PackageAttribution, CharSequence> trustedLabels =
- getTrustedAttributionLabels(usages);
-
- for (List<PackageAttribution> chain : proxyChains) {
- // If this chain is empty, or has only one link, then do not show any special labels
- if (chain.size() <= 1) {
- continue;
- }
-
- // If the last link in the chain is not user sensitive, do not show it.
- boolean lastLinkIsUserSensitive = false;
- for (int i = 0; i < usages.size(); i++) {
- PackageAttribution lastLink = chain.get(chain.size() - 1);
- if (lastLink.equals(usages.get(i).toPackageAttr())) {
- lastLinkIsUserSensitive = true;
- break;
- }
- }
- if (!lastLinkIsUserSensitive) {
- continue;
- }
-
- List<CharSequence> labels = new ArrayList<>();
- for (int i = 0; i < chain.size(); i++) {
- // If this is the last link in the proxy chain, assign it the series of labels
- // Else, if it has a special label, add that label
- // Else, if there are no other apps in the remaining part of the chain which
- // have the same package name, add the app label
- // If it is not the last link in the chain, remove its attribution
- PackageAttribution attr = chain.get(i);
- CharSequence trustedLabel = trustedLabels.get(attr);
- if (i == chain.size() - 1) {
- attributions.put(attr, formatLabelList(labels));
- } else if (trustedLabel != null && !labels.contains(trustedLabel)) {
- labels.add(trustedLabel);
- trustedLabels.remove(attr);
- } else {
- boolean remainingChainHasPackage = false;
- for (int attrNum = i + 1; attrNum < chain.size() - 1; attrNum++) {
- if (chain.get(i).packageName.equals(attr.packageName)) {
- remainingChainHasPackage = true;
- break;
- }
- }
- if (!remainingChainHasPackage) {
- try {
- ApplicationInfo appInfo = mPkgManager.getApplicationInfoAsUser(
- attr.packageName, 0, attr.getUser());
- CharSequence appLabel = appInfo.loadLabel(
- getUserContext(attr.getUser()).getPackageManager());
- labels.add(appLabel);
- } catch (PackageManager.NameNotFoundException e) {
- // Do nothing
- }
- }
- }
- }
- }
-
- for (PackageAttribution attr : trustedLabels.keySet()) {
- attributions.put(attr, trustedLabels.get(attr));
- }
-
- return attributions;
- }
-
private CharSequence formatLabelList(List<CharSequence> labels) {
return ListFormatter.getInstance().format(labels);
}
- /**
- * Get all chains of proxy usages. A proxy chain is defined as one usage at the root, then
- * further proxy usages, where the app and attribution tag of the proxy in the proxy usage
- * matches the previous usage in the chain.
- *
- * @param usages The permission usages
- *
- * @return A set of lists of package attributions. One list represents a chain of proxy usages,
- * with the start of the chain (the usage without a proxy) at position 0, and each usage down
- * the chain has the previous one listed as a proxy usage.
- */
- private Set<List<PackageAttribution>> getProxyChains(List<OpUsage> usages) {
+ private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) {
+ ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>();
+
if (usages == null) {
- return new ArraySet<>();
+ return usagesAndLabels;
}
- ArrayMap<PackageAttribution, ArrayList<PackageAttribution>> proxyChains = new ArrayMap<>();
- // map of usages that still need to be removed, or added to a chain
- ArrayMap<PackageAttribution, OpUsage> remainingUsages = new ArrayMap<>();
- // map of usage.proxy -> usage, telling us if a usage is a proxy
- ArrayMap<PackageAttribution, PackageAttribution> proxies = new ArrayMap<>();
+ ArrayMap<Integer, OpUsage> allUsages = new ArrayMap<>();
+ // map of uid -> most recent non-proxy-related usage for that uid.
+ ArrayMap<Integer, OpUsage> mostRecentUsages = new ArrayMap<>();
+ // set of all uids involved in a proxy usage
+ ArraySet<Integer> proxyUids = new ArraySet<>();
+ // map of usage -> list of proxy app labels
+ ArrayMap<OpUsage, ArrayList<CharSequence>> proxyLabels = new ArrayMap<>();
+ // map of usage.proxy hash -> usage hash, telling us if a usage is a proxy
+ ArrayMap<Integer, OpUsage> proxies = new ArrayMap<>();
for (int i = 0; i < usages.size(); i++) {
OpUsage usage = usages.get(i);
- remainingUsages.put(usage.toPackageAttr(), usage);
+ allUsages.put(usage.getPackageAttrHash(), usage);
if (usage.proxy != null) {
- proxies.put(usage.proxy.toPackageAttr(), usage.toPackageAttr());
+ proxies.put(usage.proxy.getPackageAttrHash(), usage);
}
}
- // find all possible end points for chains
- List<PackageAttribution> keys = new ArrayList<>(remainingUsages.keySet());
- for (int usageNum = 0; usageNum < remainingUsages.size(); usageNum++) {
- OpUsage usage = remainingUsages.get(keys.get(usageNum));
+ // find all possible end points for chains, and find the most recent of the rest of the uses
+ for (int usageNum = 0; usageNum < usages.size(); usageNum++) {
+ OpUsage usage = usages.get(usageNum);
if (usage == null) {
continue;
}
- PackageAttribution usageAttr = usage.toPackageAttr();
+
+ int usageAttr = usage.getPackageAttrHash();
// If this usage has a proxy, but is not a proxy, it is the end of a chain.
- // If it has no proxy, and isn't a proxy, remove it.
if (!proxies.containsKey(usageAttr) && usage.proxy != null) {
- ArrayList<PackageAttribution> proxyList = new ArrayList<>();
- proxyList.add(usageAttr);
- proxyChains.put(usageAttr, proxyList);
- } else if (!proxies.containsKey(usageAttr) && usage.proxy == null) {
- remainingUsages.remove(keys.get(usageNum));
+ proxyLabels.put(usage, new ArrayList<>());
+ proxyUids.add(usage.uid);
+ }
+ if (!mostRecentUsages.containsKey(usage.uid) || usage.lastAccessTime
+ > mostRecentUsages.get(usage.uid).lastAccessTime) {
+ mostRecentUsages.put(usage.uid, usage);
}
}
- // assemble the chains in reverse order, then invert them
- for (int numStart = 0; numStart < proxyChains.size(); numStart++) {
- PackageAttribution currPackageAttr = proxyChains.keyAt(numStart);
- ArrayList<PackageAttribution> proxyChain = proxyChains.get(currPackageAttr);
- OpUsage currentUsage = remainingUsages.get(currPackageAttr);
- if (currentUsage == null || proxyChain == null) {
+ // get all the proxy labels
+ for (int numStart = 0; numStart < proxyLabels.size(); numStart++) {
+ OpUsage start = proxyLabels.keyAt(numStart);
+ // Remove any non-proxy usage for the starting uid
+ mostRecentUsages.remove(start.uid);
+ OpUsage currentUsage = proxyLabels.keyAt(numStart);
+ ArrayList<CharSequence> proxyLabelList = proxyLabels.get(currentUsage);
+ if (currentUsage == null || proxyLabelList == null) {
continue;
}
+ int iterNum = 0;
+ int maxUsages = allUsages.size();
while (currentUsage.proxy != null) {
- currPackageAttr = currentUsage.proxy.toPackageAttr();
- currentUsage = remainingUsages.get(currPackageAttr);
- boolean invalidState = false;
- for (int chainNum = 0; chainNum < proxyChain.size(); chainNum++) {
- if (currentUsage == null || proxyChain.get(chainNum).equals(currPackageAttr)) {
- // either our current value is not in the usage list, or we have a cycle
- invalidState = true;
+ if (allUsages.containsKey(currentUsage.proxy.getPackageAttrHash())) {
+ currentUsage = allUsages.get(currentUsage.proxy.getPackageAttrHash());
+ } else {
+ // We are missing the proxy usage. This may be because it's a one-step trusted
+ // proxy. Check if we should show the proxy label, and show it, if so.
+ OpUsage proxy = currentUsage.proxy;
+ if (PermissionManager.isSpecialCaseShownIndicator(mContext, proxy.packageName)
+ || isUserSensitive(proxy.packageName, proxy.getUser(), proxy.op)) {
+ currentUsage = proxy;
+ // We've effectively added one usage, so increment the max number of usages
+ maxUsages++;
+ } else {
break;
}
}
- if (invalidState) {
+
+ if (currentUsage == null || iterNum == maxUsages
+ || currentUsage.getPackageAttrHash() == start.getPackageAttrHash()) {
+ // We have an invalid state, or a cycle, so break
break;
}
- proxyChain.add(currPackageAttr);
- }
- // invert the lists, so the element without a proxy is first on the list
- Collections.reverse(proxyChain);
- }
-
- return new ArraySet<>(proxyChains.values());
- }
-
- /**
- * Gets all trusted proxied voice IME and voice recognition microphone uses, and get the
- * label needed to display with it, as well as information about the proxy whose label is being
- * shown, if applicable.
- *
- * @param usages The permission usages
- *
- * @return A map of package attribution -> the attribution label for that package attribution,
- * if applicable
- */
- private Map<PackageAttribution, CharSequence> getTrustedAttributionLabels(
- List<OpUsage> usages) {
- List<UserHandle> users = new ArrayList<>();
- for (int i = 0; i < usages.size(); i++) {
- UserHandle user = UserHandle.getUserHandleForUid(usages.get(i).uid);
- if (!users.contains(user)) {
- users.add(user);
- }
- }
-
- Map<PackageAttribution, CharSequence> trustedLabels = new ArrayMap<>();
- for (int userNum = 0; userNum < users.size(); userNum++) {
- UserHandle user = users.get(userNum);
- Context userContext = mContext.createContextAsUser(user, 0);
-
- // Get all voice IME labels
- Map<String, CharSequence> voiceInputs = new ArrayMap<>();
- List<InputMethodInfo> inputs = userContext.getSystemService(InputMethodManager.class)
- .getEnabledInputMethodList();
- for (int inputNum = 0; inputNum < inputs.size(); inputNum++) {
- InputMethodInfo input = inputs.get(inputNum);
- for (int subtypeNum = 0; subtypeNum < input.getSubtypeCount(); subtypeNum++) {
- if (VOICE_IME_SUBTYPE.equals(input.getSubtypeAt(subtypeNum).getMode())) {
- voiceInputs.put(input.getPackageName(), input.getServiceInfo()
- .loadUnsafeLabel(userContext.getPackageManager()));
- break;
+ 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);
}
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
}
+ iterNum++;
}
+ usagesAndLabels.put(start,
+ proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList));
+ }
- // Get the currently selected recognizer from the secure setting
- String recognitionPackageName = Settings.Secure.getString(
- userContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE);
- if (recognitionPackageName == null) {
- continue;
- }
- recognitionPackageName =
- ComponentName.unflattenFromString(recognitionPackageName).getPackageName();
- Map<String, CharSequence> recognizers = new ArrayMap<>();
- List<ResolveInfo> availableRecognizers = mPkgManager.queryIntentServicesAsUser(
- new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
- user.getIdentifier());
- for (int recogNum = 0; recogNum < availableRecognizers.size(); recogNum++) {
- ResolveInfo info = availableRecognizers.get(recogNum);
- if (recognitionPackageName.equals(info.serviceInfo.packageName)) {
- recognizers.put(recognitionPackageName, info.serviceInfo.loadUnsafeLabel(
- userContext.getPackageManager()));
- }
- }
-
- Map<String, CharSequence> recognizerIntents = new ArrayMap<>();
- List<ResolveInfo> availableRecognizerIntents = mPkgManager.queryIntentActivitiesAsUser(
- new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
- PackageManager.GET_META_DATA, user);
- for (int recogNum = 0; recogNum < availableRecognizerIntents.size(); recogNum++) {
- ResolveInfo info = availableRecognizerIntents.get(recogNum);
- if (info.activityInfo == null) {
- continue;
- }
- String pkgName = info.activityInfo.packageName;
- if (recognitionPackageName.equals(pkgName) && recognizers.containsKey(pkgName)) {
- recognizerIntents.put(pkgName, recognizers.get(pkgName));
- }
- }
- for (int usageNum = 0; usageNum < usages.size(); usageNum++) {
- setTrustedAttrsForAccess(usages.get(usageNum), user, false, voiceInputs,
- trustedLabels);
- setTrustedAttrsForAccess(usages.get(usageNum), user, false, recognizerIntents,
- trustedLabels);
- setTrustedAttrsForAccess(usages.get(usageNum), user, true, recognizers,
- trustedLabels);
+ for (int uid : mostRecentUsages.keySet()) {
+ if (!proxyUids.contains(uid)) {
+ usagesAndLabels.put(mostRecentUsages.get(uid), null);
}
}
- return trustedLabels;
- }
-
- private void setTrustedAttrsForAccess(OpUsage opUsage, UserHandle currUser, boolean getProxy,
- Map<String, CharSequence> trustedMap, Map<PackageAttribution, CharSequence> toSetMap) {
- OpUsage usage = opUsage;
- if (getProxy) {
- usage = opUsage.proxy;
- }
-
- if (usage == null || !usage.getUser().equals(currUser)
- || !trustedMap.containsKey(usage.packageName)) {
- return;
- }
-
- CharSequence label = getAttributionLabel(usage);
- if (trustedMap.get(usage.packageName).equals(label)) {
- toSetMap.put(opUsage.toPackageAttr(), label);
- }
- }
-
- private CharSequence getAttributionLabel(OpUsage usage) {
- if (usage.attributionTag == null) {
- return null;
- }
-
- PackageInfo pkgInfo;
- try {
- pkgInfo = mPkgManager.getPackageInfoAsUser(usage.packageName,
- PackageManager.GET_ATTRIBUTIONS, usage.getUser().getIdentifier());
- if (pkgInfo.attributions == null || pkgInfo.attributions.length == 0) {
- return null;
- }
- for (int attrNum = 0; attrNum < pkgInfo.attributions.length; attrNum++) {
- Attribution attr = pkgInfo.attributions[attrNum];
- if (usage.attributionTag.equals(attr.getTag())) {
- return mContext.createPackageContextAsUser(usage.packageName, 0,
- usage.getUser()).getString(attr.getLabel());
- }
- }
- return null;
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- /**
- * If we have multiple usages of a
- * @param rawUsages The list of all usages that we wish to
- * @param specialAttributions A set of all usages that have a special label
- * @param proxies A list of proxy chains- all links but the last on the chain should be removed,
- * if the last link has a special label
- * @return A list of usages without duplicates or proxy usages.
- */
- private List<OpUsage> removeDuplicatesAndProxies(List<OpUsage> rawUsages,
- Set<PackageAttribution> specialAttributions,
- Set<List<PackageAttribution>> proxies) {
- List<OpUsage> deDuped = new ArrayList<>();
- if (rawUsages == null) {
- return deDuped;
- }
-
- List<PackageAttribution> toRemoveProxies = new ArrayList<>();
- for (List<PackageAttribution> proxyList: proxies) {
- PackageAttribution lastLink = proxyList.get(proxyList.size() - 1);
- if (!specialAttributions.contains(lastLink)) {
- continue;
- }
- for (int proxyNum = 0; proxyNum < proxyList.size(); proxyNum++) {
- if (!proxyList.get(proxyNum).equals(lastLink)) {
- toRemoveProxies.add(proxyList.get(proxyNum));
- }
- }
- }
-
- for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) {
- OpUsage usage = rawUsages.get(usageNum);
-
- // If this attribution is a proxy, remove it
- if (toRemoveProxies.contains(usage.toPackageAttr())) {
- continue;
- }
-
- // If this attribution has a special attribution, do not remove it
- if (specialAttributions.contains(usage.toPackageAttr())) {
- deDuped.add(usage);
- continue;
- }
-
-
- // Search the rest of the list for usages with the same UID. If this is the most recent
- // usage for that uid, keep it. Otherwise, remove it
- boolean isMostRecentForUid = true;
- for (int otherUsageNum = 0; otherUsageNum < rawUsages.size(); otherUsageNum++) {
- // Do not compare this usage to itself
- if (otherUsageNum == usageNum) {
- continue;
- }
-
- OpUsage otherUsage = rawUsages.get(otherUsageNum);
- if (otherUsage.uid == usage.uid) {
- if (otherUsage.isRunning && !usage.isRunning) {
- isMostRecentForUid = false;
- } else if (usage.isRunning
- && otherUsage.lastAccessTime >= usage.lastAccessTime) {
- isMostRecentForUid = false;
- } else if (otherUsage.lastAccessTime >= usage.lastAccessTime) {
- isMostRecentForUid = false;
- }
-
- if (!isMostRecentForUid) {
- break;
- }
- }
- }
-
- if (isMostRecentForUid) {
- deDuped.add(usage);
- }
- }
-
- return deDuped;
+ return usagesAndLabels;
}
private boolean isUserSensitive(String packageName, UserHandle user, String op) {
@@ -764,11 +459,6 @@
return (permFlags & FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
}
- private boolean isLocationProvider(String packageName, UserHandle user) {
- return getUserContext(user)
- .getSystemService(LocationManager.class).isProviderPackage(packageName);
- }
-
/**
* Represents the usage of an App op by a particular package and attribution
*/
@@ -776,62 +466,45 @@
public final String packageName;
public final String attributionTag;
+ public final String op;
public final int uid;
public final long lastAccessTime;
public final OpUsage proxy;
public final boolean isRunning;
- OpUsage(String packageName, String attributionTag, int uid, long lastAccessTime,
+ OpUsage(String packageName, String attributionTag, String op, int uid, long lastAccessTime,
boolean isRunning, OpUsage proxy) {
- this.isRunning = isRunning;
this.packageName = packageName;
this.attributionTag = attributionTag;
+ this.op = op;
this.uid = uid;
this.lastAccessTime = lastAccessTime;
+ this.isRunning = isRunning;
this.proxy = proxy;
}
- public PackageAttribution toPackageAttr() {
- return new PackageAttribution(packageName, attributionTag, uid);
- }
-
public UserHandle getUser() {
return UserHandle.getUserHandleForUid(uid);
}
- }
- /**
- * A unique identifier for one package attribution, made up of attribution tag, package name
- * and user
- */
- private static class PackageAttribution {
- public final String packageName;
- public final String attributionTag;
- public final int uid;
-
- PackageAttribution(String packageName, String attributionTag, int uid) {
- this.packageName = packageName;
- this.attributionTag = attributionTag;
- this.uid = uid;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof PackageAttribution)) {
- return false;
- }
- PackageAttribution other = (PackageAttribution) obj;
- return Objects.equals(packageName, other.packageName) && Objects.equals(attributionTag,
- other.attributionTag) && Objects.equals(uid, other.uid);
+ public int getPackageAttrHash() {
+ return Objects.hash(packageName, attributionTag, uid);
}
@Override
public int hashCode() {
- return Objects.hash(packageName, attributionTag, uid);
+ return Objects.hash(packageName, attributionTag, op, uid, lastAccessTime, isRunning);
}
- public UserHandle getUser() {
- return UserHandle.getUserHandleForUid(uid);
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof OpUsage)) {
+ return false;
+ }
+ OpUsage other = (OpUsage) obj;
+ return Objects.equals(packageName, other.packageName) && Objects.equals(attributionTag,
+ other.attributionTag) && Objects.equals(op, other.op) && uid == other.uid
+ && lastAccessTime == other.lastAccessTime && isRunning == other.isRunning;
}
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6865041..f1c80af 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -433,6 +433,20 @@
"android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
/**
+ * Activity Action: Show settings to allow configuration of
+ * {@link Manifest.permission#MANAGE_MEDIA} permission
+ *
+ * Input: Optionally, the Intent's data URI can specify the application package name to
+ * directly invoke the management GUI specific to the package name. For example
+ * "package:com.my.app".
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REQUEST_MANAGE_MEDIA =
+ "android.settings.REQUEST_MANAGE_MEDIA";
+
+ /**
* Activity Action: Show settings to allow configuration of cross-profile access for apps
*
* Input: Optionally, the Intent's data URI can specify the application package name to
@@ -4126,7 +4140,6 @@
* unset or a match is not made, only the standard color modes will be restored.
* @hide
*/
- @Readable
public static final String DISPLAY_COLOR_MODE_VENDOR_HINT =
"display_color_mode_vendor_hint";
@@ -6163,7 +6176,6 @@
*
* @hide
*/
- @Readable
public static final String CAMERA_AUTOROTATE = "camera_autorotate";
/**
@@ -6724,7 +6736,6 @@
* android.app.timezonedetector.TimeZoneDetector#updateConfiguration} to update.
* @hide
*/
- @Readable
public static final String LOCATION_TIME_ZONE_DETECTION_ENABLED =
"location_time_zone_detection_enabled";
@@ -7040,6 +7051,15 @@
"enabled_accessibility_services";
/**
+ * List of the notified non-accessibility category accessibility services.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES =
+ "notified_non_accessibility_category_services";
+
+ /**
* List of the accessibility services to which the user has granted
* permission to put the device into touch exploration mode.
*
@@ -7515,7 +7535,6 @@
*
* @hide
*/
- @Readable
public static final String REDUCE_BRIGHT_COLORS_ACTIVATED =
"reduce_bright_colors_activated";
@@ -7525,7 +7544,6 @@
*
* @hide
*/
- @Readable
public static final String REDUCE_BRIGHT_COLORS_LEVEL =
"reduce_bright_colors_level";
@@ -7534,7 +7552,6 @@
*
* @hide
*/
- @Readable
public static final String REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS =
"reduce_bright_colors_persist_across_reboots";
@@ -8245,7 +8262,6 @@
* @see #MATCH_CONTENT_FRAMERATE_ALWAYS
* @hide
*/
- @Readable
public static final String MATCH_CONTENT_FRAME_RATE =
"match_content_frame_rate";
@@ -8373,7 +8389,6 @@
* {@link Display.STATE_OFF} and {@link Display.STATE_DOZE}.
* @hide
*/
- @Readable
public static final String DOZE_QUICK_PICKUP_GESTURE = "doze_quick_pickup_gesture";
/**
@@ -8479,7 +8494,6 @@
* For user preference if swipe bottom to expand notification gesture enabled.
* @hide
*/
- @Readable
public static final String SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
"swipe_bottom_to_notification_enabled";
@@ -8487,28 +8501,24 @@
* For user preference if One-Handed Mode enabled.
* @hide
*/
- @Readable
public static final String ONE_HANDED_MODE_ENABLED = "one_handed_mode_enabled";
/**
* For user preference if One-Handed Mode timeout.
* @hide
*/
- @Readable
public static final String ONE_HANDED_MODE_TIMEOUT = "one_handed_mode_timeout";
/**
* For user taps app to exit One-Handed Mode.
* @hide
*/
- @Readable
public static final String TAPS_APP_TO_EXIT = "taps_app_to_exit";
/**
* Internal use, one handed mode tutorial showed times.
* @hide
*/
- @Readable
public static final String ONE_HANDED_TUTORIAL_SHOW_COUNT =
"one_handed_tutorial_show_count";
@@ -8534,7 +8544,6 @@
* The last computed night mode bool the last time the phone was on
* @hide
*/
- @Readable
public static final String UI_NIGHT_MODE_LAST_COMPUTED = "ui_night_mode_last_computed";
/**
@@ -8953,7 +8962,6 @@
*
* @hide
*/
- @Readable
public static final String EMERGENCY_GESTURE_ENABLED = "emergency_gesture_enabled";
/**
@@ -8961,7 +8969,6 @@
*
* @hide
*/
- @Readable
public static final String EMERGENCY_GESTURE_SOUND_ENABLED =
"emergency_gesture_sound_enabled";
@@ -9694,7 +9701,6 @@
* @see Settings.Secure#MEDIA_CONTROLS_RESUME
* @hide
*/
- @Readable
public static final String MEDIA_CONTROLS_RESUME_BLOCKED = "qs_media_resumption_blocked";
/**
@@ -9742,7 +9748,6 @@
* @hide
*/
@TestApi
- @Readable
public static final String ACCESSIBILITY_MAGNIFICATION_CAPABILITY =
"accessibility_magnification_capability";
@@ -9752,7 +9757,6 @@
*
* @hide
*/
- @Readable
public static final String ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT =
"accessibility_show_window_magnification_prompt";
@@ -9769,7 +9773,6 @@
* @see #ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU
* @hide
*/
- @Readable
public static final String ACCESSIBILITY_BUTTON_MODE =
"accessibility_button_mode";
@@ -9798,7 +9801,6 @@
*
* @hide
*/
- @Readable
public static final String ACCESSIBILITY_FLOATING_MENU_SIZE =
"accessibility_floating_menu_size";
@@ -9811,7 +9813,6 @@
*
* @hide
*/
- @Readable
public static final String ACCESSIBILITY_FLOATING_MENU_ICON_TYPE =
"accessibility_floating_menu_icon_type";
@@ -9820,7 +9821,6 @@
*
* @hide
*/
- @Readable
public static final String ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED =
"accessibility_floating_menu_fade_enabled";
@@ -9830,7 +9830,6 @@
*
* @hide
*/
- @Readable
public static final String ACCESSIBILITY_FLOATING_MENU_OPACITY =
"accessibility_floating_menu_opacity";
@@ -9839,7 +9838,6 @@
*
* @hide
*/
- @Readable
public static final String ADAPTIVE_CONNECTIVITY_ENABLED = "adaptive_connectivity_enabled";
/**
@@ -9863,7 +9861,6 @@
*
* @hide
*/
- @Readable
public static final String ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS =
"reminder_exp_learning_time_elapsed";
@@ -9872,11 +9869,18 @@
*
* @hide
*/
- @Readable
public static final String ASSIST_HANDLES_LEARNING_EVENT_COUNT =
"reminder_exp_learning_event_count";
/**
+ * Whether to show clipboard access notifications.
+ *
+ * @hide
+ */
+ public static final String CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS =
+ "clipboard_show_access_notifications";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
@@ -10573,7 +10577,6 @@
*
* @hide
*/
- @Readable
public static final String DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH =
"wm_display_settings_path";
@@ -10753,7 +10756,6 @@
*
* @hide
*/
- @Readable
public static final String HDMI_CONTROL_SEND_STANDBY_ON_SLEEP =
"hdmi_control_send_standby_on_sleep";
@@ -14431,7 +14433,6 @@
*
* @hide
*/
- @Readable
public static final String EUICC_SWITCH_SLOT_TIMEOUT_MILLIS =
"euicc_switch_slot_timeout_millis";
@@ -14441,7 +14442,6 @@
*
* @hide
*/
- @Readable
public static final String ENABLE_MULTI_SLOT_TIMEOUT_MILLIS =
"enable_multi_slot_timeout_millis";
@@ -14565,7 +14565,6 @@
*
* @hide
*/
- @Readable
public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT =
"force_non_debuggable_final_build_for_compat";
@@ -14645,7 +14644,6 @@
*
* @hide
*/
- @Readable
public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side";
/**
@@ -15328,7 +15326,6 @@
* The value 1 - enable, 0 - disable
* @hide
*/
- @Readable
public static final String NOTIFICATION_FEEDBACK_ENABLED = "notification_feedback_enabled";
/**
@@ -15571,7 +15568,6 @@
*
* @hide
*/
- @Readable
public static final String GNSS_SATELLITE_BLOCKLIST = "gnss_satellite_blocklist";
/**
@@ -15776,7 +15772,6 @@
* 1: Enabled
* @hide
*/
- @Readable
public static final String SHOW_PEOPLE_SPACE = "show_people_space";
/**
@@ -15787,7 +15782,6 @@
* 2: All conversations
* @hide
*/
- @Readable
public static final String PEOPLE_SPACE_CONVERSATION_TYPE =
"people_space_conversation_type";
@@ -15798,7 +15792,6 @@
* 1: Enabled
* @hide
*/
- @Readable
public static final String SHOW_NEW_NOTIF_DISMISS = "show_new_notif_dismiss";
/**
@@ -15813,7 +15806,6 @@
* 1: Enabled (All apps will receive the new rules)
* @hide
*/
- @Readable
public static final String BACKPORT_S_NOTIF_RULES = "backport_s_notif_rules";
/**
@@ -15828,7 +15820,6 @@
* <p>See {@link android.app.Notification.DevFlags} for more details.
* @hide
*/
- @Readable
public static final String FULLY_CUSTOM_VIEW_NOTIF_DECORATION =
"fully_custom_view_notif_decoration";
@@ -15842,7 +15833,6 @@
* <p>See {@link android.app.Notification.DevFlags} for more details.
* @hide
*/
- @Readable
public static final String DECORATED_CUSTOM_VIEW_NOTIF_DECORATION =
"decorated_custom_view_notif_decoration";
@@ -15898,7 +15888,6 @@
* 1: enabled
* @hide
*/
- @Readable
public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode";
}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 8a4812a..374de9c 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5310,5 +5310,12 @@
* @hide
*/
public static final String COLUMN_VOIMS_OPT_IN_STATUS = "voims_opt_in_status";
+
+ /**
+ * TelephonyProvider column name for device to device sharing status.
+ *
+ * @hide
+ */
+ public static final String COLUMN_D2D_STATUS_SHARING = "d2d_sharing_status";
}
}
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index e3d0741..6147c58 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -107,10 +107,12 @@
private final ArrayList<AutofillValue> mFieldValues;
private final ArrayList<RemoteViews> mFieldPresentations;
private final ArrayList<InlinePresentation> mFieldInlinePresentations;
+ private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
private final ArrayList<DatasetFieldFilter> mFieldFilters;
@Nullable private final ClipData mFieldContent;
private final RemoteViews mPresentation;
@Nullable private final InlinePresentation mInlinePresentation;
+ @Nullable private final InlinePresentation mInlineTooltipPresentation;
private final IntentSender mAuthentication;
@Nullable String mId;
@@ -119,10 +121,12 @@
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
mFieldInlinePresentations = builder.mFieldInlinePresentations;
+ mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
mFieldFilters = builder.mFieldFilters;
mFieldContent = builder.mFieldContent;
mPresentation = builder.mPresentation;
mInlinePresentation = builder.mInlinePresentation;
+ mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
mAuthentication = builder.mAuthentication;
mId = builder.mId;
}
@@ -154,6 +158,14 @@
}
/** @hide */
+ public @Nullable InlinePresentation getFieldInlineTooltipPresentation(int index) {
+ final InlinePresentation inlineTooltipPresentation =
+ mFieldInlineTooltipPresentations.get(index);
+ return inlineTooltipPresentation != null
+ ? inlineTooltipPresentation : mInlineTooltipPresentation;
+ }
+
+ /** @hide */
public @Nullable DatasetFieldFilter getFilter(int index) {
return mFieldFilters.get(index);
}
@@ -209,6 +221,10 @@
if (mFieldInlinePresentations != null) {
builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size());
}
+ if (mFieldInlineTooltipPresentations != null) {
+ builder.append(", fieldInlineTooltipInlinePresentations=").append(
+ mFieldInlineTooltipPresentations.size());
+ }
if (mFieldFilters != null) {
builder.append(", fieldFilters=").append(mFieldFilters.size());
}
@@ -218,6 +234,9 @@
if (mInlinePresentation != null) {
builder.append(", hasInlinePresentation");
}
+ if (mInlineTooltipPresentation != null) {
+ builder.append(", hasInlineTooltipPresentation");
+ }
if (mAuthentication != null) {
builder.append(", hasAuthentication");
}
@@ -245,10 +264,12 @@
private ArrayList<AutofillValue> mFieldValues;
private ArrayList<RemoteViews> mFieldPresentations;
private ArrayList<InlinePresentation> mFieldInlinePresentations;
+ private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations;
private ArrayList<DatasetFieldFilter> mFieldFilters;
@Nullable private ClipData mFieldContent;
private RemoteViews mPresentation;
@Nullable private InlinePresentation mInlinePresentation;
+ @Nullable private InlinePresentation mInlineTooltipPresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
@Nullable private String mId;
@@ -306,6 +327,31 @@
}
/**
+ * Visualizes this dataset as inline suggestions.
+ *
+ * @param inlinePresentation the {@link InlinePresentation} used to visualize this
+ * dataset as inline suggestions. If the dataset supports inline suggestions this
+ * should not be null.
+ * @param inlineTooltipPresentation the {@link InlinePresentation} used to show
+ * the tooltip for the {@code inlinePresentation}.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setInlinePresentation(
+ @NonNull InlinePresentation inlinePresentation,
+ @NonNull InlinePresentation inlineTooltipPresentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
+ Preconditions.checkNotNull(inlineTooltipPresentation,
+ "inlineTooltipPresentation must be non-null");
+ mInlinePresentation = inlinePresentation;
+ mInlineTooltipPresentation = inlineTooltipPresentation;
+ return this;
+ }
+
+ /**
* Triggers a custom UI before before autofilling the screen with the contents of this
* dataset.
*
@@ -598,6 +644,41 @@
/**
* Sets the value of a field, using a custom {@link RemoteViews presentation} to
+ * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion.
+ *
+ * @see #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if
+ * the dataset needs authentication and you have no access to the value.
+ * @param presentation the presentation used to visualize this field.
+ * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+ * as inline suggestions. If the dataset supports inline suggestions,
+ * this should not be null.
+ * @param inlineTooltipPresentation The {@link InlinePresentation} used to show
+ * the tooltip for the {@code inlinePresentation}.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+ @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation,
+ @NonNull InlinePresentation inlineTooltipPresentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(presentation, "presentation cannot be null");
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+ Preconditions.checkNotNull(inlineTooltipPresentation,
+ "inlineTooltipPresentation cannot be null");
+ setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
+ inlineTooltipPresentation, null);
+ return this;
+ }
+
+ /**
+ * Sets the value of a field, using a custom {@link RemoteViews presentation} to
* visualize it and a <a href="#Filtering">explicit filter</a>, and an
* {@link InlinePresentation} to visualize it as an inline suggestion.
*
@@ -641,6 +722,47 @@
}
/**
+ * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+ * visualize it and a <a href="#Filtering">explicit filter</a>, and an
+ * {@link InlinePresentation} to visualize it as an inline suggestion.
+ *
+ * @see #setValue(AutofillId, AutofillValue, Pattern, RemoteViews, InlinePresentation)
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if
+ * the dataset needs authentication and you have no access to the value.
+ * @param filter regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ * @param presentation the presentation used to visualize this field.
+ * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+ * as inline suggestions. If the dataset supports inline suggestions, this
+ * should not be null.
+ * @param inlineTooltipPresentation The {@link InlinePresentation} used to show
+ * the tooltip for the {@code inlinePresentation}.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+ @Nullable Pattern filter, @NonNull RemoteViews presentation,
+ @NonNull InlinePresentation inlinePresentation,
+ @NonNull InlinePresentation inlineTooltipPresentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(presentation, "presentation cannot be null");
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+ Preconditions.checkNotNull(inlineTooltipPresentation,
+ "inlineTooltipPresentation cannot be null");
+ setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
+ inlineTooltipPresentation, new DatasetFieldFilter(filter));
+ return this;
+ }
+
+ /**
* Sets the value of a field with an <a href="#Filtering">explicit filter</a>, and using an
* {@link InlinePresentation} to visualize it as an inline suggestion.
*
@@ -680,6 +802,15 @@
@Nullable AutofillValue value, @Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation,
@Nullable DatasetFieldFilter filter) {
+ setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null,
+ filter);
+ }
+
+ private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
+ @Nullable AutofillValue value, @Nullable RemoteViews presentation,
+ @Nullable InlinePresentation inlinePresentation,
+ @Nullable InlinePresentation tooltip,
+ @Nullable DatasetFieldFilter filter) {
Preconditions.checkNotNull(id, "id cannot be null");
if (mFieldIds != null) {
final int existingIdx = mFieldIds.indexOf(id);
@@ -687,6 +818,7 @@
mFieldValues.set(existingIdx, value);
mFieldPresentations.set(existingIdx, presentation);
mFieldInlinePresentations.set(existingIdx, inlinePresentation);
+ mFieldInlineTooltipPresentations.set(existingIdx, tooltip);
mFieldFilters.set(existingIdx, filter);
return;
}
@@ -695,12 +827,14 @@
mFieldValues = new ArrayList<>();
mFieldPresentations = new ArrayList<>();
mFieldInlinePresentations = new ArrayList<>();
+ mFieldInlineTooltipPresentations = new ArrayList<>();
mFieldFilters = new ArrayList<>();
}
mFieldIds.add(id);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
mFieldInlinePresentations.add(inlinePresentation);
+ mFieldInlineTooltipPresentations.add(tooltip);
mFieldFilters.add(filter);
}
@@ -755,10 +889,12 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelable(mInlinePresentation, flags);
+ parcel.writeParcelable(mInlineTooltipPresentation, flags);
parcel.writeTypedList(mFieldIds, flags);
parcel.writeTypedList(mFieldValues, flags);
parcel.writeTypedList(mFieldPresentations, flags);
parcel.writeTypedList(mFieldInlinePresentations, flags);
+ parcel.writeTypedList(mFieldInlineTooltipPresentations, flags);
parcel.writeTypedList(mFieldFilters, flags);
parcel.writeParcelable(mFieldContent, flags);
parcel.writeParcelable(mAuthentication, flags);
@@ -770,6 +906,8 @@
public Dataset createFromParcel(Parcel parcel) {
final RemoteViews presentation = parcel.readParcelable(null);
final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+ final InlinePresentation inlineTooltipPresentation =
+ parcel.readParcelable(null);
final ArrayList<AutofillId> ids =
parcel.createTypedArrayList(AutofillId.CREATOR);
final ArrayList<AutofillValue> values =
@@ -778,6 +916,8 @@
parcel.createTypedArrayList(RemoteViews.CREATOR);
final ArrayList<InlinePresentation> inlinePresentations =
parcel.createTypedArrayList(InlinePresentation.CREATOR);
+ final ArrayList<InlinePresentation> inlineTooltipPresentations =
+ parcel.createTypedArrayList(InlinePresentation.CREATOR);
final ArrayList<DatasetFieldFilter> filters =
parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
final ClipData fieldContent = parcel.readParcelable(null);
@@ -790,8 +930,13 @@
final Builder builder = (presentation != null) ? new Builder(presentation)
: new Builder();
if (inlinePresentation != null) {
- builder.setInlinePresentation(inlinePresentation);
+ if (inlineTooltipPresentation != null) {
+ builder.setInlinePresentation(inlinePresentation, inlineTooltipPresentation);
+ } else {
+ builder.setInlinePresentation(inlinePresentation);
+ }
}
+
if (fieldContent != null) {
builder.setContent(ids.get(0), fieldContent);
}
@@ -802,9 +947,11 @@
final RemoteViews fieldPresentation = presentations.get(i);
final InlinePresentation fieldInlinePresentation =
i < inlinePresentationsSize ? inlinePresentations.get(i) : null;
+ final InlinePresentation fieldInlineTooltipPresentation =
+ i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null;
final DatasetFieldFilter filter = filters.get(i);
builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation,
- fieldInlinePresentation, filter);
+ fieldInlinePresentation, fieldInlineTooltipPresentation, filter);
}
builder.setAuthentication(authentication);
builder.setId(datasetId);
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index b1107a8..740ae26 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -23,6 +23,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.Activity;
import android.content.IntentSender;
@@ -76,6 +77,7 @@
private final @Nullable Bundle mClientState;
private final @Nullable RemoteViews mPresentation;
private final @Nullable InlinePresentation mInlinePresentation;
+ private final @Nullable InlinePresentation mInlineTooltipPresentation;
private final @Nullable RemoteViews mHeader;
private final @Nullable RemoteViews mFooter;
private final @Nullable IntentSender mAuthentication;
@@ -95,6 +97,7 @@
mClientState = builder.mClientState;
mPresentation = builder.mPresentation;
mInlinePresentation = builder.mInlinePresentation;
+ mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
mHeader = builder.mHeader;
mFooter = builder.mFooter;
mAuthentication = builder.mAuthentication;
@@ -135,6 +138,11 @@
}
/** @hide */
+ public @Nullable InlinePresentation getInlineTooltipPresentation() {
+ return mInlineTooltipPresentation;
+ }
+
+ /** @hide */
public @Nullable RemoteViews getHeader() {
return mHeader;
}
@@ -219,6 +227,7 @@
private Bundle mClientState;
private RemoteViews mPresentation;
private InlinePresentation mInlinePresentation;
+ private InlinePresentation mInlineTooltipPresentation;
private RemoteViews mHeader;
private RemoteViews mFooter;
private IntentSender mAuthentication;
@@ -360,6 +369,22 @@
public Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation,
@Nullable InlinePresentation inlinePresentation) {
+ return setAuthentication(ids, authentication, presentation, inlinePresentation, null);
+ }
+
+ /**
+ * Triggers a custom UI before before autofilling the screen with any data set in this
+ * response.
+ *
+ * <p>This method like
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)}
+ * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip.
+ */
+ @NonNull
+ public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids,
+ @Nullable IntentSender authentication, @Nullable RemoteViews presentation,
+ @Nullable InlinePresentation inlinePresentation,
+ @Nullable InlinePresentation inlineTooltipPresentation) {
throwIfDestroyed();
throwIfDisableAutofillCalled();
if (mHeader != null || mFooter != null) {
@@ -373,6 +398,7 @@
mAuthentication = authentication;
mPresentation = presentation;
mInlinePresentation = inlinePresentation;
+ mInlineTooltipPresentation = inlineTooltipPresentation;
mAuthenticationIds = assertValid(ids);
return this;
}
@@ -737,6 +763,9 @@
if (mInlinePresentation != null) {
builder.append(", hasInlinePresentation");
}
+ if (mInlineTooltipPresentation != null) {
+ builder.append(", hasInlineTooltipPresentation");
+ }
if (mHeader != null) {
builder.append(", hasHeader");
}
@@ -784,6 +813,7 @@
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelable(mInlinePresentation, flags);
+ parcel.writeParcelable(mInlineTooltipPresentation, flags);
parcel.writeParcelable(mHeader, flags);
parcel.writeParcelable(mFooter, flags);
parcel.writeParcelable(mUserData, flags);
@@ -818,9 +848,10 @@
final IntentSender authentication = parcel.readParcelable(null);
final RemoteViews presentation = parcel.readParcelable(null);
final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+ final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null);
if (authenticationIds != null) {
builder.setAuthentication(authenticationIds, authentication, presentation,
- inlinePresentation);
+ inlinePresentation, inlineTooltipPresentation);
}
final RemoteViews header = parcel.readParcelable(null);
if (header != null) {
diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java
index fbf23b6..4034957 100644
--- a/core/java/android/service/autofill/InlinePresentation.java
+++ b/core/java/android/service/autofill/InlinePresentation.java
@@ -75,9 +75,23 @@
return hints.toArray(new String[hints.size()]);
}
+ /**
+ * Creates a presentation for the inline suggestion tooltip
+ *
+ * @param slice Represents the UI content and the action for the inline suggestion tooltip.
+ * @param spec Specifies the UI specification for the inline suggestion tooltip.
+ * @return An {@link InlinePresentation} for the inline suggestion tooltip
+ */
+ @NonNull
+ public static InlinePresentation createTooltipPresentation(@NonNull Slice slice,
+ @NonNull InlinePresentationSpec spec) {
+ return new InlinePresentation(slice, spec, /* pinned */ false);
+
+ }
- // Code below generated by codegen v1.0.20.
+
+ // Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -259,10 +273,10 @@
};
@DataClass.Generated(
- time = 1604456277638L,
- codegenVersion = "1.0.20",
+ time = 1615968415006L,
+ codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java",
- inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
+ inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size java.lang.String[] getAutofillHints()\npublic static @android.annotation.NonNull android.service.autofill.InlinePresentation createTooltipPresentation(android.app.slice.Slice,android.widget.inline.InlinePresentationSpec)\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/core/java/android/service/displayhash/DisplayHashParams.aidl
similarity index 81%
copy from telephony/java/android/telephony/data/SliceInfo.aidl
copy to core/java/android/service/displayhash/DisplayHashParams.aidl
index 286ea5e..90f9bf1 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/core/java/android/service/displayhash/DisplayHashParams.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package android.service.displayhash;
-parcelable SliceInfo;
+parcelable DisplayHashParams;
diff --git a/core/java/android/service/displayhash/DisplayHashParams.java b/core/java/android/service/displayhash/DisplayHashParams.java
new file mode 100644
index 0000000..6a176a33
--- /dev/null
+++ b/core/java/android/service/displayhash/DisplayHashParams.java
@@ -0,0 +1,251 @@
+/*
+ * 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.displayhash;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcelable;
+import android.util.Size;
+import android.view.displayhash.DisplayHashResultCallback;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information passed from the {@link DisplayHasherService} to system server about how to get the
+ * display data that will be used to generate the {@link android.view.displayhash.DisplayHash}
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(genAidl = true, genToString = true, genParcelable = true, genHiddenConstructor = true)
+public final class DisplayHashParams implements Parcelable {
+ /**
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+ * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+ * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+ * If {@code null}, the buffer size will not be changed.
+ */
+ @Nullable
+ private final Size mBufferSize;
+
+ /**
+ * Whether the content captured will use filtering when scaling.
+ */
+ private final boolean mBufferScaleWithFiltering;
+
+ /**
+ * Whether the content will be captured in grayscale or color.
+ */
+ private final boolean mGrayscaleBuffer;
+
+ /**
+ * A builder for {@link DisplayHashParams}
+ */
+ public static final class Builder {
+ @Nullable
+ private Size mBufferSize;
+ private boolean mBufferScaleWithFiltering;
+ private boolean mGrayscaleBuffer;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash.
+ */
+ @NonNull
+ public Builder setBufferSize(int w, int h) {
+ mBufferSize = new Size(w, h);
+ return this;
+ }
+
+ /**
+ * Whether the content captured will use filtering when scaling.
+ */
+ @NonNull
+ public Builder setBufferScaleWithFiltering(boolean value) {
+ mBufferScaleWithFiltering = value;
+ return this;
+ }
+
+ /**
+ * Whether the content will be captured in grayscale or color.
+ */
+ @NonNull
+ public Builder setGrayscaleBuffer(boolean value) {
+ mGrayscaleBuffer = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ @NonNull
+ public DisplayHashParams build() {
+ return new DisplayHashParams(mBufferSize, mBufferScaleWithFiltering, mGrayscaleBuffer);
+ }
+ }
+
+
+
+ // 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/displayhash/DisplayHashParams.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DisplayHashParams.
+ *
+ * @param bufferSize
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+ * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+ * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+ * If {@code null}, the buffer size will not be changed.
+ * @param bufferScaleWithFiltering
+ * Whether the content captured will use filtering when scaling.
+ * @param grayscaleBuffer
+ * Whether the content will be captured in grayscale or color.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DisplayHashParams(
+ @Nullable Size bufferSize,
+ boolean bufferScaleWithFiltering,
+ boolean grayscaleBuffer) {
+ this.mBufferSize = bufferSize;
+ this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+ this.mGrayscaleBuffer = grayscaleBuffer;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+ * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+ * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+ * If {@code null}, the buffer size will not be changed.
+ */
+ @DataClass.Generated.Member
+ public @Nullable Size getBufferSize() {
+ return mBufferSize;
+ }
+
+ /**
+ * Whether the content captured will use filtering when scaling.
+ */
+ @DataClass.Generated.Member
+ public boolean isBufferScaleWithFiltering() {
+ return mBufferScaleWithFiltering;
+ }
+
+ /**
+ * Whether the content will be captured in grayscale or color.
+ */
+ @DataClass.Generated.Member
+ public boolean isGrayscaleBuffer() {
+ return mGrayscaleBuffer;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DisplayHashParams { " +
+ "bufferSize = " + mBufferSize + ", " +
+ "bufferScaleWithFiltering = " + mBufferScaleWithFiltering + ", " +
+ "grayscaleBuffer = " + mGrayscaleBuffer +
+ " }";
+ }
+
+ @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 (mBufferScaleWithFiltering) flg |= 0x2;
+ if (mGrayscaleBuffer) flg |= 0x4;
+ if (mBufferSize != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mBufferSize != null) dest.writeSize(mBufferSize);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DisplayHashParams(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean bufferScaleWithFiltering = (flg & 0x2) != 0;
+ boolean grayscaleBuffer = (flg & 0x4) != 0;
+ Size bufferSize = (flg & 0x1) == 0 ? null : (Size) in.readSize();
+
+ this.mBufferSize = bufferSize;
+ this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+ this.mGrayscaleBuffer = grayscaleBuffer;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DisplayHashParams> CREATOR
+ = new Parcelable.Creator<DisplayHashParams>() {
+ @Override
+ public DisplayHashParams[] newArray(int size) {
+ return new DisplayHashParams[size];
+ }
+
+ @Override
+ public DisplayHashParams createFromParcel(@NonNull android.os.Parcel in) {
+ return new DisplayHashParams(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1615565493989L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/service/displayhash/DisplayHashParams.java",
+ inputSignatures = "private final @android.annotation.Nullable android.util.Size mBufferSize\nprivate final boolean mBufferScaleWithFiltering\nprivate final boolean mGrayscaleBuffer\nclass DisplayHashParams extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.util.Size mBufferSize\nprivate boolean mBufferScaleWithFiltering\nprivate boolean mGrayscaleBuffer\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferSize(int,int)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genAidl=true, genToString=true, genParcelable=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/displayhash/DisplayHasherService.java b/core/java/android/service/displayhash/DisplayHasherService.java
index 331dbe9..2105d84 100644
--- a/core/java/android/service/displayhash/DisplayHasherService.java
+++ b/core/java/android/service/displayhash/DisplayHasherService.java
@@ -34,6 +34,8 @@
import android.view.displayhash.DisplayHashResultCallback;
import android.view.displayhash.VerifiedDisplayHash;
+import java.util.Map;
+
/**
* A service that handles generating and verify {@link DisplayHash}.
*
@@ -50,15 +52,6 @@
"android.service.displayhash.extra.VERIFIED_DISPLAY_HASH";
/**
- * Manifest metadata key for the resource string array containing the names of all hashing
- * algorithms provided by the service.
- *
- * @hide
- */
- public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
- "android.displayhash.available_algorithms";
-
- /**
* The {@link Intent} action that must be declared as handled by a service in its manifest
* for the system to recognize it as a DisplayHash providing service.
*
@@ -96,7 +89,7 @@
* @param buffer The buffer for the content to generate the hash for.
* @param bounds The size and position of the content in window space.
* @param hashAlgorithm The String for the hashing algorithm to use based values in
- * {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
+ * {@link #getDisplayHashAlgorithms(RemoteCallback)}.
* @param callback The callback to invoke
* {@link DisplayHashResultCallback#onDisplayHashResult(DisplayHash)}
* if successfully generated a DisplayHash or {@link
@@ -108,6 +101,12 @@
@NonNull String hashAlgorithm, @NonNull DisplayHashResultCallback callback);
/**
+ * Returns a map of supported algorithms and their {@link DisplayHashParams}
+ */
+ @NonNull
+ public abstract Map<String, DisplayHashParams> onGetDisplayHashAlgorithms();
+
+ /**
* Call to verify that the DisplayHash passed in was generated by the system.
*
* @param salt The salt value to use when verifying the hmac. This should be the
@@ -132,6 +131,15 @@
callback.sendResult(data);
}
+ private void getDisplayHashAlgorithms(RemoteCallback callback) {
+ Map<String, DisplayHashParams> displayHashParams = onGetDisplayHashAlgorithms();
+ final Bundle data = new Bundle();
+ for (Map.Entry<String, DisplayHashParams> entry : displayHashParams.entrySet()) {
+ data.putParcelable(entry.getKey(), entry.getValue());
+ }
+ callback.sendResult(data);
+ }
+
private final class DisplayHasherServiceWrapper extends IDisplayHasherService.Stub {
@Override
public void generateDisplayHash(byte[] salt, HardwareBuffer buffer, Rect bounds,
@@ -164,5 +172,11 @@
obtainMessage(DisplayHasherService::verifyDisplayHash,
DisplayHasherService.this, salt, displayHash, callback));
}
+
+ @Override
+ public void getDisplayHashAlgorithms(RemoteCallback callback) {
+ mHandler.sendMessage(obtainMessage(DisplayHasherService::getDisplayHashAlgorithms,
+ DisplayHasherService.this, callback));
+ }
}
}
diff --git a/core/java/android/service/displayhash/IDisplayHasherService.aidl b/core/java/android/service/displayhash/IDisplayHasherService.aidl
index 236bc28..d9dcdca 100644
--- a/core/java/android/service/displayhash/IDisplayHasherService.aidl
+++ b/core/java/android/service/displayhash/IDisplayHasherService.aidl
@@ -51,4 +51,11 @@
* @param callback The callback invoked to send back the VerifiedDisplayHash.
*/
void verifyDisplayHash(in byte[] salt, in DisplayHash displayHash, in RemoteCallback callback);
+
+ /**
+ * Call to get a map of supported algorithms and their {@link DisplayHashParams}
+ *
+ * @param callback The callback invoked to send back the map of algorithms to DisplayHashParams.
+ */
+ void getDisplayHashAlgorithms(in RemoteCallback callback);
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7aa5bbc9..bf5f24d 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1786,7 +1786,8 @@
* {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
* no such preference has been expressed.
*/
- public int getLockscreenVisibilityOverride() {
+ public @Notification.NotificationVisibilityOverride
+ int getLockscreenVisibilityOverride() {
return mVisibilityOverride;
}
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index d71a830..a9348c6 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -218,7 +218,30 @@
}
/**
- * Starts the provider sending updates.
+ * Informs the provider that it should start detecting and reporting the detected time zone
+ * state via the various {@code report} methods. Implementations of {@link
+ * #onStartUpdates(long)} should return immediately, and will typically be used to start
+ * worker threads or begin asynchronous location listening.
+ *
+ * <p>Between {@link #onStartUpdates(long)} and {@link #onStopUpdates()} calls, the Android
+ * system server holds the latest report from the provider in memory. After an initial report,
+ * provider implementations are only required to send a report via {@link
+ * #reportSuggestion(TimeZoneProviderSuggestion)} or via {@link #reportUncertain()} when it
+ * differs from the previous report.
+ *
+ * <p>{@link #reportPermanentFailure(Throwable)} can also be called by provider implementations
+ * in rare cases, after which the provider should consider itself stopped and not make any
+ * further reports. {@link #onStopUpdates()} will not be called in this case.
+ *
+ * <p>The {@code initializationTimeoutMillis} parameter indicates how long the provider has been
+ * granted to call one of the {@code report} methods for the first time. If the provider does
+ * not call one of the {@code report} methods in this time, it may be judged uncertain and the
+ * Android system server may move on to use other providers or detection methods. Providers
+ * should therefore make best efforts during this time to generate a report, which could involve
+ * increased power usage. Providers should preferably report an explicit {@link
+ * #reportUncertain()} if the time zone(s) cannot be detected within the initialization timeout.
+ *
+ * @see #onStopUpdates() for the signal from the system server to stop sending reports
*/
public abstract void onStartUpdates(@DurationMillisLong long initializationTimeoutMillis);
@@ -228,7 +251,8 @@
}
/**
- * Stops the provider sending updates.
+ * Stops the provider sending further updates. This will be called after {@link
+ * #onStartUpdates(long)}.
*/
public abstract void onStopUpdates();
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 048d9f5..9ba39a1 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -353,6 +353,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
@NonNull
public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
@SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index bbe887f..d47ae27 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
@@ -1262,6 +1261,8 @@
// default implementation empty
}
+
+
/**
* The callback methods need to be called on the handler thread where
* this object was created. If the binder did that for us it'd be nice.
@@ -1577,7 +1578,12 @@
// default implementation empty
}
- public void onAllowedNetworkTypesChanged(Map allowedNetworkTypesList) {
+ public void onAllowedNetworkTypesChanged(int reason, long allowedNetworkType) {
+ // default implementation empty
+ }
+
+ public void onLinkCapacityEstimateChanged(
+ List<LinkCapacityEstimate> linkCapacityEstimateList) {
// default implementation empty
}
}
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 2cadda2..d000000 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -546,9 +546,6 @@
/**
* Event for changes to allowed network list based on all active subscriptions.
*
- * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
- * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
* @hide
* @see AllowedNetworkTypesListener#onAllowedNetworkTypesChanged
*/
@@ -568,6 +565,21 @@
@RequiresPermission(android.Manifest.permission.READ_CALL_LOG)
public static final int EVENT_LEGACY_CALL_STATE_CHANGED = 36;
+
+ /**
+ * Event for changes to the link capacity estimate (LCE)
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
+ *
+ * @see LinkCapacityEstimateChangedListener#onLinkCapacityEstimateChanged
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+ public static final int EVENT_LINK_CAPACITY_ESTIMATE_CHANGED = 37;
+
+
/**
* @hide
*/
@@ -607,7 +619,8 @@
EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED,
EVENT_DATA_ENABLED_CHANGED,
EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED,
- EVENT_LEGACY_CALL_STATE_CHANGED
+ EVENT_LEGACY_CALL_STATE_CHANGED,
+ EVENT_LINK_CAPACITY_ESTIMATE_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1265,30 +1278,34 @@
public interface AllowedNetworkTypesListener {
/**
* Callback invoked when the current allowed network type list has changed on the
- * registered subscription.
+ * registered subscription for a specified reason.
* Note, the registered subscription is associated with {@link TelephonyManager} object
- * on which
- * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}
+ * on which {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}
* was called.
* If this TelephonyManager object was created with
* {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
* given subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @param allowedNetworkTypesList Map associating all allowed network type reasons
- * ({@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER},
- * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER},
- * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER}, and
- * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}) with reason's allowed
- * network type values.
+ * @param reason an allowed network type reasons.
+ * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER
+ * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER
+ * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER
+ * @see TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G
+ *
+ * @param allowedNetworkType an allowed network type bitmask value. (for example,
+ * the long bitmask value is {{@link TelephonyManager#NETWORK_TYPE_BITMASK_NR}|
+ * {@link TelephonyManager#NETWORK_TYPE_BITMASK_LTE}})
+ *
* For example:
- * map{{TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER, long type value},
- * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_POWER, long type value},
- * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_CARRIER, long type value},
- * {TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, long type value}}
+ * If the latest allowed network type is changed by user, then the system
+ * notifies the {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER} and
+ * long type value}.
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- void onAllowedNetworkTypesChanged(@NonNull Map<Integer, Long> allowedNetworkTypesList);
+ void onAllowedNetworkTypesChanged(
+ @TelephonyManager.AllowedNetworkTypesReason int reason,
+ @TelephonyManager.NetworkTypeBitMask long allowedNetworkType);
}
/**
@@ -1336,10 +1353,7 @@
/**
* Interface for current physical channel configuration listener.
- *
- * @hide
*/
- @SystemApi
public interface PhysicalChannelConfigListener {
/**
* Callback invoked when the current physical channel configuration has changed
@@ -1369,6 +1383,25 @@
@TelephonyManager.DataEnabledReason int reason);
}
+ /**
+ * Interface for link capacity estimate changed listener.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface LinkCapacityEstimateChangedListener {
+ /**
+ * Callback invoked when the link capacity estimate (LCE) changes
+ *
+ * @param linkCapacityEstimateList a list of {@link LinkCapacityEstimate}
+ * The list size is at least 1.
+ * In case of a dual connected network, the list size could be 2.
+ * Use {@link LinkCapacityEstimate#getType()} to get the type of each element.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+ void onLinkCapacityEstimateChanged(
+ @NonNull List<LinkCapacityEstimate> linkCapacityEstimateList);
+ }
/**
* The callback methods need to be called on the handler thread where
@@ -1707,14 +1740,26 @@
enabled, reason)));
}
- public void onAllowedNetworkTypesChanged(Map allowedNetworkTypesList) {
+ public void onAllowedNetworkTypesChanged(int reason, long allowedNetworkType) {
AllowedNetworkTypesListener listener =
(AllowedNetworkTypesListener) mTelephonyCallbackWeakRef.get();
if (listener == null) return;
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(
- () -> listener.onAllowedNetworkTypesChanged(allowedNetworkTypesList)));
+ () -> listener.onAllowedNetworkTypesChanged(reason,
+ allowedNetworkType)));
+ }
+
+ public void onLinkCapacityEstimateChanged(
+ List<LinkCapacityEstimate> linkCapacityEstimateList) {
+ LinkCapacityEstimateChangedListener listener =
+ (LinkCapacityEstimateChangedListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> listener.onLinkCapacityEstimateChanged(
+ linkCapacityEstimateList)));
}
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9cda4ae..1ec12fe 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -825,24 +825,40 @@
}
/**
- * Notify emergency number list changed on certain subscription.
- *
- * @param slotIndex for which emergency number list changed. Can be derived from subId except
- * when subId is invalid.
- * @param subId for which emergency number list changed.
+ * Notify the allowed network types has changed for a specific subscription and the specific
+ * reason.
+ * @param slotIndex for which allowed network types changed.
+ * @param subId for which allowed network types changed.
+ * @param reason an allowed network type reasons.
+ * @param allowedNetworkType an allowed network type bitmask value.
*/
public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId,
- Map<Integer, Long> allowedNetworkTypeList) {
+ int reason, long allowedNetworkType) {
try {
- sRegistry.notifyAllowedNetworkTypesChanged(slotIndex, subId, allowedNetworkTypeList);
+ sRegistry.notifyAllowedNetworkTypesChanged(slotIndex, subId, reason,
+ allowedNetworkType);
} catch (RemoteException ex) {
// system process is dead
}
}
+ /**
+ * Notify that the link capacity estimate has changed.
+ * @param slotIndex for the phone object that gets the updated link capacity estimate
+ * @param subId for subscription that gets the updated link capacity estimate
+ * @param linkCapacityEstimateList a list of {@link LinkCapacityEstimate}
+ */
+ public void notifyLinkCapacityEstimateChanged(int slotIndex, int subId,
+ List<LinkCapacityEstimate> linkCapacityEstimateList) {
+ try {
+ sRegistry.notifyLinkCapacityEstimateChanged(slotIndex, subId, linkCapacityEstimateList);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
public @NonNull Set<Integer> getEventsFromCallback(
@NonNull TelephonyCallback telephonyCallback) {
-
Set<Integer> eventList = new ArraySet<>();
if (telephonyCallback instanceof TelephonyCallback.ServiceStateListener) {
@@ -974,6 +990,10 @@
eventList.add(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED);
}
+ if (telephonyCallback instanceof TelephonyCallback.LinkCapacityEstimateChangedListener) {
+ eventList.add(TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED);
+ }
+
return eventList;
}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index 3880131..f61ab29 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -16,14 +16,26 @@
package android.util;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Formatter;
+import java.util.Locale;
+
/**
* @hide
*/
public final class Slog {
+ @GuardedBy("sMessageBuilder")
+ private static final StringBuilder sMessageBuilder = new StringBuilder();
+
+ @GuardedBy("sMessageBuilder")
+ private static final Formatter sFormatter = new Formatter(sMessageBuilder, Locale.ENGLISH);
+
private Slog() {
}
@@ -37,6 +49,15 @@
msg + '\n' + Log.getStackTraceString(tr));
}
+ /**
+ * Logs a {@link Log.VERBOSE} message.
+ */
+ public static void v(String tag, String format, @Nullable Object... args) {
+ if (!Log.isLoggable(tag, Log.VERBOSE)) return;
+
+ v(tag, getMessage(format, args));
+ }
+
@UnsupportedAppUsage
public static int d(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
@@ -48,6 +69,15 @@
msg + '\n' + Log.getStackTraceString(tr));
}
+ /**
+ * Logs a {@link Log.DEBUG} message.
+ */
+ public static void d(String tag, String format, @Nullable Object... args) {
+ if (!Log.isLoggable(tag, Log.DEBUG)) return;
+
+ d(tag, getMessage(format, args));
+ }
+
@UnsupportedAppUsage
public static int i(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
@@ -58,6 +88,15 @@
msg + '\n' + Log.getStackTraceString(tr));
}
+ /**
+ * Logs a {@link Log.INFO} message.
+ */
+ public static void i(String tag, String format, @Nullable Object... args) {
+ if (!Log.isLoggable(tag, Log.INFO)) return;
+
+ i(tag, getMessage(format, args));
+ }
+
@UnsupportedAppUsage
public static int w(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
@@ -73,6 +112,24 @@
return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, Log.getStackTraceString(tr));
}
+ /**
+ * Logs a {@link Log.WARN} message.
+ */
+ public static void w(String tag, String format, @Nullable Object... args) {
+ if (!Log.isLoggable(tag, Log.WARN)) return;
+
+ w(tag, getMessage(format, args));
+ }
+
+ /**
+ * Logs a {@link Log.WARN} message with an exception
+ */
+ public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
+ if (!Log.isLoggable(tag, Log.WARN)) return;
+
+ w(tag, getMessage(format, args), exception);
+ }
+
@UnsupportedAppUsage
public static int e(String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
@@ -85,6 +142,24 @@
}
/**
+ * Logs a {@link Log.ERROR} message.
+ */
+ public static void e(String tag, String format, @Nullable Object... args) {
+ if (!Log.isLoggable(tag, Log.ERROR)) return;
+
+ e(tag, getMessage(format, args));
+ }
+
+ /**
+ * Logs a {@link Log.ERROR} message with an exception
+ */
+ public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
+ if (!Log.isLoggable(tag, Log.ERROR)) return;
+
+ e(tag, getMessage(format, args), exception);
+ }
+
+ /**
* Like {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
* will always be handled asynchronously. Primarily for use by coding running within
* the system process.
@@ -95,6 +170,21 @@
}
/**
+ * Logs a {@code wtf} message.
+ */
+ public static void wtf(String tag, String format, @Nullable Object... args) {
+ wtf(tag, getMessage(format, args));
+ }
+
+ /**
+ * Logs a {@code wtf} message with an exception.
+ */
+ public static void wtf(String tag, Exception exception, String format,
+ @Nullable Object... args) {
+ wtf(tag, getMessage(format, args), exception);
+ }
+
+ /**
* Like {@link #wtf(String, String)}, but does not output anything to the log.
*/
public static void wtfQuiet(String tag, String msg) {
@@ -134,5 +224,13 @@
public static int println(int priority, String tag, String msg) {
return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg);
}
-}
+ private static String getMessage(String format, @Nullable Object... args) {
+ synchronized (sMessageBuilder) {
+ sFormatter.format(format, args);
+ String message = sMessageBuilder.toString();
+ sMessageBuilder.setLength(0);
+ return message;
+ }
+ }
+}
diff --git a/core/java/android/view/CrossWindowBlurListeners.java b/core/java/android/view/CrossWindowBlurListeners.java
index 5a1b850..55fc4f4 100644
--- a/core/java/android/view/CrossWindowBlurListeners.java
+++ b/core/java/android/view/CrossWindowBlurListeners.java
@@ -16,13 +16,19 @@
package android.view;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.util.ArraySet;
+import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -42,7 +48,7 @@
private static final Object sLock = new Object();
private final BlurEnabledListenerInternal mListenerInternal = new BlurEnabledListenerInternal();
- private final ArraySet<Consumer<Boolean>> mListeners = new ArraySet();
+ private final ArrayMap<Consumer<Boolean>, Executor> mListeners = new ArrayMap();
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private boolean mInternalListenerAttached = false;
private boolean mCrossWindowBlurEnabled;
@@ -74,20 +80,22 @@
}
}
- void addListener(Consumer<Boolean> listener) {
- if (listener == null) return;
+ void addListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ Preconditions.checkNotNull(executor, "executor cannot be null");
synchronized (sLock) {
attachInternalListenerIfNeededLocked();
- mListeners.add(listener);
- notifyListenerOnMain(listener, mCrossWindowBlurEnabled);
+ mListeners.put(listener, executor);
+ notifyListener(listener, executor, mCrossWindowBlurEnabled);
}
}
void removeListener(Consumer<Boolean> listener) {
- if (listener == null) return;
+ Preconditions.checkNotNull(listener, "listener cannot be null");
synchronized (sLock) {
mListeners.remove(listener);
@@ -116,10 +124,8 @@
}
}
- private void notifyListenerOnMain(Consumer<Boolean> listener, boolean enabled) {
- mMainHandler.post(() -> {
- listener.accept(enabled);
- });
+ private void notifyListener(Consumer<Boolean> listener, Executor executor, boolean enabled) {
+ executor.execute(() -> listener.accept(enabled));
}
private final class BlurEnabledListenerInternal extends ICrossWindowBlurEnabledListener.Stub {
@@ -128,8 +134,13 @@
synchronized (sLock) {
mCrossWindowBlurEnabled = enabled;
- for (int i = 0; i < mListeners.size(); i++) {
- notifyListenerOnMain(mListeners.valueAt(i), enabled);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = 0; i < mListeners.size(); i++) {
+ notifyListener(mListeners.keyAt(i), mListeners.valueAt(i), enabled);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
}
diff --git a/core/java/android/view/DragAndDropPermissions.java b/core/java/android/view/DragAndDropPermissions.java
index d47604d..16204d8 100644
--- a/core/java/android/view/DragAndDropPermissions.java
+++ b/core/java/android/view/DragAndDropPermissions.java
@@ -16,12 +16,15 @@
package android.view;
+import static java.lang.Integer.toHexString;
+
import android.app.Activity;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.view.IDragAndDropPermissions;
@@ -56,9 +59,28 @@
*/
public final class DragAndDropPermissions implements Parcelable {
- private final IDragAndDropPermissions mDragAndDropPermissions;
+ private static final String TAG = "DragAndDrop";
+ private static final boolean DEBUG = false;
- private IBinder mTransientToken;
+ /**
+ * Permissions for a drop can be granted in one of two ways:
+ * <ol>
+ * <li>An app can explicitly request permissions using
+ * {@link Activity#requestDragAndDropPermissions(DragEvent)}. In this case permissions are
+ * revoked automatically when then activity is destroyed. See {@link #take(IBinder)}.
+ * <li>The platform can request permissions on behalf of the app (e.g. in
+ * {@link android.widget.Editor}). In this case permissions are revoked automatically when
+ * the app process terminates. See {@link #takeTransient()}.
+ * </ol>
+ *
+ * <p>In order to implement the second case above, we create a static token object here. This
+ * ensures that the token stays alive for the lifetime of the app process, allowing us to
+ * revoke permissions when the app process terminates using {@link IBinder#linkToDeath} in
+ * {@code DragAndDropPermissionsHandler}.
+ */
+ private static IBinder sAppToken;
+
+ private final IDragAndDropPermissions mDragAndDropPermissions;
/**
* Create a new {@link DragAndDropPermissions} object to control the access permissions for
@@ -81,30 +103,51 @@
}
/**
- * Take the permissions and bind their lifetime to the activity.
+ * Take permissions, binding their lifetime to the activity.
+ *
+ * <p>Note: This API is exposed to apps via
+ * {@link Activity#requestDragAndDropPermissions(DragEvent)}.
+ *
* @param activityToken Binder pointing to an Activity instance to bind the lifetime to.
* @return True if permissions are successfully taken.
+ *
* @hide
*/
public boolean take(IBinder activityToken) {
try {
+ if (DEBUG) {
+ Log.d(TAG, this + ": calling take() with activity-bound token: "
+ + toHexString(activityToken.hashCode()));
+ }
mDragAndDropPermissions.take(activityToken);
} catch (RemoteException e) {
+ Log.w(TAG, this + ": take() failed with a RemoteException", e);
return false;
}
return true;
}
/**
- * Take the permissions. Must call {@link #release} explicitly.
+ * Take permissions transiently. Permissions will be revoked when the app process terminates.
+ *
+ * <p>Note: This API is not exposed to apps.
+ *
* @return True if permissions are successfully taken.
+ *
* @hide
*/
public boolean takeTransient() {
try {
- mTransientToken = new Binder();
- mDragAndDropPermissions.takeTransient(mTransientToken);
+ if (sAppToken == null) {
+ sAppToken = new Binder();
+ }
+ if (DEBUG) {
+ Log.d(TAG, this + ": calling takeTransient() with process-bound token: "
+ + toHexString(sAppToken.hashCode()));
+ }
+ mDragAndDropPermissions.takeTransient(sAppToken);
} catch (RemoteException e) {
+ Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
return false;
}
return true;
@@ -116,8 +159,8 @@
public void release() {
try {
mDragAndDropPermissions.release();
- mTransientToken = null;
} catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -142,11 +185,9 @@
@Override
public void writeToParcel(Parcel destination, int flags) {
destination.writeStrongInterface(mDragAndDropPermissions);
- destination.writeStrongBinder(mTransientToken);
}
private DragAndDropPermissions(Parcel in) {
mDragAndDropPermissions = IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());
- mTransientToken = in.readStrongBinder();
}
}
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index ddb49786..10721ad 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -43,8 +43,11 @@
* accordingly. This should be called before `finish`
* @param taskId for which the leash should be updated
* @param destinationBounds bounds of the final PiP window
+ * @param windowCrop bounds to crop as part of final transform.
+ * @param float9 An array of 9 floats to be used as matrix transform.
*/
- void setFinishTaskBounds(int taskId, in Rect destinationBounds);
+ void setFinishTaskBounds(int taskId, in Rect destinationBounds, in Rect windowCrop,
+ in float[] float9);
/**
* Notifies to the system that the animation into Recents should end, and all leashes associated
diff --git a/core/java/android/view/IScrollCaptureCallbacks.aidl b/core/java/android/view/IScrollCaptureCallbacks.aidl
index 26eaac0..9b35614 100644
--- a/core/java/android/view/IScrollCaptureCallbacks.aidl
+++ b/core/java/android/view/IScrollCaptureCallbacks.aidl
@@ -27,13 +27,6 @@
*/
interface IScrollCaptureCallbacks {
/**
- * Provides the result of WindowManagerService#requestScrollCapture
- *
- * @param response the response which describes the result
- */
- oneway void onScrollCaptureResponse(in ScrollCaptureResponse response);
-
- /**
* Called in reply to IScrollCaptureConnection#startCapture, when the remote end has confirmed
* the request and is ready to begin capturing images.
*/
diff --git a/core/java/android/view/IScrollCaptureConnection.aidl b/core/java/android/view/IScrollCaptureConnection.aidl
index c55e888..3a6b693 100644
--- a/core/java/android/view/IScrollCaptureConnection.aidl
+++ b/core/java/android/view/IScrollCaptureConnection.aidl
@@ -18,6 +18,7 @@
import android.graphics.Rect;
import android.os.ICancellationSignal;
+import android.view.IScrollCaptureCallbacks;
import android.view.Surface;
@@ -31,11 +32,12 @@
/**
* Informs the target that it has been selected for scroll capture.
*
- * @param surface a return channel for image buffers
+ * @param surface used to shuttle image buffers between processes
+ * @param callbacks a return channel for requests
*
- * @return a cancallation signal which is used cancel the request
+ * @return a cancallation signal which is used cancel the start request
*/
- ICancellationSignal startCapture(in Surface surface);
+ ICancellationSignal startCapture(in Surface surface, IScrollCaptureCallbacks callbacks);
/**
* Request the target capture an image within the provided rectangle.
diff --git a/core/java/android/view/IScrollCaptureResponseListener.aidl b/core/java/android/view/IScrollCaptureResponseListener.aidl
new file mode 100644
index 0000000..7220f6c
--- /dev/null
+++ b/core/java/android/view/IScrollCaptureResponseListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import android.graphics.Rect;
+import android.view.ScrollCaptureResponse;
+import android.view.Surface;
+
+/**
+ * Asynchronous callback channel for the initial response to a scroll capture request.
+ *
+ * {@hide}
+ */
+interface IScrollCaptureResponseListener {
+ /**
+ * Provides the initial response to a scroll capture request.
+ *
+ * @param response the response which describes the result
+ */
+ oneway void onScrollCaptureResponse(in ScrollCaptureResponse response);
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index fb012eb..8d59ba0 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -26,7 +26,7 @@
import android.view.DragEvent;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.window.ClientWindowFrames;
@@ -134,5 +134,5 @@
*
* @param callbacks to receive responses
*/
- void requestScrollCapture(in IScrollCaptureCallbacks callbacks);
+ void requestScrollCapture(in IScrollCaptureResponseListener callbacks);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b345b2e..a42126f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -42,7 +42,7 @@
import android.view.IDisplayWindowRotationController;
import android.view.IOnKeyguardExitResult;
import android.view.IPinnedTaskListener;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.RemoteAnimationAdapter;
import android.view.IRotationWatcher;
import android.view.ISystemGestureExclusionListener;
@@ -744,10 +744,10 @@
* @param behindClient token for a window, used to filter the search to windows behind it, or
* {@code null} to accept a window at any zOrder
* @param taskId specifies the id of a task the result must belong to, or -1 to ignore task ids
- * @param callbacks the object to receive replies
+ * @param listener the object to receive the response
*/
void requestScrollCapture(int displayId, IBinder behindClient, int taskId,
- IScrollCaptureCallbacks callbacks);
+ IScrollCaptureResponseListener listener);
/**
* Holds the WM lock for the specified amount of milliseconds.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index a89c540..7a61df8 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -29,6 +29,7 @@
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsState.ISIDE_BOTTOM;
+import static android.view.InsetsState.ISIDE_FLOATING;
import static android.view.InsetsState.ISIDE_LEFT;
import static android.view.InsetsState.ISIDE_RIGHT;
import static android.view.InsetsState.ISIDE_TOP;
@@ -74,8 +75,7 @@
private final WindowInsetsAnimationControlListener mListener;
private final SparseArray<InsetsSourceControl> mControls;
- private final SparseIntArray mTypeSideMap = new SparseIntArray();
- private final SparseSetArray<InsetsSourceControl> mSideSourceMap = new SparseSetArray<>();
+ private final SparseSetArray<InsetsSourceControl> mSideControlsMap = new SparseSetArray<>();
/** @see WindowInsetsAnimationController#getHiddenStateInsets */
private final Insets mHiddenInsets;
@@ -104,8 +104,8 @@
private Boolean mPerceptible;
@VisibleForTesting
- public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
- InsetsState state, WindowInsetsAnimationControlListener listener,
+ public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
+ @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator,
@AnimationType int animationType, CompatibilityInfo.Translator translator) {
@@ -114,19 +114,30 @@
mTypes = types;
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
- mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
- mPendingInsets = mCurrentInsets;
- mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
- null /* typeSideMap */);
- mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
- mTypeSideMap);
- mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
- if (mHasZeroInsetsIme) {
- // IME has shownInsets of ZERO, and can't map to a side by default.
- // Map zero insets IME to bottom, making it a special case of bottom insets.
- mTypeSideMap.put(ITYPE_IME, ISIDE_BOTTOM);
+ if (frame != null) {
+ final SparseIntArray typeSideMap = new SparseIntArray();
+ mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
+ mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
+ null /* typeSideMap */);
+ mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
+ typeSideMap);
+ mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+ if (mHasZeroInsetsIme) {
+ // IME has shownInsets of ZERO, and can't map to a side by default.
+ // Map zero insets IME to bottom, making it a special case of bottom insets.
+ typeSideMap.put(ITYPE_IME, ISIDE_BOTTOM);
+ }
+ buildSideControlsMap(typeSideMap, mSideControlsMap, controls);
+ } else {
+ // Passing a null frame indicates the caller wants to play the insets animation anyway,
+ // no matter the source provides insets to the frame or not.
+ mCurrentInsets = calculateInsets(mInitialInsetsState, controls, true /* shown */);
+ mHiddenInsets = calculateInsets(null, controls, false /* shown */);
+ mShownInsets = calculateInsets(null, controls, true /* shown */);
+ mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+ buildSideControlsMap(mSideControlsMap, controls);
}
- buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
+ mPendingInsets = mCurrentInsets;
mAnimation = new WindowInsetsAnimation(mTypes, interpolator,
durationMs);
@@ -312,25 +323,10 @@
proto.end(token);
}
- WindowInsetsAnimationControlListener getListener() {
- return mListener;
- }
-
SparseArray<InsetsSourceControl> getControls() {
return mControls;
}
- private Insets calculateInsets(InsetsState state, Rect frame,
- SparseArray<InsetsSourceControl> controls, boolean shown,
- @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
- for (int i = controls.size() - 1; i >= 0; i--) {
- // control may be null if it got revoked.
- if (controls.valueAt(i) == null) continue;
- state.getSource(controls.valueAt(i).getType()).setVisible(shown);
- }
- return getInsetsFromState(state, frame, typeSideMap);
- }
-
private Insets getInsetsFromState(InsetsState state, Rect frame,
@Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
return state.calculateInsets(frame, null /* ignoringVisibilityState */,
@@ -340,6 +336,41 @@
WINDOWING_MODE_UNDEFINED, typeSideMap).getInsets(mTypes);
}
+ /** Computes the insets relative to the given frame. */
+ private Insets calculateInsets(InsetsState state, Rect frame,
+ SparseArray<InsetsSourceControl> controls, boolean shown,
+ @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+ for (int i = controls.size() - 1; i >= 0; i--) {
+ final InsetsSourceControl control = controls.valueAt(i);
+ if (control == null) {
+ // control may be null if it got revoked.
+ continue;
+ }
+ state.getSource(control.getType()).setVisible(shown);
+ }
+ return getInsetsFromState(state, frame, typeSideMap);
+ }
+
+ /** Computes the insets from the insets hints of controls. */
+ private Insets calculateInsets(InsetsState state, SparseArray<InsetsSourceControl> controls,
+ boolean shownOrCurrent) {
+ Insets insets = Insets.NONE;
+ if (!shownOrCurrent) {
+ return insets;
+ }
+ for (int i = controls.size() - 1; i >= 0; i--) {
+ final InsetsSourceControl control = controls.valueAt(i);
+ if (control == null) {
+ // control may be null if it got revoked.
+ continue;
+ }
+ if (state == null || state.getSource(control.getType()).isVisible()) {
+ insets = Insets.max(insets, control.getInsetsHint());
+ }
+ }
+ return insets;
+ }
+
private Insets sanitize(Insets insets) {
if (insets == null) {
insets = getCurrentInsets();
@@ -356,13 +387,13 @@
private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
ArrayList<SurfaceParams> surfaceParams, @Nullable InsetsState outState, float alpha) {
- ArraySet<InsetsSourceControl> items = mSideSourceMap.get(side);
- if (items == null) {
+ final ArraySet<InsetsSourceControl> controls = mSideControlsMap.get(side);
+ if (controls == null) {
return;
}
// TODO: Implement behavior when inset spans over multiple types
- for (int i = items.size() - 1; i >= 0; i--) {
- final InsetsSourceControl control = items.valueAt(i);
+ for (int i = controls.size() - 1; i >= 0; i--) {
+ final InsetsSourceControl control = controls.valueAt(i);
final InsetsSource source = mInitialInsetsState.getSource(control.getType());
final SurfaceControl leash = control.getLeash();
@@ -371,7 +402,7 @@
addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM
- ? (mAnimationType == ANIMATION_TYPE_SHOW ? true : !mFinished)
+ ? (mAnimationType == ANIMATION_TYPE_SHOW || !mFinished)
: inset != 0;
if (outState != null) {
@@ -391,32 +422,32 @@
}
}
- private void addTranslationToMatrix(@InternalInsetsSide int side, int inset, Matrix m,
+ private void addTranslationToMatrix(@InternalInsetsSide int side, int offset, Matrix m,
Rect frame) {
final float surfaceOffset = mTranslator != null
- ? mTranslator.translateLengthInAppWindowToScreen(inset) : inset;
+ ? mTranslator.translateLengthInAppWindowToScreen(offset) : offset;
switch (side) {
case ISIDE_LEFT:
m.postTranslate(-surfaceOffset, 0);
- frame.offset(-inset, 0);
+ frame.offset(-offset, 0);
break;
case ISIDE_TOP:
m.postTranslate(0, -surfaceOffset);
- frame.offset(0, -inset);
+ frame.offset(0, -offset);
break;
case ISIDE_RIGHT:
m.postTranslate(surfaceOffset, 0);
- frame.offset(inset, 0);
+ frame.offset(offset, 0);
break;
case ISIDE_BOTTOM:
m.postTranslate(0, surfaceOffset);
- frame.offset(0, inset);
+ frame.offset(0, offset);
break;
}
}
- private static void buildTypeSourcesMap(SparseIntArray typeSideMap,
- SparseSetArray<InsetsSourceControl> sideSourcesMap,
+ private static void buildSideControlsMap(SparseIntArray typeSideMap,
+ SparseSetArray<InsetsSourceControl> sideControlsMap,
SparseArray<InsetsSourceControl> controls) {
for (int i = typeSideMap.size() - 1; i >= 0; i--) {
final int type = typeSideMap.keyAt(i);
@@ -427,7 +458,24 @@
// there can be some null controllers.
continue;
}
- sideSourcesMap.add(side, control);
+ sideControlsMap.add(side, control);
+ }
+ }
+
+ private static void buildSideControlsMap(
+ SparseSetArray<InsetsSourceControl> sideControlsMap,
+ SparseArray<InsetsSourceControl> controls) {
+ for (int i = controls.size() - 1; i >= 0; i--) {
+ final InsetsSourceControl control = controls.valueAt(i);
+ if (control == null) {
+ // control may be null if it got revoked.
+ continue;
+ }
+ @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
+ if (side == ISIDE_FLOATING && control.getType() == ITYPE_IME) {
+ side = ISIDE_BOTTOM;
+ }
+ sideControlsMap.add(side, control);
}
}
}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 6a34a15..6122c90 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -19,6 +19,7 @@
import static android.view.InsetsController.DEBUG;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
+import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.res.CompatibilityInfo;
import android.graphics.Rect;
@@ -100,8 +101,8 @@
};
@UiThread
- public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls, Rect frame,
- InsetsState state, WindowInsetsAnimationControlListener listener,
+ public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
+ @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator,
@AnimationType int animationType, CompatibilityInfo.Translator translator,
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index e681c0e..8bf78db3 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -837,9 +837,12 @@
PendingControlRequest pendingRequest = mPendingImeControlRequest;
mPendingImeControlRequest = null;
mHandler.removeCallbacks(mPendingControlTimeout);
+
+ // We are about to playing the default animation. Passing a null frame indicates the
+ // controlled types should be animated regardless of the frame.
controlAnimationUnchecked(
pendingRequest.types, pendingRequest.cancellationSignal,
- pendingRequest.listener, mFrame,
+ pendingRequest.listener, null /* frame */,
true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
pendingRequest.animationType,
pendingRequest.layoutInsetsDuringAnimation,
@@ -934,7 +937,7 @@
private void controlAnimationUnchecked(@InsetsType int types,
@Nullable CancellationSignal cancellationSignal,
- WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
+ WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
long durationMs, Interpolator interpolator,
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
@@ -1358,10 +1361,10 @@
show, hasAnimationCallbacks, types, skipAnim || mAnimationsDisabled,
mHost.dipToPx(InternalAnimationControlListener.FLOATING_IME_BOTTOM_INSET));
- // Show/hide animations always need to be relative to the display frame, in order that shown
- // and hidden state insets are correct.
+ // 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, mState.getDisplayFrame(), fromIme,
+ types, null /* cancellationSignal */, listener, null /* frame */, fromIme,
listener.getDurationMs(), listener.getInterpolator(),
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 1d4b411..9256bef 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -23,6 +23,7 @@
import static android.view.InsetsSourceControlProto.TYPE;
import android.annotation.Nullable;
+import android.graphics.Insets;
import android.graphics.Point;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,13 +42,19 @@
private final @InternalInsetsType int mType;
private final @Nullable SurfaceControl mLeash;
private final Point mSurfacePosition;
+
+ // This is used while playing an insets animation regardless of the relative frame. This would
+ // be the insets received by the bounds of its source window.
+ private Insets mInsetsHint;
+
private boolean mSkipAnimationOnce;
public InsetsSourceControl(@InternalInsetsType int type, @Nullable SurfaceControl leash,
- Point surfacePosition) {
+ Point surfacePosition, Insets insetsHint) {
mType = type;
mLeash = leash;
mSurfacePosition = surfacePosition;
+ mInsetsHint = insetsHint;
}
public InsetsSourceControl(InsetsSourceControl other) {
@@ -58,9 +65,18 @@
mLeash = null;
}
mSurfacePosition = new Point(other.mSurfacePosition);
+ mInsetsHint = other.mInsetsHint;
mSkipAnimationOnce = other.getAndClearSkipAnimationOnce();
}
+ public InsetsSourceControl(Parcel in) {
+ mType = in.readInt();
+ mLeash = in.readTypedObject(SurfaceControl.CREATOR);
+ mSurfacePosition = in.readTypedObject(Point.CREATOR);
+ mInsetsHint = in.readTypedObject(Insets.CREATOR);
+ mSkipAnimationOnce = in.readBoolean();
+ }
+
public int getType() {
return mType;
}
@@ -75,13 +91,6 @@
return mLeash;
}
- public InsetsSourceControl(Parcel in) {
- mType = in.readInt();
- mLeash = in.readTypedObject(SurfaceControl.CREATOR);
- mSurfacePosition = in.readTypedObject(Point.CREATOR);
- mSkipAnimationOnce = in.readBoolean();
- }
-
public boolean setSurfacePosition(int left, int top) {
if (mSurfacePosition.equals(left, top)) {
return false;
@@ -90,14 +99,26 @@
return true;
}
- public void setSkipAnimationOnce(boolean skipAnimation) {
- mSkipAnimationOnce = skipAnimation;
- }
-
public Point getSurfacePosition() {
return mSurfacePosition;
}
+ public void setInsetsHint(Insets insets) {
+ mInsetsHint = insets;
+ }
+
+ public void setInsetsHint(int left, int top, int right, int bottom) {
+ mInsetsHint = Insets.of(left, top, right, bottom);
+ }
+
+ public Insets getInsetsHint() {
+ return mInsetsHint;
+ }
+
+ public void setSkipAnimationOnce(boolean skipAnimation) {
+ mSkipAnimationOnce = skipAnimation;
+ }
+
/**
* Get the state whether the current control needs to skip animation or not.
*
@@ -121,6 +142,7 @@
dest.writeInt(mType);
dest.writeTypedObject(mLeash, 0 /* parcelableFlags */);
dest.writeTypedObject(mSurfacePosition, 0 /* parcelableFlags */);
+ dest.writeTypedObject(mInsetsHint, 0 /* parcelableFlags */);
dest.writeBoolean(mSkipAnimationOnce);
}
@@ -135,6 +157,7 @@
pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType));
pw.print(" mLeash="); pw.print(mLeash);
pw.print(" mSurfacePosition="); pw.print(mSurfacePosition);
+ pw.print(" mInsetsHint="); pw.print(mInsetsHint);
pw.print(" mSkipAnimationOnce="); pw.print(mSkipAnimationOnce);
pw.println();
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index fce1952..3d1c8ff 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -400,7 +400,7 @@
* Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
* is set in order that this method returns a meaningful result.
*/
- private @InternalInsetsSide int getInsetSide(Insets insets) {
+ static @InternalInsetsSide int getInsetSide(Insets insets) {
if (Insets.NONE.equals(insets)) {
return ISIDE_FLOATING;
}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 02a9788..aa1acc1 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.input.InputManager;
import android.os.Build;
@@ -25,6 +26,8 @@
import android.util.AndroidRuntimeException;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.text.Normalizer;
/**
@@ -297,6 +300,8 @@
private static native char nativeGetDisplayLabel(long ptr, int keyCode);
private static native int nativeGetKeyboardType(long ptr);
private static native KeyEvent[] nativeGetEvents(long ptr, char[] chars);
+ private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId);
+ private static native boolean nativeEquals(long ptr1, long ptr2);
private KeyCharacterMap(Parcel in) {
if (in == null) {
@@ -323,6 +328,18 @@
}
/**
+ * Obtain empty key character map
+ * @param deviceId The input device ID
+ * @return The KeyCharacterMap object
+ * @hide
+ */
+ @VisibleForTesting
+ @Nullable
+ public static KeyCharacterMap obtainEmptyMap(int deviceId) {
+ return nativeObtainEmptyKeyCharacterMap(deviceId);
+ }
+
+ /**
* Loads the key character maps for the keyboard with the specified device id.
*
* @param deviceId The device id of the keyboard.
@@ -729,6 +746,18 @@
return 0;
}
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof KeyCharacterMap)) {
+ return false;
+ }
+ KeyCharacterMap peer = (KeyCharacterMap) obj;
+ if (mPtr == 0 || peer.mPtr == 0) {
+ return mPtr == peer.mPtr;
+ }
+ return nativeEquals(mPtr, peer.mPtr);
+ }
+
/**
* Thrown by {@link KeyCharacterMap#load} when a key character map could not be loaded.
*/
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index b1b670f..14dcdad 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -31,6 +31,7 @@
import static android.view.RemoteAnimationTargetProto.START_LEASH;
import static android.view.RemoteAnimationTargetProto.TASK_ID;
import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import android.annotation.IntDef;
import android.app.PictureInPictureParams;
@@ -195,12 +196,30 @@
*/
public PictureInPictureParams pictureInPictureParams;
+ /**
+ * The {@link android.view.WindowManager.LayoutParams.WindowType} of this window. It's only used
+ * for non-app window.
+ */
+ public final @WindowManager.LayoutParams.WindowType int windowType;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
WindowConfiguration windowConfig, boolean isNotInRecents,
SurfaceControl startLeash, Rect startBounds,
PictureInPictureParams pictureInPictureParams) {
+ this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex,
+ position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash,
+ startBounds, pictureInPictureParams, INVALID_WINDOW_TYPE);
+ }
+
+ public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
+ Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
+ Rect localBounds, Rect screenSpaceBounds,
+ WindowConfiguration windowConfig, boolean isNotInRecents,
+ SurfaceControl startLeash, Rect startBounds,
+ PictureInPictureParams pictureInPictureParams,
+ @WindowManager.LayoutParams.WindowType int windowType) {
this.mode = mode;
this.taskId = taskId;
this.leash = leash;
@@ -217,6 +236,7 @@
this.startLeash = startLeash;
this.startBounds = startBounds == null ? null : new Rect(startBounds);
this.pictureInPictureParams = pictureInPictureParams;
+ this.windowType = windowType;
}
public RemoteAnimationTarget(Parcel in) {
@@ -236,6 +256,7 @@
startLeash = in.readTypedObject(SurfaceControl.CREATOR);
startBounds = in.readTypedObject(Rect.CREATOR);
pictureInPictureParams = in.readTypedObject(PictureInPictureParams.CREATOR);
+ windowType = in.readInt();
}
@Override
@@ -261,6 +282,7 @@
dest.writeTypedObject(startLeash, 0 /* flags */);
dest.writeTypedObject(startBounds, 0 /* flags */);
dest.writeTypedObject(pictureInPictureParams, 0 /* flags */);
+ dest.writeInt(windowType);
}
public void dump(PrintWriter pw, String prefix) {
@@ -278,6 +300,7 @@
pw.print(prefix); pw.print("windowConfiguration="); pw.println(windowConfiguration);
pw.print(prefix); pw.print("leash="); pw.println(leash);
pw.print(prefix); pw.print("pictureInPictureParams="); pw.println(pictureInPictureParams);
+ pw.print(prefix); pw.print("windowType="); pw.print(windowType);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/RoundedCorner.java b/core/java/android/view/RoundedCorner.java
index cc7525b..56b4383 100644
--- a/core/java/android/view/RoundedCorner.java
+++ b/core/java/android/view/RoundedCorner.java
@@ -163,7 +163,7 @@
* @hide
*/
public boolean isEmpty() {
- return mRadius == 0 || mCenter.x == 0 || mCenter.y == 0;
+ return mRadius == 0 || mCenter.x <= 0 || mCenter.y <= 0;
}
private String getPositionString(@Position int position) {
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index 569c287..623d969 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -181,16 +181,16 @@
boolean hasRoundedCorner;
switch (position) {
case POSITION_TOP_LEFT:
- hasRoundedCorner = radius > insetTop || radius > insetLeft;
+ hasRoundedCorner = radius > insetTop && radius > insetLeft;
break;
case POSITION_TOP_RIGHT:
- hasRoundedCorner = radius > insetTop || radius > insetRight;
+ hasRoundedCorner = radius > insetTop && radius > insetRight;
break;
case POSITION_BOTTOM_RIGHT:
- hasRoundedCorner = radius > insetBottom || radius > insetRight;
+ hasRoundedCorner = radius > insetBottom && radius > insetRight;
break;
case POSITION_BOTTOM_LEFT:
- hasRoundedCorner = radius > insetBottom || radius > insetLeft;
+ hasRoundedCorner = radius > insetBottom && radius > insetLeft;
break;
default:
throw new IllegalArgumentException(
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index 3456e01..a6d786e 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -32,6 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -50,8 +51,9 @@
private final Object mLock = new Object();
private final Rect mScrollBounds;
private final Point mPositionInWindow;
- private final CloseGuard mCloseGuard;
private final Executor mUiThread;
+ private final CloseGuard mCloseGuard = new CloseGuard();
+
private ScrollCaptureCallback mLocal;
private IScrollCaptureCallbacks mRemote;
@@ -60,42 +62,38 @@
private CancellationSignal mCancellation;
- private volatile boolean mStarted;
- private volatile boolean mConnected;
+ private volatile boolean mActive;
/**
* Constructs a ScrollCaptureConnection.
*
+ * @param uiThread an executor for the UI thread of the containing View
* @param selectedTarget the target the client is controlling
- * @param remote the callbacks to reply to system requests
*
* @hide
*/
public ScrollCaptureConnection(
@NonNull Executor uiThread,
- @NonNull ScrollCaptureTarget selectedTarget,
- @NonNull IScrollCaptureCallbacks remote) {
+ @NonNull ScrollCaptureTarget selectedTarget) {
mUiThread = requireNonNull(uiThread, "<uiThread> must non-null");
requireNonNull(selectedTarget, "<selectedTarget> must non-null");
- mRemote = requireNonNull(remote, "<callbacks> must non-null");
mScrollBounds = requireNonNull(Rect.copyOrNull(selectedTarget.getScrollBounds()),
"target.getScrollBounds() must be non-null to construct a client");
-
mLocal = selectedTarget.getCallback();
mPositionInWindow = new Point(selectedTarget.getPositionInWindow());
-
- mCloseGuard = new CloseGuard();
- mCloseGuard.open("close");
- mConnected = true;
}
@BinderThread
@Override
- public ICancellationSignal startCapture(Surface surface) throws RemoteException {
- checkConnected();
+ public ICancellationSignal startCapture(@NonNull Surface surface,
+ @NonNull IScrollCaptureCallbacks remote) throws RemoteException {
+
+ mCloseGuard.open("close");
+
if (!surface.isValid()) {
throw new RemoteException(new IllegalArgumentException("surface must be valid"));
}
+ mRemote = requireNonNull(remote, "<callbacks> must non-null");
ICancellationSignal cancellation = CancellationSignal.createTransport();
mCancellation = CancellationSignal.fromTransport(cancellation);
@@ -110,7 +108,7 @@
@UiThread
private void onStartCaptureCompleted() {
- mStarted = true;
+ mActive = true;
try {
mRemote.onCaptureStarted();
} catch (RemoteException e) {
@@ -119,13 +117,11 @@
}
}
-
@BinderThread
@Override
public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
Trace.beginSection("requestImage");
- checkConnected();
- checkStarted();
+ checkActive();
ICancellationSignal cancellation = CancellationSignal.createTransport();
mCancellation = CancellationSignal.fromTransport(cancellation);
@@ -152,8 +148,7 @@
@BinderThread
@Override
public ICancellationSignal endCapture() throws RemoteException {
- checkConnected();
- checkStarted();
+ checkActive();
ICancellationSignal cancellation = CancellationSignal.createTransport();
mCancellation = CancellationSignal.fromTransport(cancellation);
@@ -167,64 +162,48 @@
@UiThread
private void onEndCaptureCompleted() {
- synchronized (mLock) {
- mStarted = false;
- try {
+ mActive = false;
+ try {
+ if (mRemote != null) {
mRemote.onCaptureEnded();
- } catch (RemoteException e) {
- Log.w(TAG, "Shutting down due to error: ", e);
- close();
}
+ } catch (RemoteException e) {
+ Log.w(TAG, "Caught exception confirming capture end!", e);
+ } finally {
+ close();
}
}
@BinderThread
@Override
public void close() {
- if (mStarted) {
- Log.w(TAG, "close(): capture is still started?! Ending now.");
-
+ if (mActive) {
+ if (mCancellation != null) {
+ Log.w(TAG, "close(): cancelling pending operation.");
+ mCancellation.cancel();
+ mCancellation = null;
+ }
+ Log.w(TAG, "close(): capture session still active! Ending now.");
// -> UiThread
mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ }));
- mStarted = false;
+ mActive = false;
}
- disconnect();
+ mActive = false;
+ mSession = null;
+ mRemote = null;
+ mLocal = null;
+ mCloseGuard.close();
+ Reference.reachabilityFence(this);
}
- /**
- * Shuts down this client and releases references to dependent objects. No attempt is made
- * to notify the controller, use with caution!
- */
- private void disconnect() {
+ @VisibleForTesting
+ public boolean isActive() {
+ return mActive;
+ }
+
+ private void checkActive() throws RemoteException {
synchronized (mLock) {
- mSession = null;
- mConnected = false;
- mStarted = false;
- mRemote = null;
- mLocal = null;
- mCloseGuard.close();
- }
- }
-
- public boolean isConnected() {
- return mConnected;
- }
-
- public boolean isStarted() {
- return mStarted;
- }
-
- private synchronized void checkConnected() throws RemoteException {
- synchronized (mLock) {
- if (!mConnected) {
- throw new RemoteException(new IllegalStateException("Not connected"));
- }
- }
- }
-
- private void checkStarted() throws RemoteException {
- synchronized (mLock) {
- if (!mStarted) {
+ if (!mActive) {
throw new RemoteException(new IllegalStateException("Not started!"));
}
}
@@ -233,24 +212,16 @@
/** @return a string representation of the state of this client */
public String toString() {
return "ScrollCaptureConnection{"
- + "connected=" + mConnected
- + ", started=" + mStarted
+ + "active=" + mActive
+ ", session=" + mSession
+ ", remote=" + mRemote
+ ", local=" + mLocal
+ "}";
}
- @VisibleForTesting
- public CancellationSignal getCancellation() {
- return mCancellation;
- }
-
protected void finalize() throws Throwable {
try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
+ mCloseGuard.warnIfOpen();
close();
} finally {
super.finalize();
diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java
index 564113e..8808827 100644
--- a/core/java/android/view/ScrollCaptureResponse.java
+++ b/core/java/android/view/ScrollCaptureResponse.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Parcelable;
+import android.os.RemoteException;
import com.android.internal.util.DataClass;
@@ -57,11 +58,22 @@
@DataClass.PluralOf("message")
private ArrayList<String> mMessages = new ArrayList<>();
- /** Whether a connection has been returned. */
+ /** Whether an active connection is present. */
public boolean isConnected() {
- return mConnection != null;
+ return mConnection != null && mConnection.asBinder().isBinderAlive();
}
+ /** Closes a connection returned with this response. */
+ public void close() {
+ if (mConnection != null) {
+ try {
+ mConnection.close();
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ mConnection = null;
+ }
+ }
@@ -367,10 +379,10 @@
}
@DataClass.Generated(
- time = 1612282689462L,
+ time = 1614833185795L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 03dd100..0167147 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -200,7 +200,8 @@
private static native void nativeSyncInputWindows(long transactionObj);
private static native boolean nativeGetDisplayBrightnessSupport(IBinder displayToken);
private static native boolean nativeSetDisplayBrightness(IBinder displayToken,
- float brightness);
+ float sdrBrightness, float sdrBrightnessNits, float displayBrightness,
+ float displayBrightnessNits);
private static native long nativeReadTransactionFromParcel(Parcel in);
private static native void nativeWriteTransactionToParcel(long nativeObject, Parcel out);
private static native void nativeSetShadowRadius(long transactionObj, long nativeObject,
@@ -2405,13 +2406,50 @@
* @hide
*/
public static boolean setDisplayBrightness(IBinder displayToken, float brightness) {
+ return setDisplayBrightness(displayToken, brightness, -1, brightness, -1);
+ }
+
+ /**
+ * Sets the brightness of a display.
+ *
+ * @param displayToken
+ * The token for the display whose brightness is set.
+ * @param sdrBrightness
+ * A number between 0.0f (minimum brightness) and 1.0f (maximum brightness), or -1.0f to
+ * turn the backlight off. Specifies the desired brightness of SDR content.
+ * @param sdrBrightnessNits
+ * The value of sdrBrightness converted to calibrated nits. -1 if this isn't available.
+ * @param displayBrightness
+ * A number between 0.0f (minimum brightness) and 1.0f (maximum brightness), or
+ * -1.0f to turn the backlight off. Specifies the desired brightness of the display itself,
+ * used directly for HDR content.
+ * @param displayBrightnessNits
+ * The value of displayBrightness converted to calibrated nits. -1 if this isn't
+ * available.
+ *
+ * @return Whether the method succeeded or not.
+ *
+ * @throws IllegalArgumentException if:
+ * - displayToken is null;
+ * - brightness is NaN or greater than 1.0f.
+ *
+ * @hide
+ */
+ public static boolean setDisplayBrightness(IBinder displayToken, float sdrBrightness,
+ float sdrBrightnessNits, float displayBrightness, float displayBrightnessNits) {
Objects.requireNonNull(displayToken);
- if (Float.isNaN(brightness) || brightness > 1.0f
- || (brightness < 0.0f && brightness != -1.0f)) {
- throw new IllegalArgumentException("brightness must be a number between 0.0f and 1.0f,"
- + " or -1 to turn the backlight off: " + brightness);
+ if (Float.isNaN(displayBrightness) || displayBrightness > 1.0f
+ || (displayBrightness < 0.0f && displayBrightness != -1.0f)) {
+ throw new IllegalArgumentException("displayBrightness must be a number between 0.0f "
+ + " and 1.0f, or -1 to turn the backlight off: " + displayBrightness);
}
- return nativeSetDisplayBrightness(displayToken, brightness);
+ if (Float.isNaN(sdrBrightness) || sdrBrightness > 1.0f
+ || (sdrBrightness < 0.0f && sdrBrightness != -1.0f)) {
+ throw new IllegalArgumentException("sdrBrightness must be a number between 0.0f "
+ + "and 1.0f, or -1 to turn the backlight off: " + displayBrightness);
+ }
+ return nativeSetDisplayBrightness(displayToken, sdrBrightness, sdrBrightnessNits,
+ displayBrightness, displayBrightnessNits);
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 9688c67..3ffe0c6 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1390,14 +1390,6 @@
viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber);
return;
}
-
- // Otherwise if the if the ViewRoot is not null, use deferred transaction instead.
- if (frameNumber > 0 && viewRoot != null && viewRoot.mSurface.isValid()
- && mSurfaceControl != null) {
- mRtTransaction.deferTransactionUntil(mSurfaceControl,
- viewRoot.getSurfaceControl(), frameNumber);
- }
- mRtTransaction.apply();
}
private Rect mRTLastReportedPosition = new Rect();
@@ -1470,12 +1462,6 @@
*/
synchronized (mSurfaceControlLock) {
final ViewRootImpl viewRoot = getViewRootImpl();
- boolean deferTransaction = frameNumber > 0 && viewRoot != null
- && viewRoot.mSurface.isValid() && !useBLASTSync(viewRoot);
- if (deferTransaction) {
- mRtTransaction.deferTransactionUntil(mSurfaceControl,
- viewRoot.getSurfaceControl(), frameNumber);
- }
mRtTransaction.hide(mSurfaceControl);
if (mRtReleaseSurfaces) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 35726c0..e4fb611 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1100,6 +1100,7 @@
mTempControls);
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
} catch (RemoteException e) {
mAdded = false;
@@ -5356,7 +5357,7 @@
updateLocationInParentDisplay(msg.arg1, msg.arg2);
} break;
case MSG_REQUEST_SCROLL_CAPTURE:
- handleScrollCaptureRequest((IScrollCaptureCallbacks) msg.obj);
+ handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj);
break;
}
}
@@ -7699,6 +7700,7 @@
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
setFrame(mTmpFrames.frame);
mInsetsController.onStateChanged(mTempInsets);
@@ -8158,6 +8160,7 @@
}
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
+ mTranslator.translateSourceControlsInScreenToAppWindow(activeControls);
}
if (insetsState != null && insetsState.getSource(ITYPE_IME).isVisible()) {
ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged",
@@ -9267,10 +9270,10 @@
/**
* Dispatches a scroll capture request to the view hierarchy on the ui thread.
*
- * @param callbacks for replies
+ * @param listener for the response
*/
- public void dispatchScrollCaptureRequest(@NonNull IScrollCaptureCallbacks callbacks) {
- mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, callbacks).sendToTarget();
+ public void dispatchScrollCaptureRequest(@NonNull IScrollCaptureResponseListener listener) {
+ mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget();
}
/**
@@ -9317,10 +9320,10 @@
* A call to {@link IScrollCaptureCallbacks#onScrollCaptureResponse(ScrollCaptureResponse)}
* will follow.
*
- * @param callbacks to receive responses
+ * @param listener to receive responses
* @see ScrollCaptureTargetSelector
*/
- public void handleScrollCaptureRequest(@NonNull IScrollCaptureCallbacks callbacks) {
+ public void handleScrollCaptureRequest(@NonNull IScrollCaptureResponseListener listener) {
ScrollCaptureSearchResults results =
new ScrollCaptureSearchResults(mContext.getMainExecutor());
@@ -9335,7 +9338,7 @@
getChildVisibleRect(rootView, rect, point);
rootView.dispatchScrollCaptureSearch(rect, point, results::addTarget);
}
- Runnable onComplete = () -> dispatchScrollCaptureSearchResult(callbacks, results);
+ Runnable onComplete = () -> dispatchScrollCaptureSearchResponse(listener, results);
results.setOnCompleteListener(onComplete);
if (!results.isComplete()) {
mHandler.postDelayed(results::finish, getScrollCaptureRequestTimeout());
@@ -9343,8 +9346,8 @@
}
/** Called by {@link #handleScrollCaptureRequest} when a result is returned */
- private void dispatchScrollCaptureSearchResult(
- @NonNull IScrollCaptureCallbacks callbacks,
+ private void dispatchScrollCaptureSearchResponse(
+ @NonNull IScrollCaptureResponseListener listener,
@NonNull ScrollCaptureSearchResults results) {
ScrollCaptureTarget selectedTarget = results.getTopResult();
@@ -9361,7 +9364,7 @@
if (selectedTarget == null) {
response.setDescription("No scrollable targets found in window");
try {
- callbacks.onScrollCaptureResponse(response.build());
+ listener.onScrollCaptureResponse(response.build());
} catch (RemoteException e) {
Log.e(TAG, "Failed to send scroll capture search result", e);
}
@@ -9387,11 +9390,11 @@
// Create a connection and return it to the caller
ScrollCaptureConnection connection = new ScrollCaptureConnection(
- mView.getContext().getMainExecutor(), selectedTarget, callbacks);
+ mView.getContext().getMainExecutor(), selectedTarget);
response.setConnection(connection);
try {
- callbacks.onScrollCaptureResponse(response.build());
+ listener.onScrollCaptureResponse(response.build());
} catch (RemoteException e) {
if (DEBUG_SCROLL_CAPTURE) {
Log.w(TAG, "Failed to send scroll capture search response.", e);
@@ -9691,10 +9694,10 @@
}
@Override
- public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
+ public void requestScrollCapture(IScrollCaptureResponseListener listener) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchScrollCaptureRequest(callbacks);
+ viewAncestor.dispatchScrollCaptureRequest(listener);
}
}
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index cf5ec8d..c814e5a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2619,10 +2619,10 @@
/**
* System request to begin scroll capture.
*
- * @param callbacks to receive responses
+ * @param listener to receive the response
* @hide
*/
- public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
+ public void requestScrollCapture(IScrollCaptureResponseListener listener) {
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 818a2b0..04512c9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -81,6 +81,7 @@
import static android.view.WindowLayoutParamsProto.Y;
import android.Manifest.permission;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -121,6 +122,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -864,6 +866,33 @@
}
/**
+ * Adds a listener, which will be called when cross-window blurs are enabled/disabled at
+ * runtime. This affects both window blur behind (see {@link LayoutParams#setBlurBehindRadius})
+ * and window background blur (see {@link Window#setBackgroundBlurRadius}).
+ *
+ * Cross-window blur might not be supported by some devices due to GPU limitations. It can also
+ * be disabled at runtime, e.g. during battery saving mode, when multimedia tunneling is used or
+ * when minimal post processing is requested. In such situations, no blur will be computed or
+ * drawn, so the blur target area will not be blurred. To handle this, the app might want to
+ * change its theme to one that does not use blurs.
+ *
+ * If the listener is added successfully, it will be called immediately with the current
+ * cross-window blur enabled state.
+ *
+ * @param executor {@link Executor} to handle the listener callback
+ * @param listener the listener to be added. It will be called back with a boolean parameter,
+ * which is true if cross-window blur is enabled and false if it is disabled
+ *
+ * @see #removeCrossWindowBlurEnabledListener
+ * @see #isCrossWindowBlurEnabled
+ * @see LayoutParams#setBlurBehindRadius
+ * @see Window#setBackgroundBlurRadius
+ */
+ default void addCrossWindowBlurEnabledListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> listener) {
+ }
+
+ /**
* Removes a listener, previously added with {@link #addCrossWindowBlurEnabledListener}
*
* @param listener the listener to be removed
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index e37522b..8dce852 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiContext;
@@ -40,6 +41,7 @@
import com.android.internal.os.IResultReceiver;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -310,7 +312,13 @@
@Override
public void addCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) {
- CrossWindowBlurListeners.getInstance().addListener(listener);
+ addCrossWindowBlurEnabledListener(mContext.getMainExecutor(), listener);
+ }
+
+ @Override
+ public void addCrossWindowBlurEnabledListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> listener) {
+ CrossWindowBlurListeners.getInstance().addListener(executor, listener);
}
@Override
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index b8893ce..27c637b 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -31,6 +31,7 @@
import android.util.Size;
import android.util.Slog;
import android.view.SurfaceControlViewHost;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.inline.InlineContentView;
@@ -38,6 +39,7 @@
import com.android.internal.util.Parcelling;
import com.android.internal.view.inline.IInlineContentCallback;
import com.android.internal.view.inline.IInlineContentProvider;
+import com.android.internal.view.inline.InlineTooltipUi;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
@@ -75,6 +77,15 @@
private InlineContentCallbackImpl mInlineContentCallback;
/**
+ * Used to show up the inline suggestion tooltip.
+ *
+ * @hide
+ */
+ @Nullable
+ @DataClass.ParcelWith(InlineTooltipUiParceling.class)
+ private InlineTooltipUi mInlineTooltipUi;
+
+ /**
* Creates a new {@link InlineSuggestion}, for testing purpose.
*
* @hide
@@ -82,7 +93,8 @@
@TestApi
@NonNull
public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) {
- return new InlineSuggestion(info, null, /* inlineContentCallback */ null);
+ return new InlineSuggestion(info, null, /* inlineContentCallback */ null,
+ /* inlineTooltipUi */ null);
}
/**
@@ -92,7 +104,7 @@
*/
public InlineSuggestion(@NonNull InlineSuggestionInfo info,
@Nullable IInlineContentProvider contentProvider) {
- this(info, contentProvider, /* inlineContentCallback */ null);
+ this(info, contentProvider, /* inlineContentCallback */ null, /* inlineTooltipUi */ null);
}
/**
@@ -136,9 +148,21 @@
"size is neither between min:" + minSize + " and max:" + maxSize
+ ", nor wrap_content");
}
- mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback);
+
+ InlineSuggestion toolTip = mInfo.getTooltip();
+ if (toolTip != null) {
+ if (mInlineTooltipUi == null) {
+ mInlineTooltipUi = new InlineTooltipUi(context);
+ }
+ } else {
+ mInlineTooltipUi = null;
+ }
+
+ mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback,
+ mInlineTooltipUi);
if (mContentProvider == null) {
callbackExecutor.execute(() -> callback.accept(/* view */ null));
+ mInlineTooltipUi = null;
return;
}
try {
@@ -148,6 +172,13 @@
Slog.w(TAG, "Error creating suggestion content surface: " + e);
callbackExecutor.execute(() -> callback.accept(/* view */ null));
}
+ if (toolTip == null) return;
+
+ final Size tooltipSize = new Size(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ mInfo.getTooltip().inflate(context, tooltipSize, callbackExecutor, view -> {
+ Handler.getMain().post(() -> mInlineTooltipUi.setTooltipView(view));
+ });
}
/**
@@ -162,12 +193,13 @@
}
private synchronized InlineContentCallbackImpl getInlineContentCallback(Context context,
- Executor callbackExecutor, Consumer<InlineContentView> callback) {
+ Executor callbackExecutor, Consumer<InlineContentView> callback,
+ InlineTooltipUi inlineTooltipUi) {
if (mInlineContentCallback != null) {
throw new IllegalStateException("Already called #inflate()");
}
return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor,
- callback);
+ callback, inlineTooltipUi);
}
/**
@@ -267,14 +299,19 @@
@Nullable
private Consumer<SurfaceControlViewHost.SurfacePackage> mSurfacePackageConsumer;
+ @Nullable
+ private InlineTooltipUi mInlineTooltipUi;
+
InlineContentCallbackImpl(@NonNull Context context,
@Nullable IInlineContentProvider inlineContentProvider,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull Consumer<InlineContentView> callback) {
+ @NonNull Consumer<InlineContentView> callback,
+ @Nullable InlineTooltipUi inlineTooltipUi) {
mContext = context;
mInlineContentProvider = inlineContentProvider;
mCallbackExecutor = callbackExecutor;
mCallback = callback;
+ mInlineTooltipUi = inlineTooltipUi;
}
@BinderThread
@@ -305,6 +342,17 @@
mCallbackExecutor.execute(() -> mCallback.accept(/* view */null));
} else {
mView = new InlineContentView(mContext);
+ if (mInlineTooltipUi != null) {
+ mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (mInlineTooltipUi != null) {
+ mInlineTooltipUi.update(mView);
+ }
+ }
+ });
+ }
mView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
mView.setChildSurfacePackageUpdater(getSurfacePackageUpdater());
mCallbackExecutor.execute(() -> mCallback.accept(mView));
@@ -425,10 +473,25 @@
}
}
+ /**
+ * This class used to provide parcelling logic for InlineContentCallbackImpl. It's intended to
+ * make this parcelling a no-op, since it can't be parceled and we don't need to parcel it.
+ */
+ private static class InlineTooltipUiParceling implements
+ Parcelling<InlineTooltipUi> {
+ @Override
+ public void parcel(InlineTooltipUi item, Parcel dest, int parcelFlags) {
+ }
+
+ @Override
+ public InlineTooltipUi unparcel(Parcel source) {
+ return null;
+ }
+ }
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -446,18 +509,22 @@
*
* @param inlineContentCallback
* Used to keep a strong reference to the callback so it doesn't get garbage collected.
+ * @param inlineTooltipUi
+ * Used to show up the inline suggestion tooltip.
* @hide
*/
@DataClass.Generated.Member
public InlineSuggestion(
@NonNull InlineSuggestionInfo info,
@Nullable IInlineContentProvider contentProvider,
- @Nullable InlineContentCallbackImpl inlineContentCallback) {
+ @Nullable InlineContentCallbackImpl inlineContentCallback,
+ @Nullable InlineTooltipUi inlineTooltipUi) {
this.mInfo = info;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mInfo);
this.mContentProvider = contentProvider;
this.mInlineContentCallback = inlineContentCallback;
+ this.mInlineTooltipUi = inlineTooltipUi;
// onConstructed(); // You can define this method to get a callback
}
@@ -485,6 +552,16 @@
return mInlineContentCallback;
}
+ /**
+ * Used to show up the inline suggestion tooltip.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlineTooltipUi getInlineTooltipUi() {
+ return mInlineTooltipUi;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -494,7 +571,8 @@
return "InlineSuggestion { " +
"info = " + mInfo + ", " +
"contentProvider = " + mContentProvider + ", " +
- "inlineContentCallback = " + mInlineContentCallback +
+ "inlineContentCallback = " + mInlineContentCallback + ", " +
+ "inlineTooltipUi = " + mInlineTooltipUi +
" }";
}
@@ -513,7 +591,8 @@
return true
&& java.util.Objects.equals(mInfo, that.mInfo)
&& java.util.Objects.equals(mContentProvider, that.mContentProvider)
- && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback);
+ && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback)
+ && java.util.Objects.equals(mInlineTooltipUi, that.mInlineTooltipUi);
}
@Override
@@ -526,6 +605,7 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mInfo);
_hash = 31 * _hash + java.util.Objects.hashCode(mContentProvider);
_hash = 31 * _hash + java.util.Objects.hashCode(mInlineContentCallback);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipUi);
return _hash;
}
@@ -540,6 +620,17 @@
}
}
+ @DataClass.Generated.Member
+ static Parcelling<InlineTooltipUi> sParcellingForInlineTooltipUi =
+ Parcelling.Cache.get(
+ InlineTooltipUiParceling.class);
+ static {
+ if (sParcellingForInlineTooltipUi == null) {
+ sParcellingForInlineTooltipUi = Parcelling.Cache.put(
+ new InlineTooltipUiParceling());
+ }
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -549,10 +640,12 @@
byte flg = 0;
if (mContentProvider != null) flg |= 0x2;
if (mInlineContentCallback != null) flg |= 0x4;
+ if (mInlineTooltipUi != null) flg |= 0x8;
dest.writeByte(flg);
dest.writeTypedObject(mInfo, flags);
if (mContentProvider != null) dest.writeStrongInterface(mContentProvider);
sParcellingForInlineContentCallback.parcel(mInlineContentCallback, dest, flags);
+ sParcellingForInlineTooltipUi.parcel(mInlineTooltipUi, dest, flags);
}
@Override
@@ -570,12 +663,14 @@
InlineSuggestionInfo info = (InlineSuggestionInfo) in.readTypedObject(InlineSuggestionInfo.CREATOR);
IInlineContentProvider contentProvider = (flg & 0x2) == 0 ? null : IInlineContentProvider.Stub.asInterface(in.readStrongBinder());
InlineContentCallbackImpl inlineContentCallback = sParcellingForInlineContentCallback.unparcel(in);
+ InlineTooltipUi inlineTooltipUi = sParcellingForInlineTooltipUi.unparcel(in);
this.mInfo = info;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mInfo);
this.mContentProvider = contentProvider;
this.mInlineContentCallback = inlineContentCallback;
+ this.mInlineTooltipUi = inlineTooltipUi;
// onConstructed(); // You can define this method to get a callback
}
@@ -595,10 +690,10 @@
};
@DataClass.Generated(
- time = 1589396017700L,
- codegenVersion = "1.0.15",
+ time = 1615562097666L,
+ codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineTooltipUiParceling.class) com.android.internal.view.inline.InlineTooltipUi mInlineTooltipUi\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>,com.android.internal.view.inline.InlineTooltipUi)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 10fd0e0..5798614 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -73,6 +73,11 @@
private final boolean mPinned;
/**
+ * @hide
+ */
+ private final @Nullable InlineSuggestion mTooltip;
+
+ /**
* Creates a new {@link InlineSuggestionInfo}, for testing purpose.
*
* @hide
@@ -84,12 +89,30 @@
@NonNull @Source String source,
@SuppressLint("NullableCollection")
@Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) {
- return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned);
+ return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned,
+ null);
+ }
+
+ /**
+ * Creates a new {@link InlineSuggestionInfo}, for testing purpose.
+ *
+ * @hide
+ */
+ @NonNull
+ public static InlineSuggestionInfo newInlineSuggestionInfo(
+ @NonNull InlinePresentationSpec presentationSpec,
+ @NonNull @Source String source,
+ @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned,
+ @Nullable InlineSuggestion tooltip) {
+ return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned,
+ tooltip);
}
- // Code below generated by codegen v1.0.20.
+
+
+ // Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -141,7 +164,8 @@
@NonNull @Source String source,
@Nullable String[] autofillHints,
@NonNull @Type String type,
- boolean pinned) {
+ boolean pinned,
+ @Nullable InlineSuggestion tooltip) {
this.mInlinePresentationSpec = inlinePresentationSpec;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mInlinePresentationSpec);
@@ -171,6 +195,7 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mType);
this.mPinned = pinned;
+ this.mTooltip = tooltip;
// onConstructed(); // You can define this method to get a callback
}
@@ -215,6 +240,14 @@
return mPinned;
}
+ /**
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlineSuggestion getTooltip() {
+ return mTooltip;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -226,7 +259,8 @@
"source = " + mSource + ", " +
"autofillHints = " + java.util.Arrays.toString(mAutofillHints) + ", " +
"type = " + mType + ", " +
- "pinned = " + mPinned +
+ "pinned = " + mPinned + ", " +
+ "tooltip = " + mTooltip +
" }";
}
@@ -247,7 +281,8 @@
&& java.util.Objects.equals(mSource, that.mSource)
&& java.util.Arrays.equals(mAutofillHints, that.mAutofillHints)
&& java.util.Objects.equals(mType, that.mType)
- && mPinned == that.mPinned;
+ && mPinned == that.mPinned
+ && java.util.Objects.equals(mTooltip, that.mTooltip);
}
@Override
@@ -262,6 +297,7 @@
_hash = 31 * _hash + java.util.Arrays.hashCode(mAutofillHints);
_hash = 31 * _hash + java.util.Objects.hashCode(mType);
_hash = 31 * _hash + Boolean.hashCode(mPinned);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mTooltip);
return _hash;
}
@@ -274,11 +310,13 @@
byte flg = 0;
if (mPinned) flg |= 0x10;
if (mAutofillHints != null) flg |= 0x4;
+ if (mTooltip != null) flg |= 0x20;
dest.writeByte(flg);
dest.writeTypedObject(mInlinePresentationSpec, flags);
dest.writeString(mSource);
if (mAutofillHints != null) dest.writeStringArray(mAutofillHints);
dest.writeString(mType);
+ if (mTooltip != null) dest.writeTypedObject(mTooltip, flags);
}
@Override
@@ -298,6 +336,7 @@
String source = in.readString();
String[] autofillHints = (flg & 0x4) == 0 ? null : in.createStringArray();
String type = in.readString();
+ InlineSuggestion tooltip = (flg & 0x20) == 0 ? null : (InlineSuggestion) in.readTypedObject(InlineSuggestion.CREATOR);
this.mInlinePresentationSpec = inlinePresentationSpec;
com.android.internal.util.AnnotationValidations.validate(
@@ -328,6 +367,7 @@
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mType);
this.mPinned = pinned;
+ this.mTooltip = tooltip;
// onConstructed(); // You can define this method to get a callback
}
@@ -347,10 +387,10 @@
};
@DataClass.Generated(
- time = 1604456249219L,
- codegenVersion = "1.0.20",
+ time = 1614287616672L,
+ codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java",
- inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestion mTooltip\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\npublic static @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean,android.view.inputmethod.InlineSuggestion)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 0ab4e05..e1e1755 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -106,6 +106,27 @@
private int mHostDisplayId;
/**
+ * Specifies the UI specification for the inline suggestion tooltip in the response.
+ */
+ private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
+
+ /**
+ * Whether the IME supports inline suggestions from the default Autofill service that
+ * provides the input view.
+ *
+ * Note: The default value is {@code true}.
+ */
+ private boolean mServiceSupported;
+
+ /**
+ * Whether the IME supports inline suggestions from the application that provides the
+ * input view.
+ *
+ * Note: The default value is {@code true}.
+ */
+ private boolean mClientSupported;
+
+ /**
* @hide
* @see {@link #mHostInputToken}.
*/
@@ -151,6 +172,10 @@
for (int i = 0; i < mInlinePresentationSpecs.size(); i++) {
mInlinePresentationSpecs.get(i).filterContentTypes();
}
+
+ if (mInlineTooltipPresentationSpec != null) {
+ mInlineTooltipPresentationSpec.filterContentTypes();
+ }
}
private static int defaultMaxSuggestionCount() {
@@ -161,6 +186,10 @@
return ActivityThread.currentPackageName();
}
+ private static InlinePresentationSpec defaultInlineTooltipPresentationSpec() {
+ return null;
+ }
+
/**
* The {@link InlineSuggestionsRequest#getSupportedLocales()} now returns empty locale list when
* it's not set, instead of the default system locale.
@@ -191,6 +220,14 @@
return Bundle.EMPTY;
}
+ private static boolean defaultServiceSupported() {
+ return true;
+ }
+
+ private static boolean defaultClientSupported() {
+ return true;
+ }
+
/** @hide */
abstract static class BaseBuilder {
abstract Builder setInlinePresentationSpecs(
@@ -203,6 +240,16 @@
abstract Builder setHostDisplayId(int value);
}
+ /** @hide */
+ public boolean isServiceSupported() {
+ return mServiceSupported;
+ }
+
+ /** @hide */
+ public boolean isClientSupported() {
+ return mClientSupported;
+ }
+
// Code below generated by codegen v1.0.22.
@@ -226,7 +273,10 @@
@NonNull LocaleList supportedLocales,
@NonNull Bundle extras,
@Nullable IBinder hostInputToken,
- int hostDisplayId) {
+ int hostDisplayId,
+ @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
+ boolean serviceSupported,
+ boolean clientSupported) {
this.mMaxSuggestionCount = maxSuggestionCount;
this.mInlinePresentationSpecs = inlinePresentationSpecs;
com.android.internal.util.AnnotationValidations.validate(
@@ -242,6 +292,9 @@
NonNull.class, null, mExtras);
this.mHostInputToken = hostInputToken;
this.mHostDisplayId = hostDisplayId;
+ this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+ this.mServiceSupported = serviceSupported;
+ this.mClientSupported = clientSupported;
onConstructed();
}
@@ -324,6 +377,16 @@
return mHostDisplayId;
}
+ /**
+ * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
+ *
+ * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
+ return mInlineTooltipPresentationSpec;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -337,7 +400,10 @@
"supportedLocales = " + mSupportedLocales + ", " +
"extras = " + mExtras + ", " +
"hostInputToken = " + mHostInputToken + ", " +
- "hostDisplayId = " + mHostDisplayId +
+ "hostDisplayId = " + mHostDisplayId + ", " +
+ "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
+ "serviceSupported = " + mServiceSupported + ", " +
+ "clientSupported = " + mClientSupported +
" }";
}
@@ -360,7 +426,10 @@
&& java.util.Objects.equals(mSupportedLocales, that.mSupportedLocales)
&& extrasEquals(that.mExtras)
&& java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
- && mHostDisplayId == that.mHostDisplayId;
+ && mHostDisplayId == that.mHostDisplayId
+ && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
+ && mServiceSupported == that.mServiceSupported
+ && mClientSupported == that.mClientSupported;
}
@Override
@@ -377,6 +446,9 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mExtras);
_hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
_hash = 31 * _hash + mHostDisplayId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
+ _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
+ _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
return _hash;
}
@@ -386,9 +458,12 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- byte flg = 0;
+ int flg = 0;
+ if (mServiceSupported) flg |= 0x100;
+ if (mClientSupported) flg |= 0x200;
if (mHostInputToken != null) flg |= 0x20;
- dest.writeByte(flg);
+ if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
+ dest.writeInt(flg);
dest.writeInt(mMaxSuggestionCount);
dest.writeParcelableList(mInlinePresentationSpecs, flags);
dest.writeString(mHostPackageName);
@@ -396,6 +471,7 @@
dest.writeBundle(mExtras);
parcelHostInputToken(dest, flags);
dest.writeInt(mHostDisplayId);
+ if (mInlineTooltipPresentationSpec != null) dest.writeTypedObject(mInlineTooltipPresentationSpec, flags);
}
@Override
@@ -409,7 +485,9 @@
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- byte flg = in.readByte();
+ int flg = in.readInt();
+ boolean serviceSupported = (flg & 0x100) != 0;
+ boolean clientSupported = (flg & 0x200) != 0;
int maxSuggestionCount = in.readInt();
List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
@@ -418,6 +496,7 @@
Bundle extras = in.readBundle();
IBinder hostInputToken = unparcelHostInputToken(in);
int hostDisplayId = in.readInt();
+ InlinePresentationSpec inlineTooltipPresentationSpec = (flg & 0x80) == 0 ? null : (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR);
this.mMaxSuggestionCount = maxSuggestionCount;
this.mInlinePresentationSpecs = inlinePresentationSpecs;
@@ -434,6 +513,9 @@
NonNull.class, null, mExtras);
this.mHostInputToken = hostInputToken;
this.mHostDisplayId = hostDisplayId;
+ this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
+ this.mServiceSupported = serviceSupported;
+ this.mClientSupported = clientSupported;
onConstructed();
}
@@ -466,6 +548,9 @@
private @NonNull Bundle mExtras;
private @Nullable IBinder mHostInputToken;
private int mHostDisplayId;
+ private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
+ private boolean mServiceSupported;
+ private boolean mClientSupported;
private long mBuilderFieldsSet = 0L;
@@ -597,10 +682,51 @@
return this;
}
+ /**
+ * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
+ *
+ * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mInlineTooltipPresentationSpec = value;
+ return this;
+ }
+
+ /**
+ * Whether the IME supports inline suggestions from the default Autofill service that
+ * provides the input view.
+ *
+ * Note: The default value is {@code true}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setServiceSupported(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100;
+ mServiceSupported = value;
+ return this;
+ }
+
+ /**
+ * Whether the IME supports inline suggestions from the application that provides the
+ * input view.
+ *
+ * Note: The default value is {@code true}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setClientSupported(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x200;
+ mClientSupported = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull InlineSuggestionsRequest build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
+ mBuilderFieldsSet |= 0x400; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -620,6 +746,15 @@
if ((mBuilderFieldsSet & 0x40) == 0) {
mHostDisplayId = defaultHostDisplayId();
}
+ if ((mBuilderFieldsSet & 0x80) == 0) {
+ mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
+ }
+ if ((mBuilderFieldsSet & 0x100) == 0) {
+ mServiceSupported = defaultServiceSupported();
+ }
+ if ((mBuilderFieldsSet & 0x200) == 0) {
+ mClientSupported = defaultClientSupported();
+ }
InlineSuggestionsRequest o = new InlineSuggestionsRequest(
mMaxSuggestionCount,
mInlinePresentationSpecs,
@@ -627,12 +762,15 @@
mSupportedLocales,
mExtras,
mHostInputToken,
- mHostDisplayId);
+ mHostDisplayId,
+ mInlineTooltipPresentationSpec,
+ mServiceSupported,
+ mClientSupported);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
+ if ((mBuilderFieldsSet & 0x400) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -640,10 +778,10 @@
}
@DataClass.Generated(
- time = 1612206506050L,
+ time = 1615798784918L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
- inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate boolean mServiceSupported\nprivate boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static boolean defaultServiceSupported()\nprivate static boolean defaultClientSupported()\npublic boolean isServiceSupported()\npublic boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 6ade5e6..de4554b 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -105,7 +105,7 @@
*/
@MainThread
default void initializeInternal(IBinder token, int displayId,
- IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
+ IInputMethodPrivilegedOperations privilegedOperations) {
updateInputMethodDisplay(displayId);
attachToken(token);
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 25712f8..cc533eb 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -18,23 +18,19 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
-import android.inputmethodservice.InputMethodService;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -64,7 +60,7 @@
* @attr ref android.R.styleable#InputMethod_isDefault
* @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod
* @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions
- * @attr ref android.R.styleable#InputMethod_configChanges
+ * @attr ref android.R.styleable#InputMethod_suppressesSpellChecker
*/
public final class InputMethodInfo implements Parcelable {
static final String TAG = "InputMethodInfo";
@@ -123,10 +119,9 @@
private final boolean mInlineSuggestionsEnabled;
/**
- * The flag for configurations IME assumes the responsibility for handling in
- * {@link InputMethodService#onConfigurationChanged(Configuration)}}.
+ * The flag whether this IME suppresses spell checker.
*/
- private final int mHandledConfigChanges;
+ private final boolean mSuppressesSpellChecker;
/**
* @param service the {@link ResolveInfo} corresponds in which the IME is implemented.
@@ -171,6 +166,7 @@
boolean isAuxIme = true;
boolean supportsSwitchingToNextInputMethod = false; // false as default
boolean inlineSuggestionsEnabled = false; // false as default
+ boolean suppressesSpellChecker = false; // false as default
mForceDefault = false;
PackageManager pm = context.getPackageManager();
@@ -214,8 +210,8 @@
false);
inlineSuggestionsEnabled = sa.getBoolean(
com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false);
- mHandledConfigChanges = sa.getInt(
- com.android.internal.R.styleable.InputMethod_configChanges, 0);
+ suppressesSpellChecker = sa.getBoolean(
+ com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false);
sa.recycle();
final int depth = parser.getDepth();
@@ -287,6 +283,7 @@
mIsAuxIme = isAuxIme;
mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
mInlineSuggestionsEnabled = inlineSuggestionsEnabled;
+ mSuppressesSpellChecker = suppressesSpellChecker;
mIsVrOnly = isVrOnly;
}
@@ -297,10 +294,10 @@
mIsAuxIme = source.readInt() == 1;
mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
mInlineSuggestionsEnabled = source.readInt() == 1;
+ mSuppressesSpellChecker = source.readBoolean();
mIsVrOnly = source.readBoolean();
mService = ResolveInfo.CREATOR.createFromParcel(source);
mSubtypes = new InputMethodSubtypeArray(source);
- mHandledConfigChanges = source.readInt();
mForceDefault = false;
}
@@ -312,22 +309,7 @@
this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
- false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
- 0 /* handledConfigChanges */);
- }
-
- /**
- * Temporary API for creating a built-in input method for test.
- * @hide
- */
- @TestApi
- public InputMethodInfo(@NonNull String packageName, @NonNull String className,
- @NonNull CharSequence label, @NonNull String settingsActivity,
- int handledConfigChanges) {
- this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
- settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
- false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
- false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges);
+ false /* inlineSuggestionsEnabled */, false /* isVrOnly */);
}
/**
@@ -339,7 +321,7 @@
boolean forceDefault) {
this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
- false /* isVrOnly */, 0 /* handledconfigChanges */);
+ false /* isVrOnly */);
}
/**
@@ -350,8 +332,7 @@
List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) {
this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
- supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
- 0 /* handledConfigChanges */);
+ supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly);
}
/**
@@ -361,7 +342,7 @@
public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
- boolean isVrOnly, int handledConfigChanges) {
+ boolean isVrOnly) {
final ServiceInfo si = ri.serviceInfo;
mService = ri;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -372,8 +353,8 @@
mForceDefault = forceDefault;
mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
mInlineSuggestionsEnabled = inlineSuggestionsEnabled;
+ mSuppressesSpellChecker = false;
mIsVrOnly = isVrOnly;
- mHandledConfigChanges = handledConfigChanges;
}
private static ResolveInfo buildFakeResolveInfo(String packageName, String className,
@@ -520,23 +501,13 @@
}
}
- /**
- * Returns the bit mask of kinds of configuration changes that this IME
- * can handle itself (without being restarted by the system).
- *
- * @attr ref android.R.styleable#InputMethod_configChanges
- */
- @ActivityInfo.Config
- public int getConfigChanges() {
- return mHandledConfigChanges;
- }
-
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName
+ " mIsVrOnly=" + mIsVrOnly
+ " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
- + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled);
+ + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled
+ + " mSuppressesSpellChecker=" + mSuppressesSpellChecker);
pw.println(prefix + "mIsDefaultResId=0x"
+ Integer.toHexString(mIsDefaultResId));
pw.println(prefix + "Service:");
@@ -605,6 +576,13 @@
}
/**
+ * Return {@code true} if this input method suppresses spell checker.
+ */
+ public boolean suppressesSpellChecker() {
+ return mSuppressesSpellChecker;
+ }
+
+ /**
* Used to package this object into a {@link Parcel}.
*
* @param dest The {@link Parcel} to be written.
@@ -618,10 +596,10 @@
dest.writeInt(mIsAuxIme ? 1 : 0);
dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
dest.writeInt(mInlineSuggestionsEnabled ? 1 : 0);
+ dest.writeBoolean(mSuppressesSpellChecker);
dest.writeBoolean(mIsVrOnly);
mService.writeToParcel(dest, flags);
mSubtypes.writeToParcel(dest);
- dest.writeInt(mHandledConfigChanges);
}
/**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 53bbc0a..9872dc0 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -444,6 +444,13 @@
*/
private Matrix mActivityViewToScreenMatrix = null;
+ /**
+ * As reported by {@link InputBindResult}. This value is determined by
+ * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+ */
+ @GuardedBy("mH")
+ private boolean mIsInputMethodSuppressingSpellChecker = false;
+
// -----------------------------------------------------------
/**
@@ -455,11 +462,25 @@
*/
@UnsupportedAppUsage
String mCurId;
+
/**
- * The actual instance of the method to make calls on it.
+ * Kept for {@link UnsupportedAppUsage}. Not officially maintained.
+ *
+ * @deprecated New code should use {@link #mCurrentInputMethodSession}.
*/
+ @Deprecated
+ @GuardedBy("mH")
+ @Nullable
@UnsupportedAppUsage
IInputMethodSession mCurMethod;
+
+ /**
+ * Encapsulates IPCs to the currently connected InputMethodService.
+ */
+ @Nullable
+ @GuardedBy("mH")
+ private InputMethodSessionWrapper mCurrentInputMethodSession = null;
+
InputChannel mCurChannel;
ImeInputEventSender mCurSender;
@@ -616,11 +637,8 @@
public void finishInputAndReportToIme() {
synchronized (mH) {
finishInputLocked();
- if (mCurMethod != null) {
- try {
- mCurMethod.finishInput();
- } catch (RemoteException e) {
- }
+ if (mCurrentInputMethodSession != null) {
+ mCurrentInputMethodSession.finishInput();
}
}
}
@@ -747,7 +765,8 @@
@Override
public boolean hasActiveConnection(View view) {
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view) || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view)
+ || mCurrentInputMethodSession == null) {
return false;
}
@@ -854,10 +873,14 @@
REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
setInputChannelLocked(res.channel);
- mCurMethod = res.method;
+ mCurMethod = res.method; // for @UnsupportedAppUsage
+ mCurrentInputMethodSession =
+ InputMethodSessionWrapper.createOrNull(res.method);
mCurId = res.id;
mBindSequence = res.sequence;
mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix();
+ mIsInputMethodSuppressingSpellChecker =
+ res.isInputMethodSuppressingSpellChecker;
}
startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
return;
@@ -995,7 +1018,7 @@
}
mActivityViewToScreenMatrix.setValues(matrixValues);
- if (mCursorAnchorInfo == null || mCurMethod == null
+ if (mCursorAnchorInfo == null || mCurrentInputMethodSession == null
|| mServedInputConnectionWrapper == null) {
return;
}
@@ -1006,13 +1029,9 @@
}
// Since the host ActivityView is moved, we need to issue
// IMS#updateCursorAnchorInfo() again.
- try {
- mCurMethod.updateCursorAnchorInfo(
- CursorAnchorInfo.createForAdditionalParentMatrix(
- mCursorAnchorInfo, mActivityViewToScreenMatrix));
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
- }
+ mCurrentInputMethodSession.updateCursorAnchorInfo(
+ CursorAnchorInfo.createForAdditionalParentMatrix(
+ mCursorAnchorInfo, mActivityViewToScreenMatrix));
}
return;
}
@@ -1299,7 +1318,9 @@
* @return {@link List} of {@link InputMethodInfo}.
* @hide
*/
+ @TestApi
@RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+ @NonNull
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
try {
final Completable.InputMethodInfoList value = Completable.createInputMethodInfoList();
@@ -1470,6 +1491,15 @@
}
/**
+ * Return {@code true} if the input method is suppressing system spell checker.
+ */
+ public boolean isInputMethodSuppressingSpellChecker() {
+ synchronized (mH) {
+ return mIsInputMethodSuppressingSpellChecker;
+ }
+ }
+
+ /**
* Reset all of the state associated with being bound to an input method.
*/
void clearBindingLocked() {
@@ -1478,7 +1508,8 @@
setInputChannelLocked(null);
mBindSequence = -1;
mCurId = null;
- mCurMethod = null;
+ mCurMethod = null; // for @UnsupportedAppUsage
+ mCurrentInputMethodSession = null;
}
void setInputChannelLocked(InputChannel channel) {
@@ -1513,6 +1544,7 @@
@UnsupportedAppUsage
void finishInputLocked() {
mActivityViewToScreenMatrix = null;
+ mIsInputMethodSuppressingSpellChecker = false;
setNextServedViewLocked(null);
if (getServedViewLocked() != null) {
if (DEBUG) {
@@ -1541,11 +1573,8 @@
}
mCompletions = completions;
- if (mCurMethod != null) {
- try {
- mCurMethod.displayCompletions(mCompletions);
- } catch (RemoteException e) {
- }
+ if (mCurrentInputMethodSession != null) {
+ mCurrentInputMethodSession.displayCompletions(mCompletions);
}
}
}
@@ -1564,11 +1593,8 @@
return;
}
- if (mCurMethod != null) {
- try {
- mCurMethod.updateExtractedText(token, text);
- } catch (RemoteException e) {
- }
+ if (mCurrentInputMethodSession != null) {
+ mCurrentInputMethodSession.updateExtractedText(token, text);
}
}
}
@@ -1829,11 +1855,8 @@
if (servedView == null || servedView.getWindowToken() != windowToken) {
return;
}
- if (mCurMethod != null) {
- try {
- mCurMethod.toggleSoftInput(showFlags, hideFlags);
- } catch (RemoteException e) {
- }
+ if (mCurrentInputMethodSession != null) {
+ mCurrentInputMethodSession.toggleSoftInput(showFlags, hideFlags);
}
}
}
@@ -1854,11 +1877,8 @@
ImeTracing.getInstance().triggerClientDump(
"InputMethodManager#toggleSoftInput", InputMethodManager.this,
null /* icProto */);
- if (mCurMethod != null) {
- try {
- mCurMethod.toggleSoftInput(showFlags, hideFlags);
- } catch (RemoteException e) {
- }
+ if (mCurrentInputMethodSession != null) {
+ mCurrentInputMethodSession.toggleSoftInput(showFlags, hideFlags);
}
}
@@ -2037,10 +2057,12 @@
return false;
}
mActivityViewToScreenMatrix = res.getActivityViewToScreenMatrix();
+ mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
if (res.id != null) {
setInputChannelLocked(res.channel);
mBindSequence = res.sequence;
- mCurMethod = res.method;
+ mCurMethod = res.method; // for @UnsupportedAppUsage
+ mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method);
mCurId = res.id;
} else if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
@@ -2050,11 +2072,8 @@
mRestartOnNextWindowFocus = true;
break;
}
- if (mCurMethod != null && mCompletions != null) {
- try {
- mCurMethod.displayCompletions(mCompletions);
- } catch (RemoteException e) {
- }
+ if (mCurrentInputMethodSession != null && mCompletions != null) {
+ mCurrentInputMethodSession.displayCompletions(mCompletions);
}
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
@@ -2212,12 +2231,9 @@
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
synchronized (mH) {
- try {
- if (mCurMethod != null && mCurRootView != null
- && mCurRootView.getWindowToken() == windowToken) {
- mCurMethod.notifyImeHidden();
- }
- } catch (RemoteException re) {
+ if (mCurrentInputMethodSession != null && mCurRootView != null
+ && mCurRootView.getWindowToken() == windowToken) {
+ mCurrentInputMethodSession.notifyImeHidden();
}
}
}
@@ -2262,7 +2278,7 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
- || mCurMethod == null) {
+ || mCurrentInputMethodSession == null) {
return;
}
@@ -2271,22 +2287,20 @@
|| mCursorCandEnd != candidatesEnd) {
if (DEBUG) Log.d(TAG, "updateSelection");
- try {
- if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod);
- final int oldSelStart = mCursorSelStart;
- final int oldSelEnd = mCursorSelEnd;
- // Update internal values before sending updateSelection to the IME, because
- // if it changes the text within its onUpdateSelection handler in a way that
- // does not move the cursor we don't want to call it again with the same values.
- mCursorSelStart = selStart;
- mCursorSelEnd = selEnd;
- mCursorCandStart = candidatesStart;
- mCursorCandEnd = candidatesEnd;
- mCurMethod.updateSelection(oldSelStart, oldSelEnd,
- selStart, selEnd, candidatesStart, candidatesEnd);
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
+ if (DEBUG) {
+ Log.v(TAG, "SELECTION CHANGE: " + mCurrentInputMethodSession);
}
+ final int oldSelStart = mCursorSelStart;
+ final int oldSelEnd = mCursorSelEnd;
+ // Update internal values before sending updateSelection to the IME, because
+ // if it changes the text within its onUpdateSelection handler in a way that
+ // does not move the cursor we don't want to call it again with the same values.
+ mCursorSelStart = selStart;
+ mCursorSelEnd = selEnd;
+ mCursorCandStart = candidatesStart;
+ mCursorCandEnd = candidatesEnd;
+ mCurrentInputMethodSession.updateSelection(
+ oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
}
}
}
@@ -2320,15 +2334,11 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
- || mCurMethod == null) {
+ || mCurrentInputMethodSession == null) {
return;
}
- try {
- if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
- mCurMethod.viewClicked(focusChanged);
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
- }
+ if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
+ mCurrentInputMethodSession.viewClicked(focusChanged);
}
}
@@ -2389,21 +2399,16 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
- || mCurMethod == null) {
+ || mCurrentInputMethodSession == null) {
return;
}
mTmpCursorRect.set(left, top, right, bottom);
if (!mCursorRect.equals(mTmpCursorRect)) {
- if (DEBUG) Log.d(TAG, "updateCursor");
+ if (DEBUG) Log.d(TAG, "updateCursor: " + mCurrentInputMethodSession);
- try {
- if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
- mCurMethod.updateCursor(mTmpCursorRect);
- mCursorRect.set(mTmpCursorRect);
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
- }
+ mCurrentInputMethodSession.updateCursor(mTmpCursorRect);
+ mCursorRect.set(mTmpCursorRect);
}
}
}
@@ -2426,7 +2431,7 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
- || mCurMethod == null) {
+ || mCurrentInputMethodSession == null) {
return;
}
// If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
@@ -2443,21 +2448,16 @@
return;
}
if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo);
- try {
- if (mActivityViewToScreenMatrix != null) {
- mCurMethod.updateCursorAnchorInfo(
- CursorAnchorInfo.createForAdditionalParentMatrix(
- cursorAnchorInfo, mActivityViewToScreenMatrix));
- } else {
- mCurMethod.updateCursorAnchorInfo(cursorAnchorInfo);
- }
- mCursorAnchorInfo = cursorAnchorInfo;
- // Clear immediate bit (if any).
- mRequestUpdateCursorAnchorInfoMonitorMode &=
- ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
+ if (mActivityViewToScreenMatrix != null) {
+ mCurrentInputMethodSession.updateCursorAnchorInfo(
+ CursorAnchorInfo.createForAdditionalParentMatrix(
+ cursorAnchorInfo, mActivityViewToScreenMatrix));
+ } else {
+ mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
}
+ mCursorAnchorInfo = cursorAnchorInfo;
+ // Clear immediate bit (if any).
+ mRequestUpdateCursorAnchorInfoMonitorMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
}
}
@@ -2483,15 +2483,11 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
- || mCurMethod == null) {
+ || mCurrentInputMethodSession == null) {
return;
}
- try {
- if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
- mCurMethod.appPrivateCommand(action, data);
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
- }
+ if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
+ mCurrentInputMethodSession.appPrivateCommand(action, data);
}
}
@@ -2649,7 +2645,7 @@
public int dispatchInputEvent(InputEvent event, Object token,
FinishedInputEventCallback callback, Handler handler) {
synchronized (mH) {
- if (mCurMethod != null) {
+ if (mCurrentInputMethodSession != null) {
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent)event;
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
@@ -2660,7 +2656,9 @@
}
}
- if (DEBUG) Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurMethod);
+ if (DEBUG) {
+ Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurrentInputMethodSession);
+ }
PendingEvent p = obtainPendingEventLocked(
event, token, mCurId, callback, handler);
@@ -2750,8 +2748,7 @@
return DISPATCH_IN_PROGRESS;
}
- Log.w(TAG, "Unable to send input event to IME: "
- + mCurId + " dropping: " + event);
+ Log.w(TAG, "Unable to send input event to IME: " + mCurId + " dropping: " + event);
}
return DISPATCH_NOT_HANDLED;
}
@@ -3208,7 +3205,11 @@
+ " mBindSequence=" + mBindSequence
+ " mCurId=" + mCurId);
p.println(" mFullscreenMode=" + mFullscreenMode);
- p.println(" mCurMethod=" + mCurMethod);
+ if (mCurrentInputMethodSession != null) {
+ p.println(" mCurMethod=" + mCurrentInputMethodSession);
+ } else {
+ p.println(" mCurMethod= null");
+ }
p.println(" mCurRootView=" + mCurRootView);
p.println(" mServedView=" + getServedViewLocked());
p.println(" mNextServedView=" + getNextServedViewLocked());
@@ -3324,7 +3325,7 @@
*/
@GuardedBy("mH")
public void dumpDebug(ProtoOutputStream proto, ProtoOutputStream icProto) {
- if (mCurMethod == null) {
+ if (mCurrentInputMethodSession == null) {
return;
}
diff --git a/core/java/android/view/inputmethod/InputMethodSessionWrapper.java b/core/java/android/view/inputmethod/InputMethodSessionWrapper.java
new file mode 100644
index 0000000..c4a3773
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodSessionWrapper.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.view.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.view.IInputMethodSession;
+
+/**
+ * This class wrap the {@link IInputMethodSession} object from {@link InputMethodManager}.
+ * Using current {@link IInputMethodSession} object to communicate with
+ * {@link android.inputmethodservice.InputMethodService}.
+ */
+final class InputMethodSessionWrapper {
+
+ private static final String TAG = "InputMethodSessionWrapper";
+
+ /**
+ * The actual instance of the method to make calls on it.
+ */
+ @NonNull
+ private final IInputMethodSession mSession;
+
+ private InputMethodSessionWrapper(@NonNull IInputMethodSession inputMethodSession) {
+ mSession = inputMethodSession;
+ }
+
+ /**
+ * Create a {@link InputMethodSessionWrapper} instance if applicability.
+ *
+ * @param inputMethodSession {@link IInputMethodSession} object to be wrapped.
+ * @return an instance of {@link InputMethodSessionWrapper} if {@code inputMethodSession} is not
+ * {@code null}. {@code null} otherwise.
+ */
+ @Nullable
+ public static InputMethodSessionWrapper createOrNull(
+ @NonNull IInputMethodSession inputMethodSession) {
+ return inputMethodSession != null ? new InputMethodSessionWrapper(inputMethodSession)
+ : null;
+ }
+
+ @AnyThread
+ void finishInput() {
+ try {
+ mSession.finishInput();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
+ try {
+ mSession.updateCursorAnchorInfo(cursorAnchorInfo);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void displayCompletions(CompletionInfo[] completions) {
+ try {
+ mSession.displayCompletions(completions);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void updateExtractedText(int token, ExtractedText text) {
+ try {
+ mSession.updateExtractedText(token, text);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void toggleSoftInput(int showFlags, int hideFlags) {
+ try {
+ mSession.toggleSoftInput(showFlags, hideFlags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void appPrivateCommand(String action, Bundle data) {
+ try {
+ mSession.appPrivateCommand(action, data);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void notifyImeHidden() {
+ try {
+ mSession.notifyImeHidden();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void viewClicked(boolean focusChanged) {
+ try {
+ mSession.viewClicked(focusChanged);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void updateCursor(Rect newCursor) {
+ try {
+ mSession.updateCursor(newCursor);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ @AnyThread
+ void updateSelection(int oldSelStart, int oldSelEnd, int selStart, int selEnd,
+ int candidatesStart, int candidatesEnd) {
+ try {
+ mSession.updateSelection(
+ oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died", e);
+ }
+ }
+
+ /**
+ * @return {@link IInputMethodSession#toString()} as a debug string.
+ */
+ @AnyThread
+ @NonNull
+ @Override
+ public String toString() {
+ return mSession.toString();
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 6d5077a..ef50045 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -145,7 +145,7 @@
@StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
- WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN})
+ WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_CLIPBOARD, WIDGET_TYPE_UNKNOWN })
@interface WidgetType {}
/** The widget involved in the text classification context is a standard
@@ -172,6 +172,8 @@
String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
/** The widget involved in the text classification context is a notification */
String WIDGET_TYPE_NOTIFICATION = "notification";
+ /** The text classification context is for use with the system clipboard. */
+ String WIDGET_TYPE_CLIPBOARD = "clipboard";
/** The widget involved in the text classification context is of an unknown/unspecified type. */
String WIDGET_TYPE_UNKNOWN = "unknown";
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 35d8445..a449cf1 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -16,6 +16,8 @@
package android.view.textservice;
+import android.annotation.BinderThread;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
@@ -25,7 +27,9 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.textservice.ISpellCheckerSession;
import com.android.internal.textservice.ISpellCheckerSessionListener;
import com.android.internal.textservice.ITextServicesSessionListener;
@@ -34,6 +38,7 @@
import java.util.LinkedList;
import java.util.Queue;
+import java.util.concurrent.Executor;
/**
* The SpellCheckerSession interface provides the per client functionality of SpellCheckerService.
@@ -101,38 +106,26 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private final SpellCheckerSessionListener mSpellCheckerSessionListener;
private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
+ private final Executor mExecutor;
private final CloseGuard mGuard = CloseGuard.get();
- /** Handler that will execute the main tasks */
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ON_GET_SUGGESTION_MULTIPLE:
- handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
- break;
- case MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE:
- handleOnGetSentenceSuggestionsMultiple((SentenceSuggestionsInfo[]) msg.obj);
- break;
- }
- }
- };
-
/**
* Constructor
* @hide
*/
public SpellCheckerSession(
- SpellCheckerInfo info, TextServicesManager tsm, SpellCheckerSessionListener listener) {
+ SpellCheckerInfo info, TextServicesManager tsm, SpellCheckerSessionListener listener,
+ Executor executor) {
if (info == null || listener == null || tsm == null) {
throw new NullPointerException();
}
mSpellCheckerInfo = info;
- mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
+ mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(this);
mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
mTextServicesManager = tsm;
mSpellCheckerSessionListener = listener;
+ mExecutor = executor;
mGuard.open("finishSession");
}
@@ -176,6 +169,11 @@
* @param suggestionsLimit the maximum number of suggestions that will be returned
*/
public void getSentenceSuggestions(TextInfo[] textInfos, int suggestionsLimit) {
+ final InputMethodManager imm = mTextServicesManager.getInputMethodManager();
+ if (imm != null && imm.isInputMethodSuppressingSpellChecker()) {
+ handleOnGetSentenceSuggestionsMultiple(new SentenceSuggestionsInfo[0]);
+ return;
+ }
mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple(
textInfos, suggestionsLimit);
}
@@ -204,16 +202,22 @@
if (DBG) {
Log.w(TAG, "getSuggestions from " + mSpellCheckerInfo.getId());
}
+ final InputMethodManager imm = mTextServicesManager.getInputMethodManager();
+ if (imm != null && imm.isInputMethodSuppressingSpellChecker()) {
+ handleOnGetSuggestionsMultiple(new SuggestionsInfo[0]);
+ return;
+ }
mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
textInfos, suggestionsLimit, sequentialWords);
}
- private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) {
- mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
+ void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionsInfos) {
+ mExecutor.execute(() -> mSpellCheckerSessionListener.onGetSuggestions(suggestionsInfos));
}
- private void handleOnGetSentenceSuggestionsMultiple(SentenceSuggestionsInfo[] suggestionInfos) {
- mSpellCheckerSessionListener.onGetSentenceSuggestions(suggestionInfos);
+ void handleOnGetSentenceSuggestionsMultiple(SentenceSuggestionsInfo[] suggestionsInfos) {
+ mExecutor.execute(() ->
+ mSpellCheckerSessionListener.onGetSentenceSuggestions(suggestionsInfos));
}
private static final class SpellCheckerSessionListenerImpl
@@ -238,7 +242,8 @@
}
private final Queue<SpellCheckerParams> mPendingTasks = new LinkedList<>();
- private Handler mHandler;
+ @GuardedBy("SpellCheckerSessionListenerImpl.this")
+ private SpellCheckerSession mSpellCheckerSession;
private static final int STATE_WAIT_CONNECTION = 0;
private static final int STATE_CONNECTED = 1;
@@ -259,8 +264,8 @@
private HandlerThread mThread;
private Handler mAsyncHandler;
- public SpellCheckerSessionListenerImpl(Handler handler) {
- mHandler = handler;
+ SpellCheckerSessionListenerImpl(SpellCheckerSession spellCheckerSession) {
+ mSpellCheckerSession = spellCheckerSession;
}
private static class SpellCheckerParams {
@@ -338,6 +343,7 @@
}
}
+ @GuardedBy("SpellCheckerSessionListenerImpl.this")
private void processCloseLocked() {
if (DBG) Log.d(TAG, "entering processCloseLocked:"
+ " session" + (mISpellCheckerSession != null ? ".hashCode()=#"
@@ -347,7 +353,7 @@
if (mThread != null) {
mThread.quit();
}
- mHandler = null;
+ mSpellCheckerSession = null;
mPendingTasks.clear();
mThread = null;
mAsyncHandler = null;
@@ -491,23 +497,30 @@
processTask(session, scp, false);
}
+ @BinderThread
@Override
public void onGetSuggestions(SuggestionsInfo[] results) {
- synchronized (this) {
- if (mHandler != null) {
- mHandler.sendMessage(Message.obtain(mHandler,
- MSG_ON_GET_SUGGESTION_MULTIPLE, results));
- }
+ SpellCheckerSession session = getSpellCheckerSession();
+ if (session != null) {
+ // Lock should not be held when calling callback, in order to avoid deadlock.
+ session.handleOnGetSuggestionsMultiple(results);
}
}
+ @BinderThread
@Override
public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
- synchronized (this) {
- if (mHandler != null) {
- mHandler.sendMessage(Message.obtain(mHandler,
- MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
- }
+ SpellCheckerSession session = getSpellCheckerSession();
+ if (session != null) {
+ // Lock should not be held when calling callback, in order to avoid deadlock.
+ session.handleOnGetSentenceSuggestionsMultiple(results);
+ }
+ }
+
+ @Nullable
+ private SpellCheckerSession getSpellCheckerSession() {
+ synchronized (SpellCheckerSessionListenerImpl.this) {
+ return mSpellCheckerSession;
}
}
}
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 996757d..bf91cca 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -16,6 +16,7 @@
package android.view.textservice;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -25,19 +26,25 @@
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
import com.android.internal.textservice.ISpellCheckerSessionListener;
import com.android.internal.textservice.ITextServicesManager;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* System API to the overall text services, which arbitrates interaction between applications
@@ -88,10 +95,15 @@
@UserIdInt
private final int mUserId;
- private TextServicesManager(@UserIdInt int userId) throws ServiceNotFoundException {
+ @Nullable
+ private final InputMethodManager mInputMethodManager;
+
+ private TextServicesManager(@UserIdInt int userId,
+ @Nullable InputMethodManager inputMethodManager) throws ServiceNotFoundException {
mService = ITextServicesManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
mUserId = userId;
+ mInputMethodManager = inputMethodManager;
}
/**
@@ -105,7 +117,8 @@
@NonNull
public static TextServicesManager createInstance(@NonNull Context context)
throws ServiceNotFoundException {
- return new TextServicesManager(context.getUserId());
+ return new TextServicesManager(context.getUserId(), context.getSystemService(
+ InputMethodManager.class));
}
/**
@@ -118,7 +131,7 @@
synchronized (TextServicesManager.class) {
if (sInstance == null) {
try {
- sInstance = new TextServicesManager(UserHandle.myUserId());
+ sInstance = new TextServicesManager(UserHandle.myUserId(), null);
} catch (ServiceNotFoundException e) {
throw new IllegalStateException(e);
}
@@ -127,6 +140,12 @@
}
}
+ /** @hide */
+ @Nullable
+ public InputMethodManager getInputMethodManager() {
+ return mInputMethodManager;
+ }
+
/**
* Returns the language component of a given locale string.
*/
@@ -147,10 +166,12 @@
* {@link SuggestionsInfo#RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS} will be passed to the spell
* checker as supported attributes.
*
- * @see #newSpellCheckerSession(Bundle, Locale, SpellCheckerSessionListener, boolean, int)
+ * @see #newSpellCheckerSession(Locale, boolean, int, Bundle, Executor,
+ * SpellCheckerSessionListener)
* @param bundle A bundle to pass to the spell checker.
* @param locale The locale for the spell checker.
* @param listener A spell checker session lister for getting results from the spell checker.
+ * The listener will be called on the calling thread.
* @param referToSpellCheckerLanguageSettings If true, the session for one of enabled
* languages in settings will be used.
* @return A spell checker session from the spell checker.
@@ -160,10 +181,15 @@
@Nullable Locale locale,
@NonNull SpellCheckerSessionListener listener,
boolean referToSpellCheckerLanguageSettings) {
- return newSpellCheckerSession(bundle, locale, listener, referToSpellCheckerLanguageSettings,
- SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
- | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
- | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS);
+ // Attributes existed before {@link #newSpellCheckerSession(Locale, boolean, int, Bundle,
+ // Executor, SpellCheckerSessionListener)} was introduced.
+ int supportedAttributes = SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
+ | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
+ | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS;
+ // Using the implicit looper to preserve the old behavior.
+ Executor executor = new HandlerExecutor(new Handler());
+ return newSpellCheckerSession(locale, referToSpellCheckerLanguageSettings,
+ supportedAttributes, bundle, executor, listener);
}
/**
@@ -177,25 +203,28 @@
* language only (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
* selected.
*
- * @param bundle A bundle to pass to the spell checker.
* @param locale The locale for the spell checker.
- * @param listener A spell checker session lister for getting results from a spell checker.
* @param referToSpellCheckerLanguageSettings If true, the session for one of enabled
* languages in settings will be used.
* @param supportedAttributes A union of {@link SuggestionsInfo} attributes that the spell
* checker can set in the spell checking results.
+ * @param bundle A bundle for passing implementation-specific extra parameters for the spell
+ * checker. You can check the current spell checker package by
+ * {@link #getCurrentSpellCheckerInfo()}.
+ * @param executor An executor to call the listener on.
+ * @param listener A spell checker session lister for getting results from a spell checker.
* @return The spell checker session of the spell checker.
*/
@Nullable
public SpellCheckerSession newSpellCheckerSession(
- @SuppressLint("NullableCollection") @Nullable Bundle bundle,
@SuppressLint("UseIcu") @Nullable Locale locale,
- @NonNull SpellCheckerSessionListener listener,
- @SuppressLint("ListenerLast") boolean referToSpellCheckerLanguageSettings,
- @SuppressLint("ListenerLast") @SuggestionsInfo.ResultAttrs int supportedAttributes) {
- if (listener == null) {
- throw new NullPointerException();
- }
+ boolean referToSpellCheckerLanguageSettings,
+ @SuggestionsInfo.ResultAttrs int supportedAttributes,
+ @Nullable Bundle bundle,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SpellCheckerSessionListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
if (!referToSpellCheckerLanguageSettings && locale == null) {
throw new IllegalArgumentException("Locale should not be null if you don't refer"
+ " settings.");
@@ -245,7 +274,7 @@
if (subtypeInUse == null) {
return null;
}
- final SpellCheckerSession session = new SpellCheckerSession(sci, this, listener);
+ final SpellCheckerSession session = new SpellCheckerSession(sci, this, listener, executor);
try {
mService.getSpellCheckerService(mUserId, sci.getId(), subtypeInUse.getLocale(),
session.getTextServicesSessionListener(),
@@ -275,15 +304,15 @@
}
/**
- * Retrieve the list of currently enabled spell checkers, or null if there is none.
+ * Retrieve the list of currently enabled spell checkers.
*
* @return The list of currently enabled spell checkers.
*/
- @Nullable
- @SuppressLint("NullableCollection")
+ @NonNull
public List<SpellCheckerInfo> getEnabledSpellCheckerInfos() {
final SpellCheckerInfo[] enabledSpellCheckers = getEnabledSpellCheckers();
- return enabledSpellCheckers != null ? Arrays.asList(enabledSpellCheckers) : null;
+ return enabledSpellCheckers != null
+ ? Arrays.asList(enabledSpellCheckers) : Collections.emptyList();
}
/**
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
index 7f6c4b4..d347f31 100644
--- a/core/java/android/view/translation/ITranslationManager.aidl
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -17,6 +17,7 @@
package android.view.translation;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.view.autofill.AutofillId;
import android.view.translation.TranslationSpec;
import com.android.internal.os.IResultReceiver;
@@ -40,4 +41,7 @@
void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec,
in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId,
int userId);
+
+ void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId);
+ void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId);
}
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index 7c73e70..852ffe8 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -16,28 +16,36 @@
package android.view.translation;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.assist.ActivityId;
import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillId;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.Executor;
+// TODO(b/178044703): Describe what UI Translation is.
/**
* The {@link UiTranslationManager} class provides ways for apps to use the ui translation
* function in framework.
- *
- * @hide
*/
-@SystemApi
public final class UiTranslationManager {
private static final String TAG = "UiTranslationManager";
@@ -88,6 +96,14 @@
public @interface UiTranslationState {
}
+ // Keys for the data transmitted in the internal UI Translation state callback.
+ /** @hide */
+ public static final String EXTRA_STATE = "state";
+ /** @hide */
+ public static final String EXTRA_SOURCE_LOCALE = "source_locale";
+ /** @hide */
+ public static final String EXTRA_TARGET_LOCALE = "target_locale";
+
@NonNull
private final Context mContext;
@@ -111,9 +127,12 @@
* @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
+ *
+ * @hide
*/
// TODO, hide the APIs
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void startTranslation(@NonNull TranslationSpec sourceSpec,
@NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds,
int taskId) {
@@ -141,8 +160,11 @@
* @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list
* @throws NullPointerException the sourceSpec, destSpec, viewIds, activityId or
* {@link android.app.assist.ActivityId#getToken()} is {@code null}
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void startTranslation(@NonNull TranslationSpec sourceSpec,
@NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds,
@NonNull ActivityId activityId) {
@@ -171,9 +193,12 @@
* NOTE: Please use {@code finishTranslation(ActivityId)} instead.
*
* @param taskId the Activity Task id which needs ui translation
+ *
+ * @hide
*/
// TODO, hide the APIs
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void finishTranslation(int taskId) {
try {
mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED,
@@ -191,8 +216,11 @@
* @param activityId the identifier for the Activity which needs ui translation
* @throws NullPointerException the activityId or
* {@link android.app.assist.ActivityId#getToken()} is {@code null}
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void finishTranslation(@NonNull ActivityId activityId) {
try {
Objects.requireNonNull(activityId);
@@ -212,9 +240,12 @@
* NOTE: Please use {@code pauseTranslation(ActivityId)} instead.
*
* @param taskId the Activity Task id which needs ui translation
+ *
+ * @hide
*/
// TODO, hide the APIs
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void pauseTranslation(int taskId) {
try {
mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED,
@@ -232,8 +263,11 @@
* @param activityId the identifier for the Activity which needs ui translation
* @throws NullPointerException the activityId or
* {@link android.app.assist.ActivityId#getToken()} is {@code null}
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void pauseTranslation(@NonNull ActivityId activityId) {
try {
Objects.requireNonNull(activityId);
@@ -253,9 +287,12 @@
* NOTE: Please use {@code resumeTranslation(ActivityId)} instead.
*
* @param taskId the Activity Task id which needs ui translation
+ *
+ * @hide
*/
// TODO, hide the APIs
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void resumeTranslation(int taskId) {
try {
mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED,
@@ -273,8 +310,11 @@
* @param activityId the identifier for the Activity which needs ui translation
* @throws NullPointerException the activityId or
* {@link android.app.assist.ActivityId#getToken()} is {@code null}
+ *
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
+ @SystemApi
public void resumeTranslation(@NonNull ActivityId activityId) {
try {
Objects.requireNonNull(activityId);
@@ -286,4 +326,105 @@
throw e.rethrowFromSystemServer();
}
}
+
+ // TODO(b/178044703): Fix the View API link when it becomes public.
+ /**
+ * Register for notifications of UI Translation state changes on the foreground activity. This
+ * is available to the owning application itself and also the current input method.
+ * <p>
+ * The application whose UI is being translated can use this to customize the UI Translation
+ * behavior in ways that aren't made easy by methods like
+ * View#onCreateTranslationRequest().
+ * <p>
+ * Input methods can use this to offer complementary features to UI Translation; for example,
+ * enabling outgoing message translation when the system is translating incoming messages in a
+ * communication app.
+ *
+ * @param callback the callback to register for receiving the state change
+ * notifications
+ */
+ public void registerUiTranslationStateCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull UiTranslationStateCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ synchronized (mCallbacks) {
+ if (mCallbacks.containsKey(callback)) {
+ Log.w(TAG, "registerUiTranslationStateCallback: callback already registered;"
+ + " ignoring.");
+ return;
+ }
+ final IRemoteCallback remoteCallback =
+ new UiTranslationStateRemoteCallback(executor, callback);
+ try {
+ mService.registerUiTranslationStateCallback(remoteCallback, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCallbacks.put(callback, remoteCallback);
+ }
+ }
+
+ /**
+ * Unregister {@code callback}.
+ *
+ * @see #registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)
+ */
+ public void unregisterUiTranslationStateCallback(@NonNull UiTranslationStateCallback callback) {
+ Objects.requireNonNull(callback);
+
+ synchronized (mCallbacks) {
+ final IRemoteCallback remoteCallback = mCallbacks.get(callback);
+ if (remoteCallback == null) {
+ Log.w(TAG, "unregisterUiTranslationStateCallback: callback not found; ignoring.");
+ return;
+ }
+ try {
+ mService.unregisterUiTranslationStateCallback(remoteCallback, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCallbacks.remove(callback);
+ }
+ }
+
+ @NonNull
+ @GuardedBy("mCallbacks")
+ private final Map<UiTranslationStateCallback, IRemoteCallback> mCallbacks = new ArrayMap<>();
+
+ private static class UiTranslationStateRemoteCallback extends IRemoteCallback.Stub {
+ private final Executor mExecutor;
+ private final UiTranslationStateCallback mCallback;
+
+ UiTranslationStateRemoteCallback(Executor executor,
+ UiTranslationStateCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void sendResult(Bundle bundle) {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> onStateChange(bundle)));
+ }
+
+ private void onStateChange(Bundle bundle) {
+ int state = bundle.getInt(EXTRA_STATE);
+ switch (state) {
+ case STATE_UI_TRANSLATION_STARTED:
+ case STATE_UI_TRANSLATION_RESUMED:
+ mCallback.onStarted(
+ bundle.getString(EXTRA_SOURCE_LOCALE),
+ bundle.getString(EXTRA_TARGET_LOCALE));
+ break;
+ case STATE_UI_TRANSLATION_PAUSED:
+ mCallback.onPaused();
+ break;
+ case STATE_UI_TRANSLATION_FINISHED:
+ mCallback.onFinished();
+ break;
+ default:
+ Log.wtf(TAG, "Unexpected translation state:" + state);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/translation/UiTranslationStateCallback.java b/core/java/android/view/translation/UiTranslationStateCallback.java
new file mode 100644
index 0000000..1946b70
--- /dev/null
+++ b/core/java/android/view/translation/UiTranslationStateCallback.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 android.view.translation;
+
+import android.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Callback for listening to UI Translation state changes. See {@link
+ * UiTranslationManager#registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)}.
+ */
+public interface UiTranslationStateCallback {
+
+ /**
+ * The system is requesting translation of the UI from {@code sourceLocale} to {@code
+ * targetLocale}.
+ * <p>
+ * This is also called if either the requested {@code sourceLocale} or {@code targetLocale} has
+ * changed; or called again after {@link #onPaused()}.
+ */
+ void onStarted(@NonNull String sourceLocale, @NonNull String targetLocale);
+
+ /**
+ * The system is requesting that the application temporarily show the UI contents in their
+ * original language.
+ */
+ void onPaused();
+
+ /**
+ * The UI Translation session has ended.
+ */
+ void onFinished();
+}
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 45352e4..3c41112 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -69,7 +69,7 @@
* @hide
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BASE)
public static final long USE_STRETCH_EDGE_EFFECT_BY_DEFAULT = 171228096L;
/**
@@ -130,7 +130,7 @@
public @interface EdgeEffectType {
}
- private static final float DEFAULT_MAX_STRETCH_INTENSITY = 1.5f;
+ private static final float DEFAULT_MAX_STRETCH_INTENSITY = 0.08f;
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "EdgeEffect";
@@ -177,6 +177,7 @@
private long mStartTime;
private float mDuration;
private float mStretchIntensity = DEFAULT_MAX_STRETCH_INTENSITY;
+ private float mStretchDistanceFraction = 1f;
private float mStretchDistance = -1f;
private final Interpolator mInterpolator = new DecelerateInterpolator();
@@ -353,13 +354,14 @@
mDistance = Math.max(0f, mPullDistance);
mVelocity = 0;
- final float absdd = Math.abs(deltaDistance);
- mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
- mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
-
if (mPullDistance == 0) {
mGlowScaleY = mGlowScaleYStart = 0;
+ mGlowAlpha = mGlowAlphaStart = 0;
} else {
+ final float absdd = Math.abs(deltaDistance);
+ mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
+ mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
+
final float scale = (float) (Math.max(0, 1 - 1 /
Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d);
@@ -467,7 +469,7 @@
public void onAbsorb(int velocity) {
if (mEdgeEffectType == TYPE_STRETCH) {
mState = STATE_RECEDE;
- mVelocity = velocity / mHeight;
+ mVelocity = velocity;
mDistance = 0;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
} else {
@@ -655,7 +657,7 @@
// for now leverage placeholder logic if no stretch distance is provided to
// consume the displacement ratio times the minimum of the width or height
mStretchDistance > 0 ? mStretchDistance :
- (mDisplacement * Math.min(mWidth, mHeight))
+ (mStretchDistanceFraction * Math.max(mWidth, mHeight))
);
}
@@ -697,7 +699,9 @@
mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
- mDistance = calculateDistanceFromGlowValues(mGlowScaleY, mGlowAlpha);
+ if (mState != STATE_PULL) {
+ mDistance = calculateDistanceFromGlowValues(mGlowScaleY, mGlowAlpha);
+ }
mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
if (t >= 1.f - EPSILON) {
@@ -745,9 +749,9 @@
final double mDampedFreq = NATURAL_FREQUENCY * Math.sqrt(1 - DAMPING_RATIO * DAMPING_RATIO);
// We're always underdamped, so we can use only those equations:
- double cosCoeff = mDistance;
+ double cosCoeff = mDistance * mHeight;
double sinCoeff = (1 / mDampedFreq) * (DAMPING_RATIO * NATURAL_FREQUENCY
- * mDistance + mVelocity);
+ * mDistance * mHeight + mVelocity);
double distance = Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
* (cosCoeff * Math.cos(mDampedFreq * deltaT)
+ sinCoeff * Math.sin(mDampedFreq * deltaT));
@@ -755,7 +759,7 @@
+ Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
* (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
+ mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
- mDistance = (float) distance;
+ mDistance = (float) distance / mHeight;
mVelocity = (float) velocity;
mStartTime = time;
if (isAtEquilibrium()) {
@@ -786,9 +790,8 @@
* considered at rest or false if it is still animating.
*/
private boolean isAtEquilibrium() {
- double velocity = mVelocity * mHeight; // in pixels/second
double displacement = mDistance * mHeight; // in pixels
- return Math.abs(velocity) < VELOCITY_THRESHOLD
+ return Math.abs(mVelocity) < VELOCITY_THRESHOLD
&& Math.abs(displacement) < VALUE_THRESHOLD;
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 7517b80..238ce85 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -988,6 +988,12 @@
if (mTextView.isTextEditable() && mTextView.isSuggestionsEnabled()
&& !(mTextView.isInExtractedMode())) {
+ final InputMethodManager imm = getInputMethodManager();
+ if (imm != null && imm.isInputMethodSuppressingSpellChecker()) {
+ // Do not close mSpellChecker here as it may be reused when the current IME has been
+ // changed.
+ return;
+ }
if (mSpellChecker == null && createSpellChecker) {
mSpellChecker = new SpellChecker(mTextView);
}
@@ -2898,9 +2904,6 @@
} finally {
mTextView.endBatchEdit();
mUndoInputFilter.freezeLastEdit();
- if (permissions != null) {
- permissions.release();
- }
}
}
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index 718076b..64570a8 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -5,6 +5,8 @@
adamp@google.com
aurimas@google.com
siyamed@google.com
+mount@google.com
+njawad@google.com
per-file TextView.java, EditText.java, Editor.java = siyamed@google.com, nona@google.com, clarabayarri@google.com
diff --git a/core/java/android/widget/RemoteCollectionItemsAdapter.java b/core/java/android/widget/RemoteCollectionItemsAdapter.java
new file mode 100644
index 0000000..d843308
--- /dev/null
+++ b/core/java/android/widget/RemoteCollectionItemsAdapter.java
@@ -0,0 +1,217 @@
+/*
+ * 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.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.ColorResources;
+import android.widget.RemoteViews.InteractionHandler;
+import android.widget.RemoteViews.RemoteCollectionItems;
+
+import com.android.internal.R;
+
+import java.util.stream.IntStream;
+
+/**
+ * List {@link Adapter} backed by a {@link RemoteCollectionItems}.
+ *
+ * @hide
+ */
+class RemoteCollectionItemsAdapter extends BaseAdapter {
+
+ private final int mViewTypeCount;
+
+ private RemoteCollectionItems mItems;
+ private InteractionHandler mInteractionHandler;
+ private ColorResources mColorResources;
+
+ private SparseIntArray mLayoutIdToViewType;
+
+ RemoteCollectionItemsAdapter(
+ @NonNull RemoteCollectionItems items,
+ @NonNull InteractionHandler interactionHandler,
+ @NonNull ColorResources colorResources) {
+ // View type count can never increase after an adapter has been set on a ListView.
+ // Additionally, decreasing it could inhibit view recycling if the count were to back and
+ // forth between 3-2-3-2 for example. Therefore, the view type count, should be fixed for
+ // the lifetime of the adapter.
+ mViewTypeCount = items.getViewTypeCount();
+
+ mItems = items;
+ mInteractionHandler = interactionHandler;
+ mColorResources = colorResources;
+
+ initLayoutIdToViewType();
+ }
+
+ /**
+ * Updates the data for the adapter, allowing recycling of views. Note that if the view type
+ * count has increased, a new adapter should be created and set on the AdapterView instead of
+ * calling this method.
+ */
+ void setData(
+ @NonNull RemoteCollectionItems items,
+ @NonNull InteractionHandler interactionHandler,
+ @NonNull ColorResources colorResources) {
+ if (mViewTypeCount < items.getViewTypeCount()) {
+ throw new IllegalArgumentException(
+ "RemoteCollectionItemsAdapter cannot increase view type count after creation");
+ }
+
+ mItems = items;
+ mInteractionHandler = interactionHandler;
+ mColorResources = colorResources;
+
+ initLayoutIdToViewType();
+
+ notifyDataSetChanged();
+ }
+
+ private void initLayoutIdToViewType() {
+ SparseIntArray previousLayoutIdToViewType = mLayoutIdToViewType;
+ mLayoutIdToViewType = new SparseIntArray(mViewTypeCount);
+
+ int[] layoutIds = IntStream.range(0, mItems.getItemCount())
+ .map(position -> mItems.getItemView(position).getLayoutId())
+ .distinct()
+ .toArray();
+ if (layoutIds.length > mViewTypeCount) {
+ throw new IllegalArgumentException(
+ "Collection items uses " + layoutIds.length + " distinct layouts, which is "
+ + "more than view type count of " + mViewTypeCount);
+ }
+
+ // Tracks whether a layout id (by index, not value) has been assigned a view type.
+ boolean[] processedLayoutIdIndices = new boolean[layoutIds.length];
+ // Tracks whether a view type has been assigned to a layout id already.
+ boolean[] assignedViewTypes = new boolean[mViewTypeCount];
+
+ if (previousLayoutIdToViewType != null) {
+ for (int i = 0; i < layoutIds.length; i++) {
+ int layoutId = layoutIds[i];
+ // Copy over any previously used view types for layout ids in the collection to keep
+ // view types stable across data updates.
+ int previousViewType = previousLayoutIdToViewType.get(layoutId, -1);
+ // Skip this layout id if it wasn't assigned to a view type previously.
+ if (previousViewType < 0) continue;
+
+ mLayoutIdToViewType.put(layoutId, previousViewType);
+ processedLayoutIdIndices[i] = true;
+ assignedViewTypes[previousViewType] = true;
+ }
+ }
+
+ int lastViewType = -1;
+ for (int i = 0; i < layoutIds.length; i++) {
+ // If a view type has already been assigned to the layout id, skip it.
+ if (processedLayoutIdIndices[i]) continue;
+
+ int layoutId = layoutIds[i];
+ // If no view type is assigned for the layout id, choose the next possible value that
+ // isn't already assigned to a layout id. There is guaranteed to be some value available
+ // due to the prior validation logic that count(distinct layout ids) <= viewTypeCount.
+ int viewType = IntStream.range(lastViewType + 1, layoutIds.length)
+ .filter(type -> !assignedViewTypes[type])
+ .findFirst()
+ .orElseThrow(
+ () -> new IllegalStateException(
+ "RemoteCollectionItems has more distinct layout ids than its "
+ + "view type count"));
+ mLayoutIdToViewType.put(layoutId, viewType);
+ processedLayoutIdIndices[i] = true;
+ assignedViewTypes[viewType] = true;
+ lastViewType = viewType;
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.getItemCount();
+ }
+
+ @Override
+ public RemoteViews getItem(int position) {
+ return mItems.getItemView(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mItems.getItemId(position);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mLayoutIdToViewType.get(mItems.getItemView(position).getLayoutId());
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return mViewTypeCount;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return mItems.hasStableIds();
+ }
+
+ @Nullable
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ if (position >= getCount()) return null;
+
+ RemoteViews item = mItems.getItemView(position);
+ item.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+ View reapplyView = getViewToReapply(convertView, item);
+
+ // Reapply the RemoteViews if we can.
+ if (reapplyView != null) {
+ try {
+ item.reapply(
+ parent.getContext(),
+ reapplyView,
+ mInteractionHandler,
+ null /* size */,
+ mColorResources);
+ return reapplyView;
+ } catch (RuntimeException e) {
+ // We can't reapply for some reason, we'll fallback to an apply and inflate a
+ // new view.
+ }
+ }
+
+ return item.apply(
+ parent.getContext(),
+ parent,
+ mInteractionHandler,
+ null /* size */,
+ mColorResources);
+ }
+
+ /** Returns {@code convertView} if it can be used to reapply {@code item}, or null otherwise. */
+ @Nullable
+ private static View getViewToReapply(@Nullable View convertView, @NonNull RemoteViews item) {
+ if (convertView == null) return null;
+
+ Object layoutIdTag = convertView.getTag(R.id.widget_frame);
+ if (!(layoutIdTag instanceof Integer)) return null;
+
+ return item.getLayoutId() == (Integer) layoutIdTag ? convertView : null;
+ }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d2f4cea..2b73923 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -28,6 +28,7 @@
import android.annotation.Px;
import android.annotation.StringRes;
import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
@@ -74,6 +75,7 @@
import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Log;
+import android.util.LongArray;
import android.util.Pair;
import android.util.SizeF;
import android.util.SparseIntArray;
@@ -112,6 +114,7 @@
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -224,6 +227,7 @@
private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29;
private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
+ private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -899,6 +903,72 @@
ArrayList<RemoteViews> list;
}
+ private static class SetRemoteCollectionItemListAdapterAction extends Action {
+ private final RemoteCollectionItems mItems;
+
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+ viewId = id;
+ mItems = items;
+ }
+
+ SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
+ viewId = parcel.readInt();
+ mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(viewId);
+ dest.writeTypedObject(mItems, flags);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
+ ColorResources colorResources) throws ActionException {
+ View target = root.findViewById(viewId);
+ if (target == null) return;
+
+ if (!(target instanceof AdapterView)) {
+ Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not "
+ + "an AdapterView (id: " + viewId + ")");
+ return;
+ }
+
+ AdapterView adapterView = (AdapterView) target;
+ Adapter adapter = adapterView.getAdapter();
+ // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type
+ // count hasn't increased. Note that AbsListView allocates a fixed size array for view
+ // recycling in setAdapter, so we must call setAdapter again if the number of view types
+ // increases.
+ if (adapter instanceof RemoteCollectionItemsAdapter
+ && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+ try {
+ ((RemoteCollectionItemsAdapter) adapter).setData(
+ mItems, handler, colorResources);
+ } catch (Throwable throwable) {
+ // setData should never failed with the validation in the items builder, but if
+ // it does, catch and rethrow.
+ throw new ActionException(throwable);
+ }
+ return;
+ }
+
+ try {
+ adapterView.setAdapter(
+ new RemoteCollectionItemsAdapter(mItems, handler, colorResources));
+ } catch (Throwable throwable) {
+ // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
+ // a type error.
+ throw new ActionException(throwable);
+ }
+ }
+
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
+ }
+ }
+
private class SetRemoteViewsAdapterIntent extends Action {
public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) {
this.viewId = id;
@@ -3543,6 +3613,8 @@
return new SetOnCheckedChangeResponse(parcel);
case NIGHT_MODE_REFLECTION_ACTION_TAG:
return new NightModeReflectionAction(parcel);
+ case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG:
+ return new SetRemoteCollectionItemListAdapterAction(parcel);
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -4215,6 +4287,25 @@
}
/**
+ * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
+ * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
+ * This is a simpler but less flexible approach to populating collection widgets. Its use is
+ * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
+ * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
+ * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
+ * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
+ *
+ * This API is supported in the compatibility library for previous API levels, see
+ * RemoteViewsCompat.
+ *
+ * @param viewId The id of the {@link AdapterView}.
+ * @param items The items to display in the {@link AdapterView}.
+ */
+ public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) {
+ addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items));
+ }
+
+ /**
* Equivalent to calling {@link ListView#smoothScrollToPosition(int)}.
*
* @param viewId The id of the view to change
@@ -4335,8 +4426,7 @@
/**
* Sets an OutlineProvider on the view whose corner radius is a dimension calculated using
- * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}. This outline may change shape
- * during system transitions.
+ * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}.
*
* <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
* Setting margins in pixels will behave poorly when the RemoteViews object is used on a
@@ -4349,7 +4439,7 @@
/**
* Sets an OutlineProvider on the view whose corner radius is a dimension resource with
- * {@code resId}. This outline may change shape during system transitions.
+ * {@code resId}.
*/
public void setViewOutlinePreferredRadiusDimen(@IdRes int viewId, @DimenRes int resId) {
addAction(new SetViewOutlinePreferredRadiusAction(viewId, resId));
@@ -4403,7 +4493,8 @@
* Call a method taking one int, a size in pixels, on a view in the layout for this
* RemoteViews.
*
- * The dimension will be resolved from the resources at the time of inflation.
+ * The dimension will be resolved from the resources at the time the {@link RemoteViews} is
+ * (re-)applied.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
@@ -4435,7 +4526,8 @@
/**
* Call a method taking one int, a color, on a view in the layout for this RemoteViews.
*
- * The ColorStateList will be resolved from the resources at the time of inflation.
+ * The Color will be resolved from the resources at the time the {@link RemoteViews} is (re-)
+ * applied.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
@@ -4512,7 +4604,8 @@
/**
* Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
*
- * The ColorStateList will be resolved from the resources at the time of inflation.
+ * The ColorStateList will be resolved from the resources at the time the {@link RemoteViews} is
+ * (re-)applied.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
@@ -4551,7 +4644,8 @@
* Call a method taking one float, a size in pixels, on a view in the layout for this
* RemoteViews.
*
- * The dimension will be resolved from the resources at the time of inflation.
+ * The dimension will be resolved from the resources at the time the {@link RemoteViews} is
+ * (re-)applied.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
@@ -4567,7 +4661,8 @@
* Call a method taking one float, a size in pixels, on a view in the layout for this
* RemoteViews.
*
- * The dimension will be resolved from the specified dimension at the time of inflation.
+ * The dimension will be resolved from the resources at the time the {@link RemoteViews} is
+ * (re-)applied.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
@@ -4629,7 +4724,8 @@
/**
* Call a method taking one CharSequence on a view in the layout for this RemoteViews.
*
- * The CharSequence will be resolved from the resources at the time of inflation.
+ * The CharSequence will be resolved from the resources at the time the {@link RemoteViews} is
+ * (re-)applied.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
@@ -6026,6 +6122,203 @@
return true;
}
+ /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */
+ public static final class RemoteCollectionItems implements Parcelable {
+ private final long[] mIds;
+ private final RemoteViews[] mViews;
+ private final boolean mHasStableIds;
+ private final int mViewTypeCount;
+
+ RemoteCollectionItems(
+ long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) {
+ mIds = ids;
+ mViews = views;
+ mHasStableIds = hasStableIds;
+ mViewTypeCount = viewTypeCount;
+ if (ids.length != views.length) {
+ throw new IllegalArgumentException(
+ "RemoteCollectionItems has different number of ids and views");
+ }
+ if (viewTypeCount < 1) {
+ throw new IllegalArgumentException("View type count must be >= 1");
+ }
+ int layoutIdCount = (int) Arrays.stream(views)
+ .mapToInt(RemoteViews::getLayoutId)
+ .distinct()
+ .count();
+ if (layoutIdCount > viewTypeCount) {
+ throw new IllegalArgumentException(
+ "View type count is set to " + viewTypeCount + ", but the collection "
+ + "contains " + layoutIdCount + " different layout ids");
+ }
+ }
+
+ RemoteCollectionItems(Parcel in) {
+ int length = in.readInt();
+ mIds = new long[length];
+ in.readLongArray(mIds);
+ mViews = new RemoteViews[length];
+ in.readTypedArray(mViews, RemoteViews.CREATOR);
+ mHasStableIds = in.readBoolean();
+ mViewTypeCount = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mIds.length);
+ dest.writeLongArray(mIds);
+ dest.writeTypedArray(mViews, flags);
+ dest.writeBoolean(mHasStableIds);
+ dest.writeInt(mViewTypeCount);
+ }
+
+ /**
+ * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id
+ * should be considered meaningful across collection updates.
+ *
+ * @return Id for the position.
+ */
+ public long getItemId(int position) {
+ return mIds[position];
+ }
+
+ /**
+ * Returns the {@link RemoteViews} to display at {@code position}.
+ *
+ * @return RemoteViews for the position.
+ */
+ @NonNull
+ public RemoteViews getItemView(int position) {
+ return mViews[position];
+ }
+
+ /**
+ * Returns the number of elements in the collection.
+ *
+ * @return Count of items.
+ */
+ public int getItemCount() {
+ return mIds.length;
+ }
+
+ /**
+ * Returns the view type count for the collection when used in an adapter
+ *
+ * @return Count of view types for the collection when used in an adapter.
+ * @see android.widget.Adapter#getViewTypeCount()
+ */
+ public int getViewTypeCount() {
+ return mViewTypeCount;
+ }
+
+ /**
+ * Indicates whether the item ids are stable across changes to the underlying data.
+ *
+ * @return True if the same id always refers to the same object.
+ * @see android.widget.Adapter#hasStableIds()
+ */
+ public boolean hasStableIds() {
+ return mHasStableIds;
+ }
+
+ @NonNull
+ public static final Creator<RemoteCollectionItems> CREATOR =
+ new Creator<RemoteCollectionItems>() {
+ @NonNull
+ @Override
+ public RemoteCollectionItems createFromParcel(@NonNull Parcel source) {
+ return new RemoteCollectionItems(source);
+ }
+
+ @NonNull
+ @Override
+ public RemoteCollectionItems[] newArray(int size) {
+ return new RemoteCollectionItems[size];
+ }
+ };
+
+ /** Builder class for {@link RemoteCollectionItems} objects.*/
+ public static final class Builder {
+ private final LongArray mIds = new LongArray();
+ private final List<RemoteViews> mViews = new ArrayList<>();
+ private boolean mHasStableIds;
+ private int mViewTypeCount;
+
+ /**
+ * Adds a {@link RemoteViews} to the collection.
+ *
+ * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to
+ * indicate that ids are stable across changes to the collection.
+ * @param view RemoteViews to display for the row.
+ */
+ @NonNull
+ // Covered by getItemId, getItemView, getItemCount.
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder addItem(long id, @NonNull RemoteViews view) {
+ if (view == null) throw new NullPointerException();
+ if (view.hasMultipleLayouts()) {
+ throw new IllegalArgumentException(
+ "RemoteViews used in a RemoteCollectionItems cannot specify separate "
+ + "layouts for orientations or sizes.");
+ }
+ mIds.add(id);
+ mViews.add(view);
+ return this;
+ }
+
+ /**
+ * Sets whether the item ids are stable across changes to the underlying data.
+ *
+ * @see android.widget.Adapter#hasStableIds()
+ */
+ @NonNull
+ public Builder setHasStableIds(boolean hasStableIds) {
+ mHasStableIds = hasStableIds;
+ return this;
+ }
+
+ /**
+ * Sets the view type count for the collection when used in an adapter. This can be set
+ * to the maximum number of different layout ids that will be used by RemoteViews in
+ * this collection.
+ *
+ * If this value is not set, then a value will be inferred from the provided items. As
+ * a result, the adapter may need to be recreated when the list is updated with
+ * previously unseen RemoteViews layouts for new items.
+ *
+ * @see android.widget.Adapter#getViewTypeCount()
+ */
+ @NonNull
+ public Builder setViewTypeCount(int viewTypeCount) {
+ mViewTypeCount = viewTypeCount;
+ return this;
+ }
+
+ /** Creates the {@link RemoteCollectionItems} defined by this builder. */
+ @NonNull
+ public RemoteCollectionItems build() {
+ if (mViewTypeCount < 1) {
+ // If a view type count wasn't specified, set it to be the number of distinct
+ // layout ids used in the items.
+ mViewTypeCount = (int) mViews.stream()
+ .mapToInt(RemoteViews::getLayoutId)
+ .distinct()
+ .count();
+ }
+ return new RemoteCollectionItems(
+ mIds.toArray(),
+ mViews.toArray(new RemoteViews[0]),
+ mHasStableIds,
+ Math.max(mViewTypeCount, 1));
+ }
+ }
+ }
+
/**
* Set the ID of the top-level view of the XML layout.
*
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index d59a415..a63305e 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -26,6 +26,7 @@
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.LruCache;
+import android.util.Range;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
@@ -62,7 +63,8 @@
// Pause between each spell check to keep the UI smooth
private final static int SPELL_PAUSE_DURATION = 400; // milliseconds
- private static final int MIN_SENTENCE_LENGTH = 50;
+ // The maximum length of sentence.
+ private static final int MAX_SENTENCE_LENGTH = WORD_ITERATOR_INTERVAL;
private static final int USE_SPAN_RANGE = -1;
@@ -89,7 +91,7 @@
// Shared by all SpellParsers. Cannot be shared with TextView since it may be used
// concurrently due to the asynchronous nature of onGetSuggestions.
- private WordIterator mWordIterator;
+ private SentenceIteratorWrapper mSentenceIterator;
@Nullable
private TextServicesManager mTextServicesManager;
@@ -124,14 +126,14 @@
|| mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) {
mSpellCheckerSession = null;
} else {
+ int supportedAttributes = SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
+ | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
+ | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
+ | SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS;
mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession(
+ mCurrentLocale, false, supportedAttributes,
null /* Bundle not currently used by the textServicesManager */,
- mCurrentLocale, this,
- false /* means any available languages from current spell checker */,
- SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
- | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
- | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
- | SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS);
+ mTextView.getContext().getMainExecutor(), this);
}
// Restore SpellCheckSpans in pool
@@ -151,8 +153,9 @@
resetSession();
if (locale != null) {
- // Change SpellParsers' wordIterator locale
- mWordIterator = new WordIterator(locale);
+ // Change SpellParsers' sentenceIterator locale
+ mSentenceIterator = new SentenceIteratorWrapper(
+ BreakIterator.getSentenceInstance(locale));
}
// This class is the listener for locale change: warn other locale-aware objects
@@ -306,22 +309,30 @@
final int start = editable.getSpanStart(spellCheckSpan);
final int end = editable.getSpanEnd(spellCheckSpan);
- // Do not check this word if the user is currently editing it
- final boolean isEditing;
+ // Check the span if any of following conditions is met:
+ // - the user is not currently editing it
+ // - or `forceCheckWhenEditingWord` is true.
+ final boolean isNotEditing;
// Defer spell check when typing a word ending with a punctuation like an apostrophe
// which could end up being a mid-word punctuation.
if (selectionStart == end + 1
&& WordIterator.isMidWordPunctuation(
mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
- isEditing = false;
- } else {
+ isNotEditing = false;
+ } else if (selectionEnd <= start || selectionStart > end) {
// Allow the overlap of the cursor and the first boundary of the spell check span
// no to skip the spell check of the following word because the
// following word will never be spell-checked even if the user finishes composing
- isEditing = selectionEnd <= start || selectionStart > end;
+ isNotEditing = true;
+ } else {
+ // When cursor is at the end of spell check span, allow spell check if the
+ // character before cursor is a separator.
+ isNotEditing = selectionStart == end
+ && selectionStart > 0
+ && isSeparator(Character.codePointBefore(editable, selectionStart));
}
- if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
+ if (start >= 0 && end > start && (forceCheckWhenEditingWord || isNotEditing)) {
spellCheckSpan.setSpellCheckInProgress(true);
final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
textInfos[textInfosCount++] = textInfo;
@@ -346,6 +357,19 @@
}
}
+ private static boolean isSeparator(int codepoint) {
+ final int type = Character.getType(codepoint);
+ return ((1 << type) & ((1 << Character.SPACE_SEPARATOR)
+ | (1 << Character.LINE_SEPARATOR)
+ | (1 << Character.PARAGRAPH_SEPARATOR)
+ | (1 << Character.DASH_PUNCTUATION)
+ | (1 << Character.END_PUNCTUATION)
+ | (1 << Character.FINAL_QUOTE_PUNCTUATION)
+ | (1 << Character.INITIAL_QUOTE_PUNCTUATION)
+ | (1 << Character.START_PUNCTUATION)
+ | (1 << Character.OTHER_PUNCTUATION))) != 0;
+ }
+
private SpellCheckSpan onGetSuggestionsInternal(
SuggestionsInfo suggestionsInfo, int offset, int length) {
if (suggestionsInfo == null || suggestionsInfo.getCookie() != mCookie) {
@@ -534,6 +558,60 @@
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
}
+ /**
+ * A wrapper of sentence iterator which only processes the specified window of the given text.
+ */
+ private static class SentenceIteratorWrapper {
+ private BreakIterator mSentenceIterator;
+ private int mStartOffset;
+ private int mEndOffset;
+
+ SentenceIteratorWrapper(BreakIterator sentenceIterator) {
+ mSentenceIterator = sentenceIterator;
+ }
+
+ /**
+ * Set the char sequence and the text window to process.
+ */
+ public void setCharSequence(CharSequence sequence, int start, int end) {
+ mStartOffset = Math.max(0, start);
+ mEndOffset = Math.min(end, sequence.length());
+ mSentenceIterator.setText(sequence.subSequence(mStartOffset, mEndOffset).toString());
+ }
+
+ /**
+ * See {@link BreakIterator#preceding(int)}
+ */
+ public int preceding(int offset) {
+ if (offset < mStartOffset) {
+ return BreakIterator.DONE;
+ }
+ int result = mSentenceIterator.preceding(offset - mStartOffset);
+ return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+ }
+
+ /**
+ * See {@link BreakIterator#following(int)}
+ */
+ public int following(int offset) {
+ if (offset > mEndOffset) {
+ return BreakIterator.DONE;
+ }
+ int result = mSentenceIterator.following(offset - mStartOffset);
+ return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+ }
+
+ /**
+ * See {@link BreakIterator#isBoundary(int)}
+ */
+ public boolean isBoundary(int offset) {
+ if (offset < mStartOffset || offset > mEndOffset) {
+ return false;
+ }
+ return mSentenceIterator.isBoundary(offset - mStartOffset);
+ }
+ }
+
private class SpellParser {
private Object mRange = new Object();
@@ -582,27 +660,15 @@
public void parse() {
Editable editable = (Editable) mTextView.getText();
- // Iterate over the newly added text and schedule new SpellCheckSpans
- final int start = Math.max(
- 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
+ final int textChangeStart = editable.getSpanStart(mRange);
+ final int textChangeEnd = editable.getSpanEnd(mRange);
- final int end = editable.getSpanEnd(mRange);
+ Range<Integer> sentenceBoundary = detectSentenceBoundary(editable, textChangeStart,
+ textChangeEnd);
+ int sentenceStart = sentenceBoundary.getLower();
+ int sentenceEnd = sentenceBoundary.getUpper();
- int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL);
- mWordIterator.setCharSequence(editable, start, wordIteratorWindowEnd);
-
- // Move back to the beginning of the current word, if any
- int wordStart = mWordIterator.preceding(start);
- int wordEnd;
- if (wordStart == BreakIterator.DONE) {
- wordEnd = mWordIterator.following(start);
- if (wordEnd != BreakIterator.DONE) {
- wordStart = mWordIterator.getBeginning(wordEnd);
- }
- } else {
- wordEnd = mWordIterator.getEnd(wordStart);
- }
- if (wordEnd == BreakIterator.DONE) {
+ if (sentenceStart == sentenceEnd) {
if (DBG) {
Log.i(TAG, "No more spell check.");
}
@@ -612,29 +678,16 @@
boolean scheduleOtherSpellCheck = false;
- if (wordIteratorWindowEnd < end) {
+ if (sentenceEnd < textChangeEnd) {
if (DBG) {
Log.i(TAG, "schedule other spell check.");
}
// Several batches needed on that region. Cut after last previous word
scheduleOtherSpellCheck = true;
}
- int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
- boolean correct = spellCheckEnd != BreakIterator.DONE;
- if (correct) {
- spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
- correct = spellCheckEnd != BreakIterator.DONE;
- }
- if (!correct) {
- if (DBG) {
- Log.i(TAG, "Incorrect range span.");
- }
- stop();
- return;
- }
+ int spellCheckEnd = sentenceEnd;
do {
- // TODO: Find the start position of the sentence.
- int spellCheckStart = wordStart;
+ int spellCheckStart = sentenceStart;
boolean createSpellCheckSpan = true;
// Cancel or merge overlapped spell check spans
for (int i = 0; i < mLength; ++i) {
@@ -671,27 +724,23 @@
}
// Stop spell checking when there are no characters in the range.
- if (spellCheckEnd < start) {
- break;
- }
if (spellCheckEnd <= spellCheckStart) {
Log.w(TAG, "Trying to spellcheck invalid region, from "
- + start + " to " + end);
+ + sentenceStart + " to " + spellCheckEnd);
break;
}
if (createSpellCheckSpan) {
addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
}
} while (false);
- wordStart = spellCheckEnd;
-
- if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
+ sentenceStart = spellCheckEnd;
+ if (scheduleOtherSpellCheck && sentenceStart != BreakIterator.DONE
+ && sentenceStart <= textChangeEnd) {
// Update range span: start new spell check from last wordStart
- setRangeSpan(editable, wordStart, end);
+ setRangeSpan(editable, sentenceStart, textChangeEnd);
} else {
removeRangeSpan(editable);
}
-
spellCheck(mForceCheckWhenEditingWord);
}
@@ -708,6 +757,94 @@
}
}
+ private Range<Integer> detectSentenceBoundary(CharSequence sequence,
+ int textChangeStart, int textChangeEnd) {
+ // Only process a substring of the full text due to performance concern.
+ final int iteratorWindowStart = findSeparator(sequence,
+ Math.max(0, textChangeStart - MAX_SENTENCE_LENGTH),
+ Math.max(0, textChangeStart - 2 * MAX_SENTENCE_LENGTH));
+ final int iteratorWindowEnd = findSeparator(sequence,
+ Math.min(textChangeStart + 2 * MAX_SENTENCE_LENGTH, textChangeEnd),
+ Math.min(textChangeStart + 3 * MAX_SENTENCE_LENGTH, sequence.length()));
+ if (DBG) {
+ Log.d(TAG, "Set iterator window as [" + iteratorWindowStart + ", " + iteratorWindowEnd
+ + ").");
+ }
+ mSentenceIterator.setCharSequence(sequence, iteratorWindowStart, iteratorWindowEnd);
+
+ // Detect the offset of sentence begin/end on the substring.
+ int sentenceStart = mSentenceIterator.isBoundary(textChangeStart) ? textChangeStart
+ : mSentenceIterator.preceding(textChangeStart);
+ int sentenceEnd = mSentenceIterator.following(sentenceStart);
+ if (sentenceEnd == BreakIterator.DONE) {
+ sentenceEnd = iteratorWindowEnd;
+ }
+ if (DBG) {
+ if (sentenceStart != sentenceEnd) {
+ Log.d(TAG, "Sentence detected [" + sentenceStart + ", " + sentenceEnd + ").");
+ }
+ }
+
+ if (sentenceEnd - sentenceStart <= MAX_SENTENCE_LENGTH) {
+ // Add more sentences until the MAX_SENTENCE_LENGTH limitation is reached.
+ while (sentenceEnd < textChangeEnd) {
+ int nextEnd = mSentenceIterator.following(sentenceEnd);
+ if (nextEnd == BreakIterator.DONE
+ || nextEnd - sentenceStart > MAX_SENTENCE_LENGTH) {
+ break;
+ }
+ sentenceEnd = nextEnd;
+ }
+ } else {
+ // If the sentence containing `textChangeStart` is longer than MAX_SENTENCE_LENGTH,
+ // the sentence will be sliced into sub-sentences of about MAX_SENTENCE_LENGTH
+ // characters each. This is done by processing the unchecked part of that sentence :
+ // [textChangeStart, sentenceEnd)
+ //
+ // - If the `uncheckedLength` is bigger than MAX_SENTENCE_LENGTH, then check the
+ // [textChangeStart, textChangeStart + MAX_SENTENCE_LENGTH), and leave the rest
+ // part for the next check.
+ //
+ // - If the `uncheckedLength` is smaller than or equal to MAX_SENTENCE_LENGTH,
+ // then check [sentenceEnd - MAX_SENTENCE_LENGTH, sentenceEnd).
+ //
+ // The offset should be rounded up to word boundary.
+ int uncheckedLength = sentenceEnd - textChangeStart;
+ if (uncheckedLength > MAX_SENTENCE_LENGTH) {
+ sentenceEnd = findSeparator(sequence, sentenceStart + MAX_SENTENCE_LENGTH,
+ sentenceEnd);
+ sentenceStart = roundUpToWordStart(sequence, textChangeStart, sentenceStart);
+ } else {
+ sentenceStart = roundUpToWordStart(sequence, sentenceEnd - MAX_SENTENCE_LENGTH,
+ sentenceStart);
+ }
+ }
+ return new Range(sentenceStart, sentenceEnd);
+ }
+
+ private int roundUpToWordStart(CharSequence sequence, int position, int frontBoundary) {
+ if (isSeparator(sequence.charAt(position))) {
+ return position;
+ }
+ int separator = findSeparator(sequence, position, frontBoundary);
+ return separator != frontBoundary ? separator + 1 : frontBoundary;
+ }
+
+ /**
+ * Search the range [start, end) of sequence and returns the position of the first separator.
+ * If end is smaller than start, do a reverse search.
+ * Returns `end` if no separator is found.
+ */
+ private static int findSeparator(CharSequence sequence, int start, int end) {
+ final int step = start < end ? 1 : -1;
+ for (int i = start; i != end; i += step) {
+ if (isSeparator(sequence.charAt(i))) {
+ return i;
+ }
+ }
+ return end;
+ }
+
public static boolean haveWordBoundariesChanged(final Editable editable, final int start,
final int end, final int spanStart, final int spanEnd) {
final boolean haveWordBoundariesChanged;
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/core/java/android/window/SizeConfigurationBuckets.aidl
similarity index 82%
copy from telephony/java/android/telephony/data/SliceInfo.aidl
copy to core/java/android/window/SizeConfigurationBuckets.aidl
index 286ea5e..adb57f0 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/core/java/android/window/SizeConfigurationBuckets.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package android.window;
-parcelable SliceInfo;
+parcelable SizeConfigurationBuckets;
diff --git a/core/java/android/window/SizeConfigurationBuckets.java b/core/java/android/window/SizeConfigurationBuckets.java
new file mode 100644
index 0000000..7422f24
--- /dev/null
+++ b/core/java/android/window/SizeConfigurationBuckets.java
@@ -0,0 +1,282 @@
+/*
+ * 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.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.os.Parcelable;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Arrays;
+
+/**
+ * Contains size-configuration buckets used to prevent excessive configuration changes during
+ * resize.
+ *
+ * These configurations are collected from application's resources based on size-sensitive
+ * qualifiers. For example, layout-w800dp will be added to mHorizontalSizeConfigurations as 800
+ * and drawable-sw400dp will be added to both as 400.
+ *
+ * @hide
+ */
+@DataClass(genAidl = true)
+public final class SizeConfigurationBuckets implements Parcelable {
+
+ /** Horizontal (screenWidthDp) buckets */
+ @Nullable
+ private final int[] mHorizontal;
+
+ /** Vertical (screenHeightDp) buckets */
+ @Nullable
+ private final int[] mVertical;
+
+ /** Smallest (smallestScreenWidthDp) buckets */
+ @Nullable
+ private final int[] mSmallest;
+
+ public SizeConfigurationBuckets(Configuration[] sizeConfigurations) {
+ SparseIntArray horizontal = new SparseIntArray();
+ SparseIntArray vertical = new SparseIntArray();
+ SparseIntArray smallest = new SparseIntArray();
+ for (int i = sizeConfigurations.length - 1; i >= 0; i--) {
+ Configuration config = sizeConfigurations[i];
+ if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ vertical.put(config.screenHeightDp, 0);
+ }
+ if (config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ horizontal.put(config.screenWidthDp, 0);
+ }
+ if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+ smallest.put(config.smallestScreenWidthDp, 0);
+ }
+ }
+ mHorizontal = horizontal.copyKeys();
+ mVertical = vertical.copyKeys();
+ mSmallest = smallest.copyKeys();
+ }
+
+ /**
+ * Get the changes between two configurations but don't count changes in sizes if they don't
+ * cross boundaries that are important to the app.
+ *
+ * This is a static helper to deal with null `buckets`. When no buckets have been specified,
+ * this actually filters out all 3 size-configs. This is legacy behavior.
+ */
+ public static int filterDiff(int diff, Configuration oldConfig, Configuration newConfig,
+ @Nullable SizeConfigurationBuckets buckets) {
+ if (buckets == null) {
+ return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
+ }
+ if ((diff & CONFIG_SCREEN_SIZE) != 0) {
+ final boolean crosses = buckets.crossesHorizontalSizeThreshold(oldConfig.screenWidthDp,
+ newConfig.screenWidthDp)
+ || buckets.crossesVerticalSizeThreshold(oldConfig.screenHeightDp,
+ newConfig.screenHeightDp);
+ if (!crosses) {
+ diff &= ~CONFIG_SCREEN_SIZE;
+ }
+ }
+ if ((diff & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
+ final int oldSmallest = oldConfig.smallestScreenWidthDp;
+ final int newSmallest = newConfig.smallestScreenWidthDp;
+ if (!buckets.crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
+ diff &= ~CONFIG_SMALLEST_SCREEN_SIZE;
+ }
+ }
+ return diff;
+ }
+
+ private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
+ return crossesSizeThreshold(mHorizontal, firstDp, secondDp);
+ }
+
+ private boolean crossesVerticalSizeThreshold(int firstDp, int secondDp) {
+ return crossesSizeThreshold(mVertical, firstDp, secondDp);
+ }
+
+ private boolean crossesSmallestSizeThreshold(int firstDp, int secondDp) {
+ return crossesSizeThreshold(mSmallest, firstDp, secondDp);
+ }
+
+ /**
+ * The purpose of this method is to decide whether the activity needs to be relaunched upon
+ * changing its size. In most cases the activities don't need to be relaunched, if the resize
+ * is small, all the activity content has to do is relayout itself within new bounds. There are
+ * cases however, where the activity's content would be completely changed in the new size and
+ * the full relaunch is required.
+ *
+ * The activity will report to us vertical and horizontal thresholds after which a relaunch is
+ * required. These thresholds are collected from the application resource qualifiers. For
+ * example, if application has layout-w600dp resource directory, then it needs a relaunch when
+ * we resize from width of 650dp to 550dp, as it crosses the 600dp threshold. However, if
+ * it resizes width from 620dp to 700dp, it won't be relaunched as it stays on the same side
+ * of the threshold.
+ */
+ private static boolean crossesSizeThreshold(int[] thresholds, int firstDp,
+ int secondDp) {
+ if (thresholds == null) {
+ return false;
+ }
+ for (int i = thresholds.length - 1; i >= 0; i--) {
+ final int threshold = thresholds[i];
+ if ((firstDp < threshold && secondDp >= threshold)
+ || (firstDp >= threshold && secondDp < threshold)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(mHorizontal) + " " + Arrays.toString(mVertical) + " "
+ + Arrays.toString(mSmallest);
+ }
+
+
+
+ // 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/window/SizeConfigurationBuckets.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new SizeConfigurationBuckets.
+ *
+ * @param horizontal
+ * Horizontal (screenWidthDp) buckets
+ * @param vertical
+ * Vertical (screenHeightDp) buckets
+ * @param smallest
+ * Smallest (smallestScreenWidthDp) buckets
+ */
+ @DataClass.Generated.Member
+ public SizeConfigurationBuckets(
+ @Nullable int[] horizontal,
+ @Nullable int[] vertical,
+ @Nullable int[] smallest) {
+ this.mHorizontal = horizontal;
+ this.mVertical = vertical;
+ this.mSmallest = smallest;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Horizontal (screenWidthDp) buckets
+ */
+ @DataClass.Generated.Member
+ public @Nullable int[] getHorizontal() {
+ return mHorizontal;
+ }
+
+ /**
+ * Vertical (screenHeightDp) buckets
+ */
+ @DataClass.Generated.Member
+ public @Nullable int[] getVertical() {
+ return mVertical;
+ }
+
+ /**
+ * Smallest (smallestScreenWidthDp) buckets
+ */
+ @DataClass.Generated.Member
+ public @Nullable int[] getSmallest() {
+ return mSmallest;
+ }
+
+ @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 (mHorizontal != null) flg |= 0x1;
+ if (mVertical != null) flg |= 0x2;
+ if (mSmallest != null) flg |= 0x4;
+ dest.writeByte(flg);
+ if (mHorizontal != null) dest.writeIntArray(mHorizontal);
+ if (mVertical != null) dest.writeIntArray(mVertical);
+ if (mSmallest != null) dest.writeIntArray(mSmallest);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ SizeConfigurationBuckets(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int[] horizontal = (flg & 0x1) == 0 ? null : in.createIntArray();
+ int[] vertical = (flg & 0x2) == 0 ? null : in.createIntArray();
+ int[] smallest = (flg & 0x4) == 0 ? null : in.createIntArray();
+
+ this.mHorizontal = horizontal;
+ this.mVertical = vertical;
+ this.mSmallest = smallest;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<SizeConfigurationBuckets> CREATOR
+ = new Parcelable.Creator<SizeConfigurationBuckets>() {
+ @Override
+ public SizeConfigurationBuckets[] newArray(int size) {
+ return new SizeConfigurationBuckets[size];
+ }
+
+ @Override
+ public SizeConfigurationBuckets createFromParcel(@NonNull android.os.Parcel in) {
+ return new SizeConfigurationBuckets(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1615845864280L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/window/SizeConfigurationBuckets.java",
+ inputSignatures = "private final @android.annotation.Nullable int[] mHorizontal\nprivate final @android.annotation.Nullable int[] mVertical\nprivate final @android.annotation.Nullable int[] mSmallest\npublic static int filterDiff(int,android.content.res.Configuration,android.content.res.Configuration,android.window.SizeConfigurationBuckets)\nprivate boolean crossesHorizontalSizeThreshold(int,int)\nprivate boolean crossesVerticalSizeThreshold(int,int)\nprivate boolean crossesSmallestSizeThreshold(int,int)\nprivate static boolean crossesSizeThreshold(int[],int,int)\npublic @java.lang.Override java.lang.String toString()\nclass SizeConfigurationBuckets extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index da445b8..ddea64a 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -23,6 +23,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -46,8 +47,6 @@
import com.android.internal.R;
import com.android.internal.policy.DecorView;
-import java.util.function.Consumer;
-
/**
* <p>The view which allows an activity to customize its splash screen exit animation.</p>
*
@@ -79,8 +78,8 @@
private Animatable mAnimatableIcon;
private ValueAnimator mAnimator;
- private Runnable mAnimationFinishListener;
- private Consumer<Canvas> mOnDrawCallback;
+ // The host activity when transfer view to it.
+ private Activity mHostActivity;
// cache original window and status
private Window mWindow;
private boolean mDrawBarBackground;
@@ -334,6 +333,17 @@
restoreSystemUIColors();
mWindow = null;
}
+ if (mHostActivity != null) {
+ mHostActivity.detachSplashScreenView();
+ }
+ }
+
+ /**
+ * Called when this view is attached to an activity.
+ * @hide
+ */
+ public void attachHostActivity(Activity activity) {
+ mHostActivity = activity;
}
/**
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 499ce25..a0b4e24 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -16,6 +16,7 @@
package android.window;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -31,6 +32,7 @@
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -284,6 +286,8 @@
private final Rect mEndAbsBounds = new Rect();
private final Point mEndRelOffset = new Point();
private ActivityManager.RunningTaskInfo mTaskInfo = null;
+ private int mStartRotation = ROTATION_UNDEFINED;
+ private int mEndRotation = ROTATION_UNDEFINED;
public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
mContainer = container;
@@ -301,6 +305,8 @@
mEndAbsBounds.readFromParcel(in);
mEndRelOffset.readFromParcel(in);
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+ mStartRotation = in.readInt();
+ mEndRotation = in.readInt();
}
/** Sets the parent of this change's container. The parent must be a participant or null. */
@@ -341,6 +347,12 @@
mTaskInfo = taskInfo;
}
+ /** Sets the start and end rotation of this container. */
+ public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
+ mStartRotation = start;
+ mEndRotation = end;
+ }
+
/** @return the container that is changing. May be null if non-remotable (eg. activity) */
@Nullable
public WindowContainerToken getContainer() {
@@ -404,6 +416,14 @@
return mTaskInfo;
}
+ public int getStartRotation() {
+ return mStartRotation;
+ }
+
+ public int getEndRotation() {
+ return mEndRotation;
+ }
+
@Override
/** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -416,6 +436,8 @@
mEndAbsBounds.writeToParcel(dest, flags);
mEndRelOffset.writeToParcel(dest, flags);
dest.writeTypedObject(mTaskInfo, flags);
+ dest.writeInt(mStartRotation);
+ dest.writeInt(mEndRotation);
}
@NonNull
@@ -442,7 +464,8 @@
public String toString() {
return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
- + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + "}";
+ + mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
+ + mStartRotation + "->" + mEndRotation + "}";
}
}
}
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index c2e1426..7baa53b 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -16,6 +16,9 @@
package com.android.internal.accessibility.util;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -28,6 +31,10 @@
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_FULL_SCREEN;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_WINDOW;
import android.content.ComponentName;
import android.view.accessibility.AccessibilityManager;
@@ -113,6 +120,19 @@
UNKNOWN_STATUS);
}
+ /**
+ * Logs the magnification activated mode and its duration of the usage.
+ * Calls this when the magnification is disabled.
+ *
+ * @param mode The activated magnification mode.
+ * @param duration The duration in milliseconds during the magnification is activated.
+ */
+ public static void logMagnificationUsageState(int mode, long duration) {
+ FrameworkStatsLog.write(FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED,
+ convertToLoggingMagnificationMode(mode),
+ duration);
+ }
+
private static int convertToLoggingShortcutType(@ShortcutType int shortcutType) {
switch (shortcutType) {
case ACCESSIBILITY_BUTTON:
@@ -127,4 +147,18 @@
return enabled ? ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__ENABLED
: ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
}
+
+ private static int convertToLoggingMagnificationMode(int mode) {
+ switch (mode) {
+ case ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_FULL_SCREEN;
+ case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_WINDOW;
+ case ACCESSIBILITY_MAGNIFICATION_MODE_ALL:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
+
+ default:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE;
+ }
+ }
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index ee98878..52801fa 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -474,6 +474,12 @@
public static final String SHARE_USE_SERVICE_TARGETS = "share_use_service_targets";
+ /*
+ * (long) The duration that the home button must be pressed before triggering Assist
+ */
+ public static final String HOME_BUTTON_LONG_PRESS_DURATION_MS =
+ "home_button_long_press_duration_ms";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index e602cd2..a60b310 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -504,8 +504,10 @@
final File visibleFile = getFileForDocId(documentId, true);
final int pfdMode = ParcelFileDescriptor.parseMode(mode);
- if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
- return openFileForRead(file);
+ if (visibleFile == null) {
+ return ParcelFileDescriptor.open(file, pfdMode);
+ } else if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY) {
+ return openFileForRead(visibleFile);
} else {
try {
// When finished writing, kick off media scanner
@@ -522,6 +524,10 @@
private ParcelFileDescriptor openFileForRead(final File target) throws FileNotFoundException {
final Uri uri = MediaStore.scanFile(getContext().getContentResolver(), target);
+ if (uri == null) {
+ Log.w(TAG, "Failed to retrieve media store URI for: " + target);
+ return ParcelFileDescriptor.open(target, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
// Passing the calling uid via EXTRA_MEDIA_CAPABILITIES_UID, so that the decision to
// transcode or not transcode can be made based upon the calling app's uid, and not based
@@ -532,7 +538,8 @@
final AssetFileDescriptor afd =
getContext().getContentResolver().openTypedAssetFileDescriptor(uri, "*/*", opts);
if (afd == null) {
- return null;
+ Log.w(TAG, "Failed to open with media_capabilities uid for URI: " + uri);
+ return ParcelFileDescriptor.open(target, ParcelFileDescriptor.MODE_READ_ONLY);
}
return afd.getParcelFileDescriptor();
diff --git a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
index b4f216b..1d865c2 100644
--- a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
@@ -75,7 +75,7 @@
// Note: they don't _have_ to be ignored, for example, we could instead turn them
// opaque. Traditionally, including outside Android, quantizers ignore transparent
// pixels, so that strategy was chosen.
- int alpha = (pixel >> 24);
+ int alpha = (pixel >> 24) & 0xff;
if (alpha < 255) {
continue;
}
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 2237efc..2f40d3b 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -58,6 +58,7 @@
public static String SYSTEM_CHANGES = "SYSTEM_CHANGES";
public static String DO_NOT_DISTURB = "DO_NOT_DISTURB";
public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
+ public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -199,6 +200,12 @@
newFeaturePrompt.setBlockable(true);
channelsList.add(newFeaturePrompt);
+ final NotificationChannel accessibilitySecurityPolicyChannel = new NotificationChannel(
+ ACCESSIBILITY_SECURITY_POLICY,
+ context.getString(R.string.notification_channel_accessibility_security_policy),
+ NotificationManager.IMPORTANCE_LOW);
+ channelsList.add(accessibilitySecurityPolicyChannel);
+
nm.createNotificationChannels(channelsList);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9ecb0ad..7f87885 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -169,7 +169,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 194;
+ static final int VERSION = 195;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -1009,8 +1009,12 @@
protected @Nullable MeasuredEnergyStats mGlobalMeasuredEnergyStats;
/** Last known screen state. Needed for apportioning display energy. */
int mScreenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN;
+ /** Bluetooth Power calculator for attributing measured bluetooth charge consumption to uids */
+ @Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null;
/** Cpu Power calculator for attributing measured cpu charge consumption to uids */
@Nullable CpuPowerCalculator mCpuPowerCalculator = null;
+ /** Wifi Power calculator for attributing measured wifi charge consumption to uids */
+ @Nullable WifiPowerCalculator mWifiPowerCalculator = null;
/**
* These provide time bases that discount the time the device is plugged
@@ -6967,6 +6971,16 @@
}
@Override
+ public long getBluetoothMeasuredBatteryConsumptionUC() {
+ return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH);
+ }
+
+ @Override
+ public long getCpuMeasuredBatteryConsumptionUC() {
+ return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU);
+ }
+
+ @Override
public long getScreenOnMeasuredBatteryConsumptionUC() {
return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
}
@@ -6976,6 +6990,11 @@
return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE);
}
+ @Override
+ public long getWifiMeasuredBatteryConsumptionUC() {
+ return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI);
+ }
+
/**
* Returns the consumption (in microcoulombs) that the given standard power bucket consumed.
* Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable
@@ -7810,6 +7829,26 @@
return mUidMeasuredEnergyStats.getAccumulatedCustomBucketCharges();
}
+ @Override
+ public long getBluetoothMeasuredBatteryConsumptionUC() {
+ return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH);
+ }
+
+ @Override
+ public long getCpuMeasuredBatteryConsumptionUC() {
+ return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU);
+ }
+
+ @Override
+ public long getScreenOnMeasuredBatteryConsumptionUC() {
+ return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
+ }
+
+ @Override
+ public long getWifiMeasuredBatteryConsumptionUC() {
+ return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI);
+ }
+
/**
* Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
* since last marked. Also sets the mark time for both these timers.
@@ -8477,11 +8516,6 @@
}
}
- @Override
- public long getScreenOnMeasuredBatteryConsumptionUC() {
- return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
- }
-
void initNetworkActivityLocked() {
detachIfNotNull(mNetworkByteActivityCounters);
mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
@@ -11427,7 +11461,7 @@
* @param info The energy information from the WiFi controller.
*/
public void updateWifiState(@Nullable final WifiActivityEnergyInfo info,
- long elapsedRealtimeMs, long uptimeMs) {
+ final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) {
if (DEBUG_ENERGY) {
Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces));
}
@@ -11449,9 +11483,21 @@
if (delta != null) {
mNetworkStatsPool.release(delta);
}
+ if (mIgnoreNextExternalStats) {
+ // TODO: Strictly speaking, we should re-mark all 5 timers for each uid (and the
+ // global one) here like we do for display. But I'm not sure it's worth the
+ // complicated code for a codepath that shouldn't ever actually happen in real
+ // life.
+ }
return;
}
+ final SparseDoubleArray uidEstimatedConsumptionMah =
+ (mGlobalMeasuredEnergyStats != null
+ && mWifiPowerCalculator != null && consumedChargeUC > 0) ?
+ new SparseDoubleArray() : null;
+ double totalEstimatedConsumptionMah = 0;
+
SparseLongArray rxPackets = new SparseLongArray();
SparseLongArray txPackets = new SparseLongArray();
long totalTxPackets = 0;
@@ -11486,6 +11532,7 @@
mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
entry.rxPackets);
+ // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum?
rxPackets.put(u.getUid(), entry.rxPackets);
// Sum the total number of packets so that the Rx Power can
@@ -11505,12 +11552,42 @@
mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
entry.txPackets);
+ // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum?
txPackets.put(u.getUid(), entry.txPackets);
// Sum the total number of packets so that the Tx Power can
// be evenly distributed amongst the apps.
totalTxPackets += entry.txPackets;
}
+
+ // Calculate consumed energy for this uid. Only do so if WifiReporting isn't
+ // enabled (if it is, we'll do it later instead using info).
+ if (uidEstimatedConsumptionMah != null && info == null && !mHasWifiReporting) {
+ final long uidRunningMs = u.mWifiRunningTimer
+ .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000;
+ if (uidRunningMs > 0) u.mWifiRunningTimer.setMark(elapsedRealtimeMs);
+
+ final long uidScanMs = u.mWifiScanTimer
+ .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000;
+ if (uidScanMs > 0) u.mWifiScanTimer.setMark(elapsedRealtimeMs);
+
+ long uidBatchScanMs = 0;
+ for (int bn = 0; bn < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bn++) {
+ if (u.mWifiBatchedScanTimer[bn] != null) {
+ long bnMs = u.mWifiBatchedScanTimer[bn]
+ .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000;
+ if (bnMs > 0) {
+ u.mWifiBatchedScanTimer[bn].setMark(elapsedRealtimeMs);
+ }
+ uidBatchScanMs += bnMs;
+ }
+ }
+
+ uidEstimatedConsumptionMah.add(u.getUid(),
+ mWifiPowerCalculator.calcPowerWithoutControllerDataMah(
+ entry.rxPackets, entry.txPackets,
+ uidRunningMs, uidScanMs, uidBatchScanMs));
+ }
}
mNetworkStatsPool.release(delta);
delta = null;
@@ -11571,15 +11648,14 @@
for (int i = 0; i < uidStatsSize; i++) {
final Uid uid = mUidStats.valueAt(i);
- long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
+ final long scanTimeSinceMarkMs = uid.mWifiScanTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
+ long scanRxTimeSinceMarkMs = scanTimeSinceMarkMs; // not final
+ long scanTxTimeSinceMarkMs = scanTimeSinceMarkMs; // not final
if (scanTimeSinceMarkMs > 0) {
// Set the new mark so that next time we get new data since this point.
uid.mWifiScanTimer.setMark(elapsedRealtimeMs);
- long scanRxTimeSinceMarkMs = scanTimeSinceMarkMs;
- long scanTxTimeSinceMarkMs = scanTimeSinceMarkMs;
-
// Our total scan time is more than the reported Tx/Rx time.
// This is possible because the cost of a scan is approximate.
// Let's normalize the result so that we evenly blame each app
@@ -11613,6 +11689,7 @@
// Distribute evenly the power consumed while Idle to each app holding a WiFi
// lock.
+ long myIdleTimeMs = 0;
final long wifiLockTimeSinceMarkMs =
uid.mFullWifiLockTimer.getTimeSinceMarkLocked(
elapsedRealtimeMs * 1000) / 1000;
@@ -11620,8 +11697,7 @@
// Set the new mark so that next time we get new data since this point.
uid.mFullWifiLockTimer.setMark(elapsedRealtimeMs);
- final long myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs)
- / totalWifiLockTimeMs;
+ myIdleTimeMs = (wifiLockTimeSinceMarkMs * idleTimeMs) / totalWifiLockTimeMs;
if (DEBUG_ENERGY) {
Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": "
+ myIdleTimeMs + " ms");
@@ -11629,6 +11705,12 @@
uid.getOrCreateWifiControllerActivityLocked().getIdleTimeCounter()
.addCountLocked(myIdleTimeMs);
}
+
+ if (uidEstimatedConsumptionMah != null) {
+ double uidEstMah = mWifiPowerCalculator.calcPowerFromControllerDataMah(
+ scanRxTimeSinceMarkMs, scanTxTimeSinceMarkMs, myIdleTimeMs);
+ uidEstimatedConsumptionMah.add(uid.getUid(), uidEstMah);
+ }
}
if (DEBUG_ENERGY) {
@@ -11648,6 +11730,11 @@
}
uid.getOrCreateWifiControllerActivityLocked().getTxTimeCounters()[0]
.addCountLocked(myTxTimeMs);
+ if (uidEstimatedConsumptionMah != null) {
+ uidEstimatedConsumptionMah.add(uid.getUid(),
+ mWifiPowerCalculator.calcPowerFromControllerDataMah(
+ 0, myTxTimeMs, 0));
+ }
}
// Distribute the remaining Rx power appropriately between all apps that received
@@ -11662,6 +11749,11 @@
}
uid.getOrCreateWifiControllerActivityLocked().getRxTimeCounter()
.addCountLocked(myRxTimeMs);
+ if (uidEstimatedConsumptionMah != null) {
+ uidEstimatedConsumptionMah.add(uid.getUid(),
+ mWifiPowerCalculator.calcPowerFromControllerDataMah(
+ myRxTimeMs, 0, 0));
+ }
}
// Any left over power use will be picked up by the WiFi category in BatteryStatsHelper.
@@ -11680,10 +11772,11 @@
// POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
final double opVolt = mPowerProfile.getAveragePower(
PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+ double controllerMaMs = 0;
if (opVolt != 0) {
// We store the power drain as mAms.
- mWifiActivity.getPowerCounter().addCountLocked(
- (long) (info.getControllerEnergyUsedMicroJoules() / opVolt));
+ controllerMaMs = info.getControllerEnergyUsedMicroJoules() / opVolt;
+ mWifiActivity.getPowerCounter().addCountLocked((long) controllerMaMs);
}
// Converting uWs to mAms.
// Conversion: (uWs * (1000ms / 1s) * (1mW / 1000uW)) / mV = mAms
@@ -11695,6 +11788,29 @@
(monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mTmpRailStats.resetWifiTotalEnergyUsed();
+
+ if (uidEstimatedConsumptionMah != null) {
+ totalEstimatedConsumptionMah = Math.max(controllerMaMs / MILLISECONDS_IN_HOUR,
+ mWifiPowerCalculator.calcPowerFromControllerDataMah(
+ rxTimeMs, txTimeMs, idleTimeMs));
+ }
+ }
+
+ // Update the MeasuredEnergyStats information.
+ if (uidEstimatedConsumptionMah != null) {
+ mGlobalMeasuredEnergyStats.updateStandardBucket(
+ MeasuredEnergyStats.POWER_BUCKET_WIFI, consumedChargeUC);
+
+ // Now calculate the consumption for each uid, according to its proportional usage.
+ if (!mHasWifiReporting) {
+ final long globalTimeMs = mGlobalWifiRunningTimer
+ .getTimeSinceMarkLocked(elapsedRealtimeMs * 1000) / 1000;
+ mGlobalWifiRunningTimer.setMark(elapsedRealtimeMs);
+ totalEstimatedConsumptionMah = mWifiPowerCalculator
+ .calcGlobalPowerWithoutControllerDataMah(globalTimeMs);
+ }
+ distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_WIFI,
+ consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah);
}
}
}
@@ -11938,7 +12054,7 @@
* @param info The energy information from the bluetooth controller.
*/
public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info,
- long elapsedRealtimeMs, long uptimeMs) {
+ final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) {
if (DEBUG_ENERGY) {
Slog.d(TAG, "Updating bluetooth stats: " + info);
}
@@ -11970,6 +12086,11 @@
Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms");
}
+ final SparseDoubleArray uidEstimatedConsumptionMah =
+ (mGlobalMeasuredEnergyStats != null
+ && mBluetoothPowerCalculator != null && consumedChargeUC > 0) ?
+ new SparseDoubleArray() : null;
+
long totalScanTimeMs = 0;
final int uidCount = mUidStats.size();
@@ -12028,6 +12149,12 @@
counter.getRxTimeCounter().addCountLocked(scanTimeRxSinceMarkMs);
counter.getTxTimeCounters()[0].addCountLocked(scanTimeTxSinceMarkMs);
+ if (uidEstimatedConsumptionMah != null) {
+ uidEstimatedConsumptionMah.add(u.getUid(),
+ mBluetoothPowerCalculator.calculatePowerMah(
+ scanTimeRxSinceMarkMs, scanTimeTxSinceMarkMs, 0));
+ }
+
leftOverRxTimeMs -= scanTimeRxSinceMarkMs;
leftOverTxTimeMs -= scanTimeTxSinceMarkMs;
}
@@ -12088,6 +12215,11 @@
Slog.d(TAG, "UID=" + uid + " rx_bytes=" + rxBytes + " rx_time=" + timeRxMs);
}
counter.getRxTimeCounter().addCountLocked(timeRxMs);
+
+ if (uidEstimatedConsumptionMah != null) {
+ uidEstimatedConsumptionMah.add(u.getUid(),
+ mBluetoothPowerCalculator.calculatePowerMah(timeRxMs, 0, 0));
+ }
}
if (totalTxBytes > 0 && txBytes > 0) {
@@ -12096,6 +12228,11 @@
Slog.d(TAG, "UID=" + uid + " tx_bytes=" + txBytes + " tx_time=" + timeTxMs);
}
counter.getTxTimeCounters()[0].addCountLocked(timeTxMs);
+
+ if (uidEstimatedConsumptionMah != null) {
+ uidEstimatedConsumptionMah.add(u.getUid(),
+ mBluetoothPowerCalculator.calculatePowerMah(0, timeTxMs, 0));
+ }
}
}
}
@@ -12107,12 +12244,26 @@
// POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
final double opVolt = mPowerProfile.getAveragePower(
PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+ double controllerMaMs = 0;
if (opVolt != 0) {
+ controllerMaMs = (info.getControllerEnergyUsed() - mLastBluetoothActivityInfo.energy)
+ / opVolt;
// We store the power drain as mAms.
- mBluetoothActivity.getPowerCounter().addCountLocked(
- (long) ((info.getControllerEnergyUsed() - mLastBluetoothActivityInfo.energy)
- / opVolt));
+ mBluetoothActivity.getPowerCounter().addCountLocked((long) controllerMaMs);
}
+
+ // Update the MeasuredEnergyStats information.
+ if (uidEstimatedConsumptionMah != null) {
+ mGlobalMeasuredEnergyStats.updateStandardBucket(
+ MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, consumedChargeUC);
+
+ double totalEstimatedMah
+ = mBluetoothPowerCalculator.calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
+ totalEstimatedMah = Math.max(totalEstimatedMah, controllerMaMs / MILLISECONDS_IN_HOUR);
+ distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
+ consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedMah);
+ }
+
mLastBluetoothActivityInfo.set(info);
}
@@ -12301,37 +12452,17 @@
// 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.
- long totalFgTimeMs = 0L;
- final ArrayMap<Uid, Long> fgTimeMsArray = new ArrayMap<>();
+ 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)
final int uidStatsSize = mUidStats.size();
for (int i = 0; i < uidStatsSize; i++) {
final Uid uid = mUidStats.valueAt(i);
- final long fgTimeMs = uid.markProcessForegroundTimeUs(elapsedRealtimeMs, true) / 1000;
- if (fgTimeMs == 0) continue;
- fgTimeMsArray.put(uid, fgTimeMs);
- totalFgTimeMs += fgTimeMs;
+ final long fgTimeUs = uid.markProcessForegroundTimeUs(elapsedRealtimeMs, true);
+ if (fgTimeUs == 0) continue;
+ fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs);
}
- long totalDisplayChargeMC = chargeUC / 1000; // not final
-
- // Actually assign and distribute power usage to apps based on their fg time since mark.
- // TODO(b/175726326): Decide on 'energy' units and make sure algorithm won't overflow.
- final long fgTimeArraySize = fgTimeMsArray.size();
- for (int i = 0; i < fgTimeArraySize; i++) {
- final Uid uid = fgTimeMsArray.keyAt(i);
- final long fgTimeMs = fgTimeMsArray.valueAt(i);
-
- // Using long division: "appEnergy = totalEnergy * appFg/totalFg + 0.5" with rounding
- final long appDisplayChargeMC =
- (totalDisplayChargeMC * fgTimeMs + (totalFgTimeMs / 2))
- / totalFgTimeMs;
- uid.addChargeToStandardBucketLocked(appDisplayChargeMC * 1000, powerBucket);
-
- // To mitigate round-off errors, remove this app from numerator & denominator totals
- totalDisplayChargeMC -= appDisplayChargeMC;
- totalFgTimeMs -= fgTimeMs;
- }
+ distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0);
}
/**
@@ -12379,6 +12510,119 @@
}
/**
+ * Attributes energy (for the given bucket) to each uid according to the following formula:
+ * blamedEnergy[uid] = totalEnergy * ratioNumerators[uid] / ratioDenominator;
+ * <p>Does nothing if ratioDenominator is 0.
+ *
+ * <p>Here, ratioDenominator = max(sumOfAllRatioNumerators, minRatioDenominator),
+ * so if given minRatioDenominator <= 0, then sumOfAllRatioNumerators will be used implicitly.
+ *
+ * <p>Note that ratioNumerators and minRatioDenominator must use the same units, but need not
+ * use the same units as totalConsumedChargeUC (which must be in microcoulombs).
+ *
+ * <p>A consequence of minRatioDenominator is that the sum over all uids might be less than
+ * totalConsumedChargeUC. This is intentional; the remainder is purposefully unnaccounted rather
+ * than incorrectly blamed on uids, and implies unknown (non-uid) sources of drain.
+ *
+ * <p>All uids in ratioNumerators must exist in mUidStats already.
+ */
+ private void distributeEnergyToUidsLocked(@StandardPowerBucket int bucket,
+ long totalConsumedChargeUC, SparseDoubleArray ratioNumerators,
+ double minRatioDenominator) {
+
+ // If the sum of all app usage was greater than the total, use that instead:
+ double sumRatioNumerators = 0;
+ for (int i = ratioNumerators.size() - 1; i >= 0; i--) {
+ sumRatioNumerators += ratioNumerators.valueAt(i);
+ }
+ final double ratioDenominator = Math.max(sumRatioNumerators, minRatioDenominator);
+ if (ratioDenominator <= 0) return;
+
+ for (int i = ratioNumerators.size() - 1; i >= 0; i--) {
+ final Uid uid = getAvailableUidStatsLocked(ratioNumerators.keyAt(i));
+ final double ratioNumerator = ratioNumerators.valueAt(i);
+ final long uidActualUC
+ = (long) (totalConsumedChargeUC * ratioNumerator / ratioDenominator + 0.5);
+ uid.addChargeToStandardBucketLocked(uidActualUC, bucket);
+ }
+ }
+
+ /**
+ * 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() {
@@ -14212,15 +14456,20 @@
if (mGlobalMeasuredEnergyStats == null) {
mGlobalMeasuredEnergyStats =
new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
- return;
} else {
supportedBucketMismatch = !mGlobalMeasuredEnergyStats.isSupportEqualTo(
supportedStandardBuckets, numCustomBuckets);
}
+ if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH]) {
+ mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
+ }
if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU]) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
+ if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_WIFI]) {
+ mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
+ }
}
if (supportedBucketMismatch) {
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 7d42de4..db14034 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -15,8 +15,11 @@
*/
package com.android.internal.os;
+import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
+
import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryStats.ControllerActivityCounter;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
@@ -65,17 +68,19 @@
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
- calculateApp(app, total);
+ calculateApp(app, total, query);
if (app.getUid() == Process.BLUETOOTH_UID) {
app.excludeFromBatteryUsageStats();
systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
}
}
- final BatteryStats.ControllerActivityCounter activityCounter =
+ final long measuredChargeUC = query.shouldForceUsePowerProfileModel() ?
+ POWER_DATA_UNAVAILABLE : batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
+ final ControllerActivityCounter activityCounter =
batteryStats.getBluetoothControllerActivity();
final long systemDurationMs = calculateDuration(activityCounter);
- final double systemPowerMah = calculatePower(activityCounter);
+ final double systemPowerMah = calculatePowerMah(measuredChargeUC, activityCounter);
// Subtract what the apps used, but clamp to 0.
final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs);
@@ -91,11 +96,16 @@
systemComponentPowerMah);
}
- private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total) {
- final BatteryStats.ControllerActivityCounter activityCounter =
+ private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
+ BatteryUsageStatsQuery query) {
+
+ final long measuredChargeUC = query.shouldForceUsePowerProfileModel() ?
+ POWER_DATA_UNAVAILABLE :
+ app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC();
+ final ControllerActivityCounter activityCounter =
app.getBatteryStatsUid().getBluetoothControllerActivity();
final long durationMs = calculateDuration(activityCounter);
- final double powerMah = calculatePower(activityCounter);
+ final double powerMah = calculatePowerMah(measuredChargeUC, activityCounter);
app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_BLUETOOTH, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah);
@@ -121,10 +131,11 @@
}
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
- final BatteryStats.ControllerActivityCounter activityCounter =
+ final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
+ final ControllerActivityCounter activityCounter =
batteryStats.getBluetoothControllerActivity();
- final double systemPowerMah = calculatePower(activityCounter);
final long systemDurationMs = calculateDuration(activityCounter);
+ final double systemPowerMah = calculatePowerMah(measuredChargeUC, activityCounter);
// Subtract what the apps used, but clamp to 0.
final double powerMah = Math.max(0, systemPowerMah - total.powerMah);
@@ -152,10 +163,11 @@
private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
PowerAndDuration total) {
- final BatteryStats.ControllerActivityCounter activityCounter =
- u.getBluetoothControllerActivity();
+
+ final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC();
+ final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity();
final long durationMs = calculateDuration(activityCounter);
- final double powerMah = calculatePower(activityCounter);
+ final double powerMah = calculatePowerMah(measuredChargeUC, activityCounter);
app.bluetoothRunningTimeMs = durationMs;
app.bluetoothPowerMah = powerMah;
@@ -166,7 +178,7 @@
total.powerMah += powerMah;
}
- private long calculateDuration(BatteryStats.ControllerActivityCounter counter) {
+ private long calculateDuration(ControllerActivityCounter counter) {
if (counter == null) {
return 0;
}
@@ -176,7 +188,11 @@
+ counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
}
- private double calculatePower(BatteryStats.ControllerActivityCounter counter) {
+ /** Returns bluetooth power usage based on the best data available. */
+ private double calculatePowerMah(long measuredChargeUC, ControllerActivityCounter counter) {
+ if (measuredChargeUC != POWER_DATA_UNAVAILABLE) {
+ return uCtoMah(measuredChargeUC);
+ }
if (counter == null) {
return 0;
}
@@ -195,6 +211,11 @@
counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
final long txTimeMs =
counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+ return calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
+ }
+
+ /** Returns estimated bluetooth power usage based on usage times. */
+ public double calculatePowerMah(long rxTimeMs, long txTimeMs, long idleTimeMs) {
return ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
/ (1000 * 60 * 60);
}
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 97f727b..b15543a 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -85,12 +85,14 @@
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
- calculateApp(app, app.getBatteryStatsUid(), result);
+ calculateApp(app, app.getBatteryStatsUid(), query, result);
}
}
- private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, Result result) {
- calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED, result);
+ private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ BatteryUsageStatsQuery query, Result result) {
+ calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED,
+ query.shouldForceUsePowerProfileModel(), result);
app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, result.powerMah)
.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, result.durationMs)
@@ -112,7 +114,7 @@
}
private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) {
- calculatePowerAndDuration(u, statsType, result);
+ calculatePowerAndDuration(u, statsType, false, result);
app.cpuPowerMah = result.powerMah;
app.cpuTimeMs = result.durationMs;
@@ -120,46 +122,16 @@
app.packageWithHighestDrain = result.packageWithHighestDrain;
}
- private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType, Result result) {
+ private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType,
+ boolean forceUsePowerProfileModel, Result result) {
long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
- // Constant battery drain when CPU is active
- double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime());
-
- // Additional per-cluster battery drain
- long[] cpuClusterTimes = u.getCpuClusterTimes();
- if (cpuClusterTimes != null) {
- if (cpuClusterTimes.length == mNumCpuClusters) {
- for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
- double power = calculatePerCpuClusterPowerMah(cluster,
- cpuClusterTimes[cluster]);
- powerMah += power;
- if (DEBUG) {
- Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
- + " clusterTimeMs=" + cpuClusterTimes[cluster]
- + " power=" + formatCharge(power));
- }
- }
- } else {
- Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # "
- + mNumCpuClusters + " actual # " + cpuClusterTimes.length);
- }
- }
-
- // Additional per-frequency battery drain
- for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
- final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length;
- for (int speed = 0; speed < speedsForCluster; speed++) {
- final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
- final double power = calculatePerCpuFreqPowerMah(cluster, speed,
- timeUs / 1000);
- if (DEBUG) {
- Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
- + speed + " timeUs=" + timeUs + " power="
- + formatCharge(power));
- }
- powerMah += power;
- }
+ final double powerMah;
+ final long consumptionUC = u.getCpuMeasuredBatteryConsumptionUC();
+ if (forceUsePowerProfileModel || consumptionUC == BatteryStats.POWER_DATA_UNAVAILABLE) {
+ powerMah = calculateUidModeledPowerMah(u, statsType);
+ } else {
+ powerMah = uCtoMah(consumptionUC);
}
if (DEBUG && (durationMs != 0 || powerMah != 0)) {
@@ -208,6 +180,48 @@
result.packageWithHighestDrain = packageWithHighestDrain;
}
+ private double calculateUidModeledPowerMah(BatteryStats.Uid u, int statsType) {
+ // Constant battery drain when CPU is active
+ double powerMah = calculateActiveCpuPowerMah(u.getCpuActiveTime());
+
+ // Additional per-cluster battery drain
+ long[] cpuClusterTimes = u.getCpuClusterTimes();
+ if (cpuClusterTimes != null) {
+ if (cpuClusterTimes.length == mNumCpuClusters) {
+ for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+ double power = calculatePerCpuClusterPowerMah(cluster,
+ cpuClusterTimes[cluster]);
+ powerMah += power;
+ if (DEBUG) {
+ Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
+ + " clusterTimeMs=" + cpuClusterTimes[cluster]
+ + " power=" + formatCharge(power));
+ }
+ }
+ } else {
+ Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # "
+ + mNumCpuClusters + " actual # " + cpuClusterTimes.length);
+ }
+ }
+
+ // Additional per-frequency battery drain
+ for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+ final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length;
+ for (int speed = 0; speed < speedsForCluster; speed++) {
+ final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
+ final double power = calculatePerCpuFreqPowerMah(cluster, speed,
+ timeUs / 1000);
+ if (DEBUG) {
+ Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
+ + speed + " timeUs=" + timeUs + " power="
+ + formatCharge(power));
+ }
+ powerMah += power;
+ }
+ }
+ return powerMah;
+ }
+
/**
* Calculates active CPU power consumption.
*
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index 791e9ad..14cdb08 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -1,7 +1,11 @@
{
"presubmit": [
{
- "file_patterns": ["Battery[^/]*\\.java"],
+ "file_patterns": [
+ "Battery[^/]*\\.java",
+ "Kernel[^/]*\\.java",
+ "[^/]*Power[^/]*\\.java"
+ ],
"name": "FrameworksCoreTests",
"options": [
{ "include-filter": "com.android.internal.os.BatteryStatsTests" },
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 98f613f..b6bfde7 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -15,6 +15,8 @@
*/
package com.android.internal.os;
+import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
+
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
@@ -79,11 +81,6 @@
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
- // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point,
- // so always check this field.
- final boolean hasWifiPowerReporting =
- mHasWifiPowerController && batteryStats.hasWifiActivityReporting();
-
final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
builder.getOrCreateSystemBatteryConsumerBuilder(
SystemBatteryConsumer.DRAIN_TYPE_WIFI);
@@ -97,7 +94,8 @@
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED,
- hasWifiPowerReporting);
+ batteryStats.hasWifiActivityReporting(),
+ query.shouldForceUsePowerProfileModel());
totalAppDurationMs += powerDurationAndTraffic.durationMs;
totalAppPowerMah += powerDurationAndTraffic.powerMah;
@@ -115,7 +113,9 @@
calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED,
- hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah);
+ batteryStats.hasWifiActivityReporting(),
+ query.shouldForceUsePowerProfileModel(),
+ totalAppDurationMs, totalAppPowerMah);
systemBatteryConsumerBuilder
.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI,
@@ -135,11 +135,6 @@
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
- // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point,
- // so always check this field.
- final boolean hasWifiPowerReporting =
- mHasWifiPowerController && batteryStats.hasWifiActivityReporting();
-
final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0);
long totalAppDurationMs = 0;
@@ -149,7 +144,7 @@
final BatterySipper app = sippers.get(i);
if (app.drainType == BatterySipper.DrainType.APP) {
calculateApp(powerDurationAndTraffic, app.uidObj, rawRealtimeUs, statsType,
- hasWifiPowerReporting);
+ batteryStats.hasWifiActivityReporting(), /* force use power model*/ false);
totalAppDurationMs += powerDurationAndTraffic.durationMs;
totalAppPowerMah += powerDurationAndTraffic.powerMah;
@@ -169,7 +164,8 @@
}
calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, statsType,
- hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah);
+ batteryStats.hasWifiActivityReporting(), /* force use power model*/ false,
+ totalAppDurationMs, totalAppPowerMah);
bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs;
bs.wifiPowerMah += powerDurationAndTraffic.powerMah;
@@ -180,8 +176,9 @@
}
private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic, BatteryStats.Uid u,
- long rawRealtimeUs,
- int statsType, boolean hasWifiPowerReporting) {
+ long rawRealtimeUs, int statsType,
+ boolean hasWifiActivityReporting, boolean shouldForceUsePowerProfileModel) {
+
powerDurationAndTraffic.wifiRxPackets = u.getNetworkActivityPackets(
BatteryStats.NETWORK_WIFI_RX_DATA,
statsType);
@@ -195,7 +192,14 @@
BatteryStats.NETWORK_WIFI_TX_DATA,
statsType);
- if (hasWifiPowerReporting) {
+ final long measuredChargeUC = u.getWifiMeasuredBatteryConsumptionUC();
+ final boolean isMeasuredPowerAvailable
+ = !shouldForceUsePowerProfileModel && measuredChargeUC != POWER_DATA_UNAVAILABLE;
+ if (isMeasuredPowerAvailable) {
+ powerDurationAndTraffic.powerMah = uCtoMah(measuredChargeUC);
+ }
+
+ if (hasWifiActivityReporting && mHasWifiPowerController) {
final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity();
if (counter != null) {
final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType);
@@ -203,9 +207,10 @@
final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType);
powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime;
- powerDurationAndTraffic.powerMah = mIdlePowerEstimator.calculatePower(idleTime)
- + mTxPowerEstimator.calculatePower(txTime)
- + mRxPowerEstimator.calculatePower(rxTime);
+ if (!isMeasuredPowerAvailable) {
+ powerDurationAndTraffic.powerMah
+ = calcPowerFromControllerDataMah(rxTime, txTime, idleTime);
+ }
if (DEBUG && powerDurationAndTraffic.powerMah != 0) {
Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime
@@ -214,21 +219,20 @@
}
}
} else {
- final double wifiPacketPower = (
- powerDurationAndTraffic.wifiRxPackets + powerDurationAndTraffic.wifiTxPackets)
- * mWifiPowerPerPacket;
final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
- final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000;
- long batchScanTimeMs = 0;
- for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
- batchScanTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000;
- }
-
powerDurationAndTraffic.durationMs = wifiRunningTime;
- powerDurationAndTraffic.powerMah = wifiPacketPower
- + mPowerOnPowerEstimator.calculatePower(wifiRunningTime)
- + mScanPowerEstimator.calculatePower(wifiScanTimeMs)
- + mBatchScanPowerEstimator.calculatePower(batchScanTimeMs);
+
+ if (!isMeasuredPowerAvailable) {
+ final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000;
+ long batchTimeMs = 0;
+ for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
+ batchTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000;
+ }
+ powerDurationAndTraffic.powerMah = calcPowerWithoutControllerDataMah(
+ powerDurationAndTraffic.wifiRxPackets,
+ powerDurationAndTraffic.wifiTxPackets,
+ wifiRunningTime, wifiScanTimeMs, batchTimeMs);
+ }
if (DEBUG && powerDurationAndTraffic.powerMah != 0) {
Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge(
@@ -238,12 +242,20 @@
}
private void calculateRemaining(PowerDurationAndTraffic powerDurationAndTraffic,
- BatteryStats stats, long rawRealtimeUs,
- int statsType, boolean hasWifiPowerReporting, long totalAppDurationMs,
- double totalAppPowerMah) {
+ BatteryStats stats, long rawRealtimeUs, int statsType,
+ boolean hasWifiActivityReporting, boolean shouldForceUsePowerProfileModel,
+ long totalAppDurationMs, double totalAppPowerMah) {
+
long totalDurationMs;
- double totalPowerMah;
- if (hasWifiPowerReporting) {
+ double totalPowerMah = 0;
+
+ final long measuredChargeUC = stats.getWifiMeasuredBatteryConsumptionUC();
+ final boolean isMeasuredPowerAvailable
+ = !shouldForceUsePowerProfileModel && measuredChargeUC != POWER_DATA_UNAVAILABLE;
+ if (isMeasuredPowerAvailable) {
+ totalPowerMah = uCtoMah(measuredChargeUC);
+ }
+ if (hasWifiActivityReporting && mHasWifiPowerController) {
final BatteryStats.ControllerActivityCounter counter =
stats.getWifiControllerActivity();
@@ -253,17 +265,19 @@
totalDurationMs = idleTimeMs + rxTimeMs + txTimeMs;
- totalPowerMah =
- counter.getPowerCounter().getCountLocked(statsType) / (double) (1000 * 60 * 60);
- if (totalPowerMah == 0) {
- // Some controllers do not report power drain, so we can calculate it here.
- totalPowerMah = mIdlePowerEstimator.calculatePower(idleTimeMs)
- + mTxPowerEstimator.calculatePower(txTimeMs)
- + mRxPowerEstimator.calculatePower(rxTimeMs);
+ if (!isMeasuredPowerAvailable) {
+ totalPowerMah = counter.getPowerCounter().getCountLocked(statsType)
+ / (double) (1000 * 60 * 60);
+ if (totalPowerMah == 0) {
+ // Some controllers do not report power drain, so we can calculate it here.
+ totalPowerMah = calcPowerFromControllerDataMah(rxTimeMs, txTimeMs, idleTimeMs);
+ }
}
} else {
totalDurationMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) / 1000;
- totalPowerMah = mPowerOnPowerEstimator.calculatePower(totalDurationMs);
+ if (!isMeasuredPowerAvailable) {
+ totalPowerMah = calcGlobalPowerWithoutControllerDataMah(totalDurationMs);
+ }
}
powerDurationAndTraffic.durationMs = Math.max(0, totalDurationMs - totalAppDurationMs);
@@ -274,6 +288,29 @@
}
}
+ /** Returns (global or uid) estimated wifi power used using WifiControllerActivity data. */
+ public double calcPowerFromControllerDataMah(long rxTimeMs, long txTimeMs, long idleTimeMs) {
+ return mRxPowerEstimator.calculatePower(rxTimeMs)
+ + mTxPowerEstimator.calculatePower(txTimeMs)
+ + mIdlePowerEstimator.calculatePower(idleTimeMs);
+ }
+
+ /** Returns per-uid estimated wifi power used using non-WifiControllerActivity data. */
+ public double calcPowerWithoutControllerDataMah(long rxPackets, long txPackets,
+ long wifiRunningTimeMs, long wifiScanTimeMs, long wifiBatchScanTimeMs) {
+ return
+ (rxPackets + txPackets) * mWifiPowerPerPacket
+ + mPowerOnPowerEstimator.calculatePower(wifiRunningTimeMs)
+ + mScanPowerEstimator.calculatePower(wifiScanTimeMs)
+ + mBatchScanPowerEstimator.calculatePower(wifiBatchScanTimeMs);
+
+ }
+
+ /** Returns global estimated wifi power used using non-WifiControllerActivity data. */
+ public double calcGlobalPowerWithoutControllerDataMah(long globalWifiRunningTimeMs) {
+ return mPowerOnPowerEstimator.calculatePower(globalWifiRunningTimeMs);
+ }
+
/**
* Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB.
*/
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 47b0f8c..2458fe3 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -41,7 +41,6 @@
import android.os.ZygoteProcess;
import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
-import android.security.keystore.AndroidKeyStoreProvider;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -74,7 +73,6 @@
import java.io.InputStreamReader;
import java.security.Provider;
import java.security.Security;
-import java.util.Optional;
/**
* Startup class for the zygote process.
@@ -227,17 +225,7 @@
// AndroidKeyStoreProvider.install() manipulates the list of JCA providers to insert
// preferred providers. Note this is not done via security.properties as the JCA providers
// are not on the classpath in the case of, for example, raw dalvikvm runtimes.
- // TODO b/171305684 This code is used to conditionally enable the installation of the
- // Keystore 2.0 provider to enable teams adjusting to Keystore 2.0 at their own
- // pace. This code will be removed when all calling code was adjusted to
- // Keystore 2.0.
- Optional<Boolean> keystore2_enabled =
- android.sysprop.Keystore2Properties.keystore2_enabled();
- if (keystore2_enabled.isPresent() && keystore2_enabled.get()) {
- android.security.keystore2.AndroidKeyStoreProvider.install();
- } else {
- AndroidKeyStoreProvider.install();
- }
+ android.security.keystore2.AndroidKeyStoreProvider.install();
Log.i(TAG, "Installed AndroidKeyStoreProvider in "
+ (SystemClock.uptimeMillis() - startTime) + "ms.");
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 6049486..39bde74 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -78,7 +78,7 @@
import android.view.CrossWindowBlurListeners;
import android.view.Gravity;
import android.view.IRotationWatcher.Stub;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -3940,12 +3940,12 @@
/**
* System request to begin scroll capture.
*
- * @param callbacks to receive responses
+ * @param listener to receive the response
* @hide
*/
@Override
- public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
- getViewRootImpl().dispatchScrollCaptureRequest(callbacks);
+ public void requestScrollCapture(IScrollCaptureResponseListener listener) {
+ getViewRootImpl().dispatchScrollCaptureRequest(listener);
}
/**
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index e3d5464..845b3e5 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -52,7 +52,9 @@
public static final int POWER_BUCKET_SCREEN_DOZE = 1;
public static final int POWER_BUCKET_SCREEN_OTHER = 2;
public static final int POWER_BUCKET_CPU = 3;
- public static final int NUMBER_STANDARD_POWER_BUCKETS = 4; // Buckets above this are custom.
+ 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.
@IntDef(prefix = {"POWER_BUCKET_"}, value = {
POWER_BUCKET_UNKNOWN,
@@ -60,6 +62,8 @@
POWER_BUCKET_SCREEN_DOZE,
POWER_BUCKET_SCREEN_OTHER,
POWER_BUCKET_CPU,
+ POWER_BUCKET_WIFI,
+ POWER_BUCKET_BLUETOOTH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface StandardPowerBucket {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index fea0751..f19a123 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.service.notification.StatusBarNotification;
@@ -156,6 +157,11 @@
void hideAuthenticationDialog();
/**
+ * Sets an instance of IUdfpsHbmListener for UdfpsController.
+ */
+ void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+
+ /**
* Notifies System UI that the display is ready to show system decorations.
*/
void onDisplayReady(int displayId);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 2e25ea3..c8a91d8 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.fingerprint.IUdfpsHbmListener;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
@@ -121,6 +122,11 @@
void hideAuthenticationDialog();
/**
+ * Sets an instance of IUdfpsHbmListener for UdfpsController.
+ */
+ void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+
+ /**
* Show a warning that the device is about to go to sleep due to user inactivity.
*/
void showInattentiveSleepWarning();
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 85114e5..f3d0858 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -21,6 +21,7 @@
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.LinkCapacityEstimate;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.PhoneCapability;
import android.telephony.PhysicalChannelConfig;
@@ -72,5 +73,6 @@
void onBarringInfoChanged(in BarringInfo barringInfo);
void onPhysicalChannelConfigChanged(in List<PhysicalChannelConfig> configs);
void onDataEnabledChanged(boolean enabled, int reason);
- void onAllowedNetworkTypesChanged(in Map allowedNetworkTypeList);
+ void onAllowedNetworkTypesChanged(in int reason, in long allowedNetworkType);
+ void onLinkCapacityEstimateChanged(in List<LinkCapacityEstimate> linkCapacityEstimateList);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 95e0a3b..3418768 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -23,6 +23,7 @@
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
+import android.telephony.LinkCapacityEstimate;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.PhoneCapability;
@@ -95,5 +96,7 @@
void notifyPhysicalChannelConfigForSubscriber(in int subId,
in List<PhysicalChannelConfig> configs);
void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason);
- void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in Map allowedNetworkTypeList);
+ void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
+ void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
+ in List<LinkCapacityEstimate> linkCapacityEstimateList);
}
diff --git a/core/java/com/android/internal/util/LocationPermissionChecker.java b/core/java/com/android/internal/util/LocationPermissionChecker.java
deleted file mode 100644
index d67bd7a..0000000
--- a/core/java/com/android/internal/util/LocationPermissionChecker.java
+++ /dev/null
@@ -1,303 +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.internal.util;
-
-import android.Manifest;
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.location.LocationManager;
-import android.net.NetworkStack;
-import android.os.Binder;
-import android.os.Build;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-
-/**
- * The methods used for location permission and location mode checking.
- *
- * @hide
- */
-public class LocationPermissionChecker {
-
- private static final String TAG = "LocationPermissionChecker";
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"LOCATION_PERMISSION_CHECK_STATUS_"}, value = {
- SUCCEEDED,
- ERROR_LOCATION_MODE_OFF,
- ERROR_LOCATION_PERMISSION_MISSING,
- })
- public @interface LocationPermissionCheckStatus{}
-
- // The location permission check succeeded.
- public static final int SUCCEEDED = 0;
- // The location mode turns off for the caller.
- public static final int ERROR_LOCATION_MODE_OFF = 1;
- // The location permission isn't granted for the caller.
- public static final int ERROR_LOCATION_PERMISSION_MISSING = 2;
-
- private final Context mContext;
- private final AppOpsManager mAppOpsManager;
-
- public LocationPermissionChecker(Context context) {
- mContext = context;
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- }
-
- /**
- * Check location permission granted by the caller.
- *
- * This API check if the location mode enabled for the caller and the caller has
- * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
- *
- * @param pkgName package name of the application requesting access
- * @param featureId The feature in the package
- * @param uid The uid of the package
- * @param message A message describing why the permission was checked. Only needed if this is
- * not inside of a two-way binder call from the data receiver
- *
- * @return {@code true} returns if the caller has location permission and the location mode is
- * enabled.
- */
- public boolean checkLocationPermission(String pkgName, @Nullable String featureId,
- int uid, @Nullable String message) {
- return checkLocationPermissionInternal(pkgName, featureId, uid, message) == SUCCEEDED;
- }
-
- /**
- * Check location permission granted by the caller.
- *
- * This API check if the location mode enabled for the caller and the caller has
- * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
- * Compared with {@link #checkLocationPermission(String, String, int, String)}, this API returns
- * the detail information about the checking result, including the reason why it's failed and
- * logs the error for the caller.
- *
- * @param pkgName package name of the application requesting access
- * @param featureId The feature in the package
- * @param uid The uid of the package
- * @param message A message describing why the permission was checked. Only needed if this is
- * not inside of a two-way binder call from the data receiver
- *
- * @return {@link LocationPermissionCheckStatus} the result of the location permission check.
- */
- public @LocationPermissionCheckStatus int checkLocationPermissionWithDetailInfo(
- String pkgName, @Nullable String featureId, int uid, @Nullable String message) {
- final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
- switch (result) {
- case ERROR_LOCATION_MODE_OFF:
- Log.e(TAG, "Location mode is disabled for the device");
- break;
- case ERROR_LOCATION_PERMISSION_MISSING:
- Log.e(TAG, "UID " + uid + " has no location permission");
- break;
- }
- return result;
- }
-
- /**
- * Enforce the caller has location permission.
- *
- * This API determines if the location mode enabled for the caller and the caller has
- * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
- * SecurityException is thrown if the caller has no permission or the location mode is disabled.
- *
- * @param pkgName package name of the application requesting access
- * @param featureId The feature in the package
- * @param uid The uid of the package
- * @param message A message describing why the permission was checked. Only needed if this is
- * not inside of a two-way binder call from the data receiver
- */
- public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
- @Nullable String message) throws SecurityException {
- final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
-
- switch (result) {
- case ERROR_LOCATION_MODE_OFF:
- throw new SecurityException("Location mode is disabled for the device");
- case ERROR_LOCATION_PERMISSION_MISSING:
- throw new SecurityException("UID " + uid + " has no location permission");
- }
- }
-
- private int checkLocationPermissionInternal(String pkgName, @Nullable String featureId,
- int uid, @Nullable String message) {
- checkPackage(uid, pkgName);
-
- // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_STACK & MAINLINE_NETWORK_STACK
- // are granted a bypass.
- if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid)
- || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid)) {
- return SUCCEEDED;
- }
-
- // Location mode must be enabled
- if (!isLocationModeEnabled()) {
- return ERROR_LOCATION_MODE_OFF;
- }
-
- // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
- // location information.
- if (!checkCallersLocationPermission(pkgName, featureId, uid,
- true /* coarseForTargetSdkLessThanQ */, message)) {
- return ERROR_LOCATION_PERMISSION_MISSING;
- }
- return SUCCEEDED;
- }
-
- /**
- * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or
- * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level)
- * and a corresponding app op is allowed for this package and uid.
- *
- * @param pkgName PackageName of the application requesting access
- * @param featureId The feature in the package
- * @param uid The uid of the package
- * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE
- * else (false or targetSDK >= Q) then will check for FINE
- * @param message A message describing why the permission was checked. Only needed if this is
- * not inside of a two-way binder call from the data receiver
- */
- public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
- int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
-
- boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
-
- String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
- if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
- // Having FINE permission implies having COARSE permission (but not the reverse)
- permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
- }
- if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) {
- return false;
- }
-
- // Always checking FINE - even if will not enforce. This will record the request for FINE
- // so that a location request by the app is surfaced to the user.
- boolean isFineLocationAllowed = noteAppOpAllowed(
- AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message);
- if (isFineLocationAllowed) {
- return true;
- }
- if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
- return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid,
- message);
- }
- return false;
- }
-
- /**
- * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
- */
- public boolean isLocationModeEnabled() {
- final LocationManager LocationManager =
- (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
- try {
- return LocationManager.isLocationEnabledForUser(UserHandle.of(
- getCurrentUser()));
- } catch (Exception e) {
- Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
- return false;
- }
- }
-
- private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
- final long ident = Binder.clearCallingIdentity();
- try {
- if (mContext.getPackageManager().getApplicationInfoAsUser(
- packageName, 0,
- UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
- < versionCode) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- // In case of exception, assume unknown app (more strict checking)
- // Note: This case will never happen since checkPackage is
- // called to verify validity before checking App's version.
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return false;
- }
-
- private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
- int uid, @Nullable String message) {
- return mAppOpsManager.noteOp(op, uid, pkgName, featureId, message)
- == AppOpsManager.MODE_ALLOWED;
- }
-
- private void checkPackage(int uid, String pkgName)
- throws SecurityException {
- if (pkgName == null) {
- throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
- }
- mAppOpsManager.checkPackage(uid, pkgName);
- }
-
- @VisibleForTesting
- protected int getCurrentUser() {
- return ActivityManager.getCurrentUser();
- }
-
- private int getUidPermission(String permissionType, int uid) {
- // We don't care about pid, pass in -1
- return mContext.checkPermission(permissionType, -1, uid);
- }
-
- /**
- * Returns true if the |uid| holds NETWORK_SETTINGS permission.
- */
- public boolean checkNetworkSettingsPermission(int uid) {
- return getUidPermission(android.Manifest.permission.NETWORK_SETTINGS, uid)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission.
- */
- public boolean checkNetworkSetupWizardPermission(int uid) {
- return getUidPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, uid)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * Returns true if the |uid| holds NETWORK_STACK permission.
- */
- public boolean checkNetworkStackPermission(int uid) {
- return getUidPermission(android.Manifest.permission.NETWORK_STACK, uid)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission.
- */
- public boolean checkMainlineNetworkStackPermission(int uid) {
- return getUidPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid)
- == PackageManager.PERMISSION_GRANTED;
- }
-
-}
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index 276cad9..bbd738b 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -59,8 +59,6 @@
public static final int BASE_TETHERING = 0x00050000;
public static final int BASE_NSD_MANAGER = 0x00060000;
public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000;
- public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000;
- public static final int BASE_NETWORK_AGENT = 0x00081000;
public static final int BASE_NETWORK_FACTORY = 0x00083000;
public static final int BASE_ETHERNET = 0x00084000;
public static final int BASE_LOWPAN = 0x00085000;
diff --git a/core/java/com/android/internal/util/TrafficStatsConstants.java b/core/java/com/android/internal/util/TrafficStatsConstants.java
index 413be48..131114c 100644
--- a/core/java/com/android/internal/util/TrafficStatsConstants.java
+++ b/core/java/com/android/internal/util/TrafficStatsConstants.java
@@ -21,24 +21,8 @@
* @hide
*/
public class TrafficStatsConstants {
- // These tags are used by the network stack to do traffic for its own purposes. Traffic
- // tagged with these will be counted toward the network stack and must stay inside the
- // range defined by
- // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_START} and
- // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_END}.
- public static final int TAG_SYSTEM_DHCP = 0xFFFFFE01;
- public static final int TAG_SYSTEM_NEIGHBOR = 0xFFFFFE02;
- public static final int TAG_SYSTEM_DHCP_SERVER = 0xFFFFFE03;
public static final int TAG_SYSTEM_NTP = 0xFFFFFF41;
public static final int TAG_SYSTEM_GPS = 0xFFFFFF44;
public static final int TAG_SYSTEM_PAC = 0xFFFFFF45;
-
- // These tags are used by the network stack to do traffic on behalf of apps. Traffic
- // tagged with these will be counted toward the app on behalf of which the network
- // stack is doing this traffic. These values must stay inside the range defined by
- // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_START} and
- // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_END}.
- public static final int TAG_SYSTEM_PROBE = 0xFFFFFF81;
- public static final int TAG_SYSTEM_DNS = 0xFFFFFF82;
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index ab0149f..47341cd 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -24,7 +24,7 @@
import android.os.RemoteException;
import android.util.MergedConfiguration;
import android.view.DragEvent;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.InsetsSourceControl;
@@ -159,9 +159,9 @@
}
@Override
- public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
+ public void requestScrollCapture(IScrollCaptureResponseListener listener) {
try {
- callbacks.onScrollCaptureResponse(
+ listener.onScrollCaptureResponse(
new ScrollCaptureResponse.Builder().setDescription("Not Implemented").build());
} catch (RemoteException ex) {
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index d0c807d..783d088 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -221,10 +221,6 @@
dispatchMessage(mH.obtainMessage(DO_GET_SURROUNDING_TEXT, flags, 0 /* unused */, args));
}
- public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
- // no-op
- }
-
public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
dispatchMessage(
mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 8d82e33..c336373 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -35,8 +35,7 @@
* {@hide}
*/
oneway interface IInputMethod {
- void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps,
- int configChanges);
+ void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps);
void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
in IInlineSuggestionsRequestCallback cb);
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index f29e95c..c9755a3 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -204,6 +204,8 @@
@Nullable
private final float[] mActivityViewToScreenMatrixValues;
+ public final boolean isInputMethodSuppressingSpellChecker;
+
/**
* @return {@link Matrix} that corresponds to {@link #mActivityViewToScreenMatrixValues}.
* {@code null} if {@link #mActivityViewToScreenMatrixValues} is {@code null}.
@@ -220,7 +222,8 @@
public InputBindResult(@ResultCode int _result,
IInputMethodSession _method, InputChannel _channel, String _id, int _sequence,
- @Nullable Matrix activityViewToScreenMatrix) {
+ @Nullable Matrix activityViewToScreenMatrix,
+ boolean isInputMethodSuppressingSpellChecker) {
result = _result;
method = _method;
channel = _channel;
@@ -232,6 +235,7 @@
mActivityViewToScreenMatrixValues = new float[9];
activityViewToScreenMatrix.getValues(mActivityViewToScreenMatrixValues);
}
+ this.isInputMethodSuppressingSpellChecker = isInputMethodSuppressingSpellChecker;
}
InputBindResult(Parcel source) {
@@ -245,6 +249,7 @@
id = source.readString();
sequence = source.readInt();
mActivityViewToScreenMatrixValues = source.createFloatArray();
+ isInputMethodSuppressingSpellChecker = source.readBoolean();
}
@Override
@@ -252,6 +257,7 @@
return "InputBindResult{result=" + getResultString() + " method="+ method + " id=" + id
+ " sequence=" + sequence
+ " activityViewToScreenMatrix=" + getActivityViewToScreenMatrix()
+ + " isInputMethodSuppressingSpellChecker=" + isInputMethodSuppressingSpellChecker
+ "}";
}
@@ -274,6 +280,7 @@
dest.writeString(id);
dest.writeInt(sequence);
dest.writeFloatArray(mActivityViewToScreenMatrixValues);
+ dest.writeBoolean(isInputMethodSuppressingSpellChecker);
}
/**
@@ -340,7 +347,7 @@
}
private static InputBindResult error(@ResultCode int result) {
- return new InputBindResult(result, null, null, null, -1, null);
+ return new InputBindResult(result, null, null, null, -1, null, false);
}
/**
diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
new file mode 100644
index 0000000..5ec8b30
--- /dev/null
+++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.view.inline;
+
+import static android.view.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.graphics.drawable.Drawable;
+import android.transition.Transition;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import android.widget.inline.InlineContentView;
+
+import java.io.PrintWriter;
+
+/**
+ * UI container for the inline suggestion tooltip.
+ */
+public final class InlineTooltipUi extends PopupWindow implements AutoCloseable {
+ private static final String TAG = "InlineTooltipUi";
+
+ private final WindowManager mWm;
+ private final ViewGroup mContentContainer;
+
+ private boolean mShowing;
+
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ private final View.OnAttachStateChangeListener mAnchorOnAttachStateChangeListener =
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ /* ignore - handled by the super class */
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ dismiss();
+ }
+ };
+
+ private final View.OnLayoutChangeListener mAnchoredOnLayoutChangeListener =
+ new View.OnLayoutChangeListener() {
+ int mHeight;
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (mHeight != bottom - top) {
+ mHeight = bottom - top;
+ adjustPosition();
+ }
+ }
+ };
+
+ public InlineTooltipUi(@NonNull Context context) {
+ mContentContainer = new LinearLayout(new ContextWrapper(context));
+ mWm = context.getSystemService(WindowManager.class);
+
+ setTouchModal(false);
+ setOutsideTouchable(true);
+ setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
+ setFocusable(false);
+ }
+
+ /**
+ * Sets the content view for inline suggestions tooltip
+ * @param v the content view of {@link android.widget.inline.InlineContentView}
+ */
+ public void setTooltipView(@NonNull InlineContentView v) {
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(v);
+ mContentContainer.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void close() {
+ hide();
+ }
+
+ @Override
+ protected boolean hasContentView() {
+ return true;
+ }
+
+ @Override
+ protected boolean hasDecorView() {
+ return true;
+ }
+
+ @Override
+ protected WindowManager.LayoutParams getDecorViewLayoutParams() {
+ return mWindowLayoutParams;
+ }
+
+ /**
+ * The effective {@code update} method that should be called by its clients.
+ */
+ public void update(View anchor) {
+ // set to the application type with the highest z-order
+ setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
+
+ // The first time to show up, the height of tooltip is zero,
+ // so set the offset Y to 2 * anchor height.
+ final int achoredHeight = mContentContainer.getHeight();
+ final int offsetY = (achoredHeight == 0)
+ ? -anchor.getHeight() << 1 : -anchor.getHeight() - achoredHeight;
+ if (!isShowing()) {
+ setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
+ setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
+ showAsDropDown(anchor, 0 , offsetY, Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ } else {
+ update(anchor, 0 , offsetY, WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ @Override
+ protected void update(View anchor, WindowManager.LayoutParams params) {
+ // update content view for the anchor is scrolling
+ if (anchor.isVisibleToUser()) {
+ show(params);
+ } else {
+ hide();
+ }
+ }
+
+ @Override
+ public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
+ if (isShowing()) {
+ return;
+ }
+
+ setShowing(true);
+ setDropDown(true);
+ attachToAnchor(anchor, xoff, yoff, gravity);
+ final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams(
+ anchor.getWindowToken());
+ final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
+ p.width, p.height, gravity, getAllowScrollingAnchorParent());
+ updateAboveAnchor(aboveAnchor);
+ p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+ p.packageName = anchor.getContext().getPackageName();
+ show(p);
+ }
+
+ @Override
+ protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+ super.attachToAnchor(anchor, xoff, yoff, gravity);
+ anchor.addOnAttachStateChangeListener(mAnchorOnAttachStateChangeListener);
+ }
+
+ @Override
+ protected void detachFromAnchor() {
+ final View anchor = getAnchor();
+ if (anchor != null) {
+ anchor.removeOnAttachStateChangeListener(mAnchorOnAttachStateChangeListener);
+ }
+ super.detachFromAnchor();
+ }
+
+ @Override
+ public void dismiss() {
+ if (!isShowing() || isTransitioningToDismiss()) {
+ return;
+ }
+
+ setShowing(false);
+ setTransitioningToDismiss(true);
+
+ hide();
+ detachFromAnchor();
+ if (getOnDismissListener() != null) {
+ getOnDismissListener().onDismiss();
+ }
+ }
+
+ private void adjustPosition() {
+ View anchor = getAnchor();
+ if (anchor == null) return;
+ update(anchor);
+ }
+
+ private void show(WindowManager.LayoutParams params) {
+ if (sVerbose) {
+ Slog.v(TAG, "show()");
+ }
+ mWindowLayoutParams = params;
+
+ try {
+ params.packageName = "android";
+ params.setTitle("Autofill Inline Tooltip"); // Title is set for debugging purposes
+ if (!mShowing) {
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mContentContainer.addOnLayoutChangeListener(mAnchoredOnLayoutChangeListener);
+ mWm.addView(mContentContainer, params);
+ mShowing = true;
+ } else {
+ mWm.updateViewLayout(mContentContainer, params);
+ }
+ } catch (WindowManager.BadTokenException e) {
+ Slog.d(TAG, "Failed with token " + params.token + " gone.");
+ } catch (IllegalStateException e) {
+ // WM throws an ISE if mContentView was added twice; this should never happen -
+ // since show() and hide() are always called in the UIThread - but when it does,
+ // it should not crash the system.
+ Slog.wtf(TAG, "Exception showing window " + params, e);
+ }
+ }
+
+ private void hide() {
+ if (sVerbose) {
+ Slog.v(TAG, "hide()");
+ }
+ try {
+ if (mShowing) {
+ mContentContainer.removeOnLayoutChangeListener(mAnchoredOnLayoutChangeListener);
+ mWm.removeView(mContentContainer);
+ mShowing = false;
+ }
+ } catch (IllegalStateException e) {
+ // WM might thrown an ISE when removing the mContentView; this should never
+ // happen - since show() and hide() are always called in the UIThread - but if it
+ // does, it should not crash the system.
+ Slog.e(TAG, "Exception hiding window ", e);
+ }
+ }
+
+ @Override
+ public int getAnimationStyle() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Drawable getBackground() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public View getContentView() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public float getElevation() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Transition getEnterTransition() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public Transition getExitTransition() {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setBackgroundDrawable(Drawable background) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setContentView(View contentView) {
+ if (contentView != null) {
+ throw new IllegalStateException("You can't call this!");
+ }
+ }
+
+ @Override
+ public void setElevation(float elevation) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setEnterTransition(Transition enterTransition) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setExitTransition(Transition exitTransition) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ @Override
+ public void setTouchInterceptor(View.OnTouchListener l) {
+ throw new IllegalStateException("You can't call this!");
+ }
+
+ /**
+ * Dumps status
+ */
+ public void dump(@NonNull PrintWriter pw, @Nullable String prefix) {
+
+ pw.print(prefix);
+
+ if (mContentContainer != null) {
+ pw.print(prefix); pw.print("Window: ");
+ final String prefix2 = prefix + " ";
+ pw.println();
+ pw.print(prefix2); pw.print("showing: "); pw.println(mShowing);
+ pw.print(prefix2); pw.print("view: "); pw.println(mContentContainer);
+ if (mWindowLayoutParams != null) {
+ pw.print(prefix2); pw.print("params: "); pw.println(mWindowLayoutParams);
+ }
+ pw.print(prefix2); pw.print("screen coordinates: ");
+ if (mContentContainer == null) {
+ pw.println("N/A");
+ } else {
+ final int[] coordinates = mContentContainer.getLocationOnScreen();
+ pw.print(coordinates[0]); pw.print("x"); pw.println(coordinates[1]);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/inline/OWNERS b/core/java/com/android/internal/view/inline/OWNERS
new file mode 100644
index 0000000..edfb211
--- /dev/null
+++ b/core/java/com/android/internal/view/inline/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/autofill/OWNERS
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
index 6cc5a4a..83345da 100644
--- a/core/java/com/android/internal/widget/CallLayout.java
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -100,7 +100,6 @@
}
// TODO(b/179178086): crop/clip the icon to a circle?
mConversationIconView.setImageIcon(icon);
- mConversationText.setText(callerName);
}
@RemotableViewMethod
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 89a90e9..17ce75e 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -16,10 +16,16 @@
package com.android.internal.widget;
+import static android.widget.EdgeEffect.TYPE_GLOW;
+import static android.widget.EdgeEffect.TYPE_STRETCH;
+import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_BY_DEFAULT;
+import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED;
+
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.Compatibility;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
@@ -460,6 +466,8 @@
private final int[] mScrollConsumed = new int[2];
private final int[] mNestedOffsets = new int[2];
+ private int mEdgeEffectType;
+
/**
* These are views that had their a11y importance changed during a layout. We defer these events
* until the end of the layout because a11y service may make sync calls back to the RV while
@@ -587,6 +595,14 @@
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
}
+ boolean defaultToStretch = Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
+ || Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.EdgeEffect);
+ mEdgeEffectType = a.getInt(com.android.internal.R.styleable.EdgeEffect_edgeEffectType,
+ defaultToStretch ? TYPE_STRETCH : TYPE_GLOW);
+ a.recycle();
+
// Re-set whether nested scrolling is enabled so that it is set on all API levels
setNestedScrollingEnabled(nestedScrollingEnabled);
}
@@ -610,6 +626,28 @@
}
/**
+ * Returns the {@link EdgeEffect#getType()} used for all EdgeEffects.
+ *
+ * @return @link EdgeEffect#getType()} used for all EdgeEffects.
+ */
+ @EdgeEffect.EdgeEffectType
+ public int getEdgeEffectType() {
+ return mEdgeEffectType;
+ }
+
+ /**
+ * Sets the {@link EdgeEffect#getType()} used in all EdgeEffects.
+ * Any existing over-scroll effects are cleared and new effects are created as needed.
+ *
+ * @param type the {@link EdgeEffect#getType()} used in all EdgeEffects.
+ */
+ public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
+ mEdgeEffectType = type;
+ invalidateGlows();
+ invalidate();
+ }
+
+ /**
* Instantiate and set a LayoutManager, if specified in the attributes.
*/
private void createLayoutManager(Context context, String className, AttributeSet attrs,
@@ -2183,6 +2221,7 @@
return;
}
mLeftGlow = new EdgeEffect(getContext());
+ mLeftGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2196,6 +2235,7 @@
return;
}
mRightGlow = new EdgeEffect(getContext());
+ mRightGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2209,6 +2249,7 @@
return;
}
mTopGlow = new EdgeEffect(getContext());
+ mTopGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2223,6 +2264,7 @@
return;
}
mBottomGlow = new EdgeEffect(getContext());
+ mBottomGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2663,7 +2705,7 @@
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
- if (mScrollState == SCROLL_STATE_SETTLING) {
+ if (stopGlowAnimations(e) || mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
@@ -2731,6 +2773,38 @@
return mScrollState == SCROLL_STATE_DRAGGING;
}
+ /**
+ * This stops any edge glow animation that is currently running by applying a
+ * 0 length pull at the displacement given by the provided MotionEvent. On pre-S devices,
+ * this method does nothing, allowing any animating edge effect to continue animating and
+ * returning <code>false</code> always.
+ *
+ * @param e The motion event to use to indicate the finger position for the displacement of
+ * the current pull.
+ * @return <code>true</code> if any edge effect had an existing effect to be drawn ond the
+ * animation was stopped or <code>false</code> if no edge effect had a value to display.
+ */
+ private boolean stopGlowAnimations(MotionEvent e) {
+ boolean stopped = false;
+ if (mLeftGlow != null && mLeftGlow.getDistance() != 0) {
+ mLeftGlow.onPullDistance(0, 1 - (e.getY() / getHeight()));
+ stopped = true;
+ }
+ if (mRightGlow != null && mRightGlow.getDistance() != 0) {
+ mRightGlow.onPullDistance(0, e.getY() / getHeight());
+ stopped = true;
+ }
+ if (mTopGlow != null && mTopGlow.getDistance() != 0) {
+ mTopGlow.onPullDistance(0, e.getX() / getWidth());
+ stopped = true;
+ }
+ if (mBottomGlow != null && mBottomGlow.getDistance() != 0) {
+ mBottomGlow.onPullDistance(0, 1 - e.getX() / getWidth());
+ stopped = true;
+ }
+ return stopped;
+ }
+
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
final int listenerCount = mOnItemTouchListeners.size();
@@ -2807,6 +2881,8 @@
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
+ dx -= releaseHorizontalGlow(dx, e.getY());
+ dy -= releaseVerticalGlow(dy, e.getX());
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
@@ -2887,6 +2963,72 @@
return true;
}
+ /**
+ * If either of the horizontal edge glows are currently active, this consumes part or all of
+ * deltaX on the edge glow.
+ *
+ * @param deltaX The pointer motion, in pixels, in the horizontal direction, positive
+ * for moving down and negative for moving up.
+ * @param y The vertical position of the pointer.
+ * @return The amount of <code>deltaX</code> that has been consumed by the
+ * edge glow.
+ */
+ private int releaseHorizontalGlow(int deltaX, float y) {
+ // First allow releasing existing overscroll effect:
+ float consumed = 0;
+ float displacement = y / getHeight();
+ float pullDistance = (float) deltaX / getWidth();
+ if (mLeftGlow != null && mLeftGlow.getDistance() != 0) {
+ consumed = -mLeftGlow.onPullDistance(-pullDistance, 1 - displacement);
+ if (mLeftGlow.getDistance() == 0) {
+ mLeftGlow.onRelease();
+ }
+ } else if (mRightGlow != null && mRightGlow.getDistance() != 0) {
+ consumed = mRightGlow.onPullDistance(pullDistance, displacement);
+ if (mRightGlow.getDistance() == 0) {
+ mRightGlow.onRelease();
+ }
+ }
+ int pixelsConsumed = Math.round(consumed * getWidth());
+ if (pixelsConsumed != 0) {
+ invalidate();
+ }
+ return pixelsConsumed;
+ }
+
+ /**
+ * If either of the vertical edge glows are currently active, this consumes part or all of
+ * deltaY on the edge glow.
+ *
+ * @param deltaY The pointer motion, in pixels, in the vertical direction, positive
+ * for moving down and negative for moving up.
+ * @param x The vertical position of the pointer.
+ * @return The amount of <code>deltaY</code> that has been consumed by the
+ * edge glow.
+ */
+ private int releaseVerticalGlow(int deltaY, float x) {
+ // First allow releasing existing overscroll effect:
+ float consumed = 0;
+ float displacement = x / getWidth();
+ float pullDistance = (float) deltaY / getHeight();
+ if (mTopGlow != null && mTopGlow.getDistance() != 0) {
+ consumed = -mTopGlow.onPullDistance(-pullDistance, displacement);
+ if (mTopGlow.getDistance() == 0) {
+ mTopGlow.onRelease();
+ }
+ } else if (mBottomGlow != null && mBottomGlow.getDistance() != 0) {
+ consumed = mBottomGlow.onPullDistance(pullDistance, 1 - displacement);
+ if (mBottomGlow.getDistance() == 0) {
+ mBottomGlow.onRelease();
+ }
+ }
+ int pixelsConsumed = Math.round(consumed * getHeight());
+ if (pixelsConsumed != 0) {
+ invalidate();
+ }
+ return pixelsConsumed;
+ }
+
private void resetTouch() {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 3bc0ef4e..94ac183 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -637,6 +637,12 @@
char saveResolvedClassesDelayMsOptsBuf[
sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX];
char madviseRandomOptsBuf[sizeof("-XX:MadviseRandomAccess:")-1 + PROPERTY_VALUE_MAX];
+ char madviseWillNeedFileSizeVdex[
+ sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX];
+ char madviseWillNeedFileSizeOdex[
+ sizeof("-XMadviseWillNeedOdexFileSize:")-1 + PROPERTY_VALUE_MAX];
+ char madviseWillNeedFileSizeArt[
+ sizeof("-XMadviseWillNeedArtFileSize:")-1 + PROPERTY_VALUE_MAX];
char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX];
char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX];
char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX];
@@ -845,6 +851,22 @@
parseRuntimeOption("dalvik.vm.madvise-random", madviseRandomOptsBuf, "-XX:MadviseRandomAccess:");
/*
+ * Use default platform configuration as limits for madvising,
+ * when no properties are specified.
+ */
+ parseRuntimeOption("dalvik.vm.madvise.vdexfile.size",
+ madviseWillNeedFileSizeVdex,
+ "-XMadviseWillNeedVdexFileSize:");
+
+ parseRuntimeOption("dalvik.vm.madvise.odexfile.size",
+ madviseWillNeedFileSizeOdex,
+ "-XMadviseWillNeedOdexFileSize:");
+
+ parseRuntimeOption("dalvik.vm.madvise.artfile.size",
+ madviseWillNeedFileSizeArt,
+ "-XMadviseWillNeedArtFileSize:");
+
+ /*
* Profile related options.
*/
parseRuntimeOption("dalvik.vm.hot-startup-method-samples", hotstartupsamplesOptsBuf,
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 62767a6..de5df20 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1450,6 +1450,42 @@
track->setPlayerIId(playerIId);
}
+static jint android_media_AudioTrack_getStartThresholdInFrames(JNIEnv *env, jobject thiz) {
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Unable to retrieve AudioTrack pointer for getStartThresholdInFrames()");
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ const ssize_t result = lpTrack->getStartThresholdInFrames();
+ if (result <= 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Internal error detected in getStartThresholdInFrames() = %zd",
+ result);
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ return (jint)result; // this should be a positive value.
+}
+
+static jint android_media_AudioTrack_setStartThresholdInFrames(JNIEnv *env, jobject thiz,
+ jint startThresholdInFrames) {
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+ if (lpTrack == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Unable to retrieve AudioTrack pointer for setStartThresholdInFrames()");
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ // non-positive values of startThresholdInFrames are not allowed by the Java layer.
+ const ssize_t result = lpTrack->setStartThresholdInFrames(startThresholdInFrames);
+ if (result <= 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Internal error detected in setStartThresholdInFrames() = %zd",
+ result);
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ return (jint)result; // this should be a positive value.
+}
+
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -1530,6 +1566,10 @@
{"native_setLogSessionId", "(Ljava/lang/String;)V",
(void *)android_media_AudioTrack_setLogSessionId},
{"native_setPlayerIId", "(I)V", (void *)android_media_AudioTrack_setPlayerIId},
+ {"native_setStartThresholdInFrames", "(I)I",
+ (void *)android_media_AudioTrack_setStartThresholdInFrames},
+ {"native_getStartThresholdInFrames", "()I",
+ (void *)android_media_AudioTrack_getStartThresholdInFrames},
};
// field names found in android/media/AudioTrack.java
diff --git a/core/jni/android_net_NetworkUtils.cpp b/core/jni/android_net_NetworkUtils.cpp
index 7508108..a781a37 100644
--- a/core/jni/android_net_NetworkUtils.cpp
+++ b/core/jni/android_net_NetworkUtils.cpp
@@ -52,27 +52,6 @@
// FrameworkListener limits the size of commands to 4096 bytes.
constexpr int MAXCMDSIZE = 4096;
-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[] = {
@@ -150,7 +129,7 @@
int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
if (fd < 0) {
- throwErrnoException(env, "resNetworkQuery", -fd);
+ jniThrowErrnoException(env, "resNetworkQuery", -fd);
return nullptr;
}
@@ -165,7 +144,7 @@
int fd = resNetworkSend(netId, data, msgLen, flags);
if (fd < 0) {
- throwErrnoException(env, "resNetworkSend", -fd);
+ jniThrowErrnoException(env, "resNetworkSend", -fd);
return nullptr;
}
@@ -180,13 +159,13 @@
int res = resNetworkResult(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,
@@ -208,7 +187,7 @@
static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
unsigned dnsNetId = 0;
if (int res = getNetworkForDns(&dnsNetId) < 0) {
- throwErrnoException(env, "getDnsNetId", -res);
+ jniThrowErrnoException(env, "getDnsNetId", -res);
return nullptr;
}
bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
@@ -233,8 +212,8 @@
// 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);
- return NULL;
+ jniThrowErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
+ return NULL;
}
struct tcp_info tcpinfo = {};
@@ -244,8 +223,8 @@
// 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);
- return NULL;
+ jniThrowErrnoException(env, "getsockopt : TCP_INFO", errno);
+ return NULL;
}
jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
diff --git a/core/jni/android_os_SharedMemory.cpp b/core/jni/android_os_SharedMemory.cpp
index dc86187..fe375f4 100644
--- a/core/jni/android_os_SharedMemory.cpp
+++ b/core/jni/android_os_SharedMemory.cpp
@@ -35,21 +35,6 @@
jclass errnoExceptionClass;
jmethodID errnoExceptionCtor; // MethodID for ErrnoException.<init>(String,I)
-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();
- }
-
- jobject exception = env->NewObject(errnoExceptionClass,
- errnoExceptionCtor,
- detailMessage.get(),
- error);
- env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {
// Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null
@@ -65,7 +50,7 @@
}
if (fd < 0) {
- throwErrnoException(env, "SharedMemory_create", err);
+ jniThrowErrnoException(env, "SharedMemory_create", err);
return nullptr;
}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f7b3f30..2e4be14 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1509,7 +1509,9 @@
res = JNI_TRUE;
} else {
jniThrowException(env, "java/util/NoSuchElementException",
- "Death link does not exist");
+ base::StringPrintf("Death link does not exist (%s)",
+ statusToString(err).c_str())
+ .c_str());
}
}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index dcfa950..d4b5c2b 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1316,24 +1316,6 @@
return removeAllProcessGroups();
}
-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));
-}
-
// 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) {
@@ -1343,7 +1325,7 @@
static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
int fd = sys_pidfd_open(pid, flags);
if (fd < 0) {
- throwErrnoException(env, "nativePidFdOpen", errno);
+ jniThrowErrnoException(env, "nativePidFdOpen", errno);
return -1;
}
return fd;
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index ab6633f..bfeb01d 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -45,12 +45,6 @@
return value ? "true" : "false";
}
-enum class HandleEventResponse : int {
- // Allowed return values of 'handleEvent' function as documented in LooperCallback::handleEvent
- REMOVE_CALLBACK = 0,
- KEEP_CALLBACK = 1
-};
-
static struct {
jclass clazz;
@@ -77,14 +71,6 @@
return str;
}
-/**
- * Convert an enumeration to its underlying type. Replace with std::to_underlying when available.
- */
-template <class T>
-static std::underlying_type_t<T> toUnderlying(const T& t) {
- return static_cast<std::underlying_type_t<T>>(t);
-}
-
class NativeInputEventReceiver : public LooperCallback {
public:
NativeInputEventReceiver(JNIEnv* env, jobject receiverWeak,
@@ -121,16 +107,11 @@
return mInputConsumer.getChannel()->getName();
}
- HandleEventResponse processOutboundEvents();
+ status_t processOutboundEvents();
// From 'LooperCallback'
int handleEvent(int receiveFd, int events, void* data) override;
};
-// Ensure HandleEventResponse underlying type matches the return type of LooperCallback::handleEvent
-static_assert(std::is_same<std::underlying_type_t<HandleEventResponse>,
- std::invoke_result_t<decltype(&LooperCallback::handleEvent),
- NativeInputEventReceiver, int, int, void*>>::value);
-
NativeInputEventReceiver::NativeInputEventReceiver(
JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel,
const sp<MessageQueue>& messageQueue)
@@ -167,26 +148,12 @@
ALOGD("channel '%s' ~ Finished input event.", getInputChannelName().c_str());
}
- status_t status = mInputConsumer.sendFinishedSignal(seq, handled);
- if (status != OK) {
- if (status == WOULD_BLOCK) {
- if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Could not send finished signal immediately. "
- "Enqueued for later.", getInputChannelName().c_str());
- }
- Finish finish;
- finish.seq = seq;
- finish.handled = handled;
- mFinishQueue.push_back(finish);
- if (mFinishQueue.size() == 1) {
- setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
- }
- return OK;
- }
- ALOGW("Failed to send finished signal on channel '%s'. status=%d",
- getInputChannelName().c_str(), status);
- }
- return status;
+ Finish finish{
+ .seq = seq,
+ .handled = handled,
+ };
+ mFinishQueue.push_back(finish);
+ return processOutboundEvents();
}
void NativeInputEventReceiver::setFdEvents(int events) {
@@ -217,7 +184,7 @@
* InputPublisher. If no events are remaining, let the looper know so that it doesn't wake up
* unnecessarily.
*/
-HandleEventResponse NativeInputEventReceiver::processOutboundEvents() {
+status_t NativeInputEventReceiver::processOutboundEvents() {
while (!mFinishQueue.empty()) {
const Finish& finish = *mFinishQueue.begin();
status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
@@ -233,7 +200,8 @@
ALOGD("channel '%s' ~ Remaining outbound events: %zu.",
getInputChannelName().c_str(), mFinishQueue.size());
}
- return HandleEventResponse::KEEP_CALLBACK; // try again later
+ setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
+ return WOULD_BLOCK; // try again later
}
// Some other error. Give up
@@ -247,42 +215,49 @@
jniThrowRuntimeException(env, message.c_str());
mMessageQueue->raiseAndClearException(env, "finishInputEvent");
}
- return HandleEventResponse::REMOVE_CALLBACK;
+ return status;
}
// The queue is now empty. Tell looper there's no more output to expect.
setFdEvents(ALOOPER_EVENT_INPUT);
- return HandleEventResponse::KEEP_CALLBACK;
+ return OK;
}
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
+ // Allowed return values of this function as documented in LooperCallback::handleEvent
+ constexpr int REMOVE_CALLBACK = 0;
+ constexpr int KEEP_CALLBACK = 1;
+
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
// This error typically occurs when the publisher has closed the input channel
// as part of removing a window or finishing an IME session, in which case
// the consumer will soon be disposed as well.
if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. "
- "events=0x%x", getInputChannelName().c_str(), events);
+ ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. events=0x%x",
+ getInputChannelName().c_str(), events);
}
- return toUnderlying(HandleEventResponse::REMOVE_CALLBACK);
+ return REMOVE_CALLBACK;
}
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
- return status == OK || status == NO_MEMORY
- ? toUnderlying(HandleEventResponse::KEEP_CALLBACK)
- : toUnderlying(HandleEventResponse::REMOVE_CALLBACK);
+ return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
}
if (events & ALOOPER_EVENT_OUTPUT) {
- return toUnderlying(processOutboundEvents());
+ const status_t status = processOutboundEvents();
+ if (status == OK || status == WOULD_BLOCK) {
+ return KEEP_CALLBACK;
+ } else {
+ return REMOVE_CALLBACK;
+ }
}
- ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
- "events=0x%x", getInputChannelName().c_str(), events);
- return toUnderlying(HandleEventResponse::KEEP_CALLBACK);
+ ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. events=0x%x",
+ getInputChannelName().c_str(), events);
+ return KEEP_CALLBACK;
}
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
@@ -503,9 +478,13 @@
sp<NativeInputEventReceiver> receiver =
reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
status_t status = receiver->finishInputEvent(seq, handled);
- if (status && status != DEAD_OBJECT) {
+ if (status == OK || status == WOULD_BLOCK) {
+ return; // normal operation
+ }
+ if (status != DEAD_OBJECT) {
std::string message =
- android::base::StringPrintf("Failed to finish input event. status=%d", status);
+ android::base::StringPrintf("Failed to finish input event. status=%s(%d)",
+ strerror(-status), status);
jniThrowRuntimeException(env, message.c_str());
}
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 9746a07..52d21a8 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -34,6 +34,8 @@
#include "core_jni_helpers.h"
+using android::base::Result;
+
namespace android {
// Log debug messages about the dispatch cycle.
@@ -170,16 +172,16 @@
// as part of finishing an IME session, in which case the publisher will
// soon be disposed as well.
if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Consumer closed input channel or an error occurred. "
- "events=0x%x", getInputChannelName().c_str(), events);
+ ALOGD("channel '%s' ~ Consumer closed input channel or an error occurred. events=0x%x",
+ getInputChannelName().c_str(), events);
}
return 0; // remove the callback
}
if (!(events & ALOOPER_EVENT_INPUT)) {
- ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
- "events=0x%x", getInputChannelName().c_str(), events);
+ ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. events=0x%x",
+ getInputChannelName().c_str(), events);
return 1;
}
@@ -197,16 +199,9 @@
ScopedLocalRef<jobject> senderObj(env, NULL);
bool skipCallbacks = false;
for (;;) {
- uint32_t publishedSeq;
- bool handled;
- std::function<void(uint32_t seq, bool handled, nsecs_t consumeTime)> callback =
- [&publishedSeq, &handled](uint32_t inSeq, bool inHandled,
- nsecs_t inConsumeTime) -> void {
- publishedSeq = inSeq;
- handled = inHandled;
- };
- status_t status = mInputPublisher.receiveFinishedSignal(callback);
- if (status) {
+ Result<InputPublisher::Finished> result = mInputPublisher.receiveFinishedSignal();
+ if (!result.ok()) {
+ const status_t status = result.error().code();
if (status == WOULD_BLOCK) {
return OK;
}
@@ -215,7 +210,7 @@
return status;
}
- auto it = mPublishedSeqMap.find(publishedSeq);
+ auto it = mPublishedSeqMap.find(result->seq);
if (it == mPublishedSeqMap.end()) {
continue;
}
@@ -224,25 +219,24 @@
mPublishedSeqMap.erase(it);
if (kDebugDispatchCycle) {
- ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, "
- "pendingEvents=%zu.",
- getInputChannelName().c_str(), seq, handled ? "true" : "false",
- mPublishedSeqMap.size());
+ 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());
+ ALOGW("channel '%s' ~ Sender object was finalized without being disposed.",
+ getInputChannelName().c_str());
return DEAD_OBJECT;
}
}
env->CallVoidMethod(senderObj.get(),
- gInputEventSenderClassInfo.dispatchInputEventFinished,
- jint(seq), jboolean(handled));
+ gInputEventSenderClassInfo.dispatchInputEventFinished,
+ static_cast<jint>(seq), static_cast<jboolean>(result->handled));
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching finished signal.");
skipCallbacks = true;
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index ebc507a..469e577 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -16,9 +16,10 @@
#include <android_runtime/AndroidRuntime.h>
-#include <input/KeyCharacterMap.h>
-#include <input/Input.h>
#include <binder/Parcel.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/KeyCharacterMap.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
@@ -75,6 +76,10 @@
reinterpret_cast<jlong>(nativeMap));
}
+static jobject nativeObtainEmptyKeyCharacterMap(JNIEnv* env, jobject /* clazz */, jint deviceId) {
+ return android_view_KeyCharacterMap_create(env, deviceId, nullptr);
+}
+
static jlong nativeReadFromParcel(JNIEnv *env, jobject clazz, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (!parcel) {
@@ -224,33 +229,37 @@
return result;
}
+static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) {
+ const std::shared_ptr<KeyCharacterMap>& map1 =
+ (reinterpret_cast<NativeKeyCharacterMap*>(ptr1))->getMap();
+ const std::shared_ptr<KeyCharacterMap>& map2 =
+ (reinterpret_cast<NativeKeyCharacterMap*>(ptr2))->getMap();
+ if (map1 == nullptr || map2 == nullptr) {
+ return map1 == map2;
+ }
+ return static_cast<jboolean>(*map1 == *map2);
+}
/*
* JNI registration.
*/
static const JNINativeMethod g_methods[] = {
- /* name, signature, funcPtr */
- { "nativeReadFromParcel", "(Landroid/os/Parcel;)J",
- (void*)nativeReadFromParcel },
- { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
- (void*)nativeWriteToParcel },
- { "nativeDispose", "(J)V",
- (void*)nativeDispose },
- { "nativeGetCharacter", "(JII)C",
- (void*)nativeGetCharacter },
- { "nativeGetFallbackAction", "(JIILandroid/view/KeyCharacterMap$FallbackAction;)Z",
- (void*)nativeGetFallbackAction },
- { "nativeGetNumber", "(JI)C",
- (void*)nativeGetNumber },
- { "nativeGetMatch", "(JI[CI)C",
- (void*)nativeGetMatch },
- { "nativeGetDisplayLabel", "(JI)C",
- (void*)nativeGetDisplayLabel },
- { "nativeGetKeyboardType", "(J)I",
- (void*)nativeGetKeyboardType },
- { "nativeGetEvents", "(J[C)[Landroid/view/KeyEvent;",
- (void*)nativeGetEvents },
+ /* name, signature, funcPtr */
+ {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+ {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+ {"nativeDispose", "(J)V", (void*)nativeDispose},
+ {"nativeGetCharacter", "(JII)C", (void*)nativeGetCharacter},
+ {"nativeGetFallbackAction", "(JIILandroid/view/KeyCharacterMap$FallbackAction;)Z",
+ (void*)nativeGetFallbackAction},
+ {"nativeGetNumber", "(JI)C", (void*)nativeGetNumber},
+ {"nativeGetMatch", "(JI[CI)C", (void*)nativeGetMatch},
+ {"nativeGetDisplayLabel", "(JI)C", (void*)nativeGetDisplayLabel},
+ {"nativeGetKeyboardType", "(J)I", (void*)nativeGetKeyboardType},
+ {"nativeGetEvents", "(J[C)[Landroid/view/KeyEvent;", (void*)nativeGetEvents},
+ {"nativeObtainEmptyKeyCharacterMap", "(I)Landroid/view/KeyCharacterMap;",
+ (void*)nativeObtainEmptyKeyCharacterMap},
+ {"nativeEquals", "(JJ)Z", (void*)nativeEquals},
};
int register_android_view_KeyCharacterMap(JNIEnv* env)
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index cbf4481..65b8b98 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -746,7 +746,7 @@
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
Rect crop(l, t, r, b);
- transaction->setCrop_legacy(ctrl, crop);
+ transaction->setCrop(ctrl, crop);
}
static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -1528,11 +1528,17 @@
}
static jboolean nativeSetDisplayBrightness(JNIEnv* env, jclass clazz, jobject displayTokenObject,
- jfloat brightness) {
+ jfloat sdrBrightness, jfloat sdrBrightnessNits,
+ jfloat displayBrightness, jfloat displayBrightnessNits) {
sp<IBinder> displayToken(ibinderForJavaObject(env, displayTokenObject));
if (displayToken == nullptr) {
return JNI_FALSE;
}
+ gui::DisplayBrightness brightness;
+ brightness.sdrWhitePoint = sdrBrightness;
+ brightness.sdrWhitePointNits = sdrBrightnessNits;
+ brightness.displayBrightness = displayBrightness;
+ brightness.displayBrightnessNits = displayBrightnessNits;
status_t error = SurfaceComposerClient::setDisplayBrightness(displayToken, brightness);
return error == OK ? JNI_TRUE : JNI_FALSE;
}
@@ -1860,7 +1866,7 @@
(void*)nativeSyncInputWindows },
{"nativeGetDisplayBrightnessSupport", "(Landroid/os/IBinder;)Z",
(void*)nativeGetDisplayBrightnessSupport },
- {"nativeSetDisplayBrightness", "(Landroid/os/IBinder;F)Z",
+ {"nativeSetDisplayBrightness", "(Landroid/os/IBinder;FFFF)Z",
(void*)nativeSetDisplayBrightness },
{"nativeReadTransactionFromParcel", "(Landroid/os/Parcel;)J",
(void*)nativeReadTransactionFromParcel },
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0bed29b..be17d92 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -825,7 +825,7 @@
PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
- bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
+ bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, true);
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
diff --git a/core/proto/android/net/networkcapabilities.proto b/core/proto/android/net/networkcapabilities.proto
new file mode 100644
index 0000000..edb6c04
--- /dev/null
+++ b/core/proto/android/net/networkcapabilities.proto
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.net;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/proto_logging/stats/enums/net/enums.proto";
+
+/**
+ * An android.net.NetworkCapabilities object.
+ */
+message NetworkCapabilitiesProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ repeated Transport transports = 1;
+
+ enum NetCapability {
+ // Indicates this is a network that has the ability to reach the
+ // carrier's MMSC for sending and receiving MMS messages.
+ NET_CAPABILITY_MMS = 0;
+ // Indicates this is a network that has the ability to reach the
+ // carrier's SUPL server, used to retrieve GPS information.
+ NET_CAPABILITY_SUPL = 1;
+ // Indicates this is a network that has the ability to reach the
+ // carrier's DUN or tethering gateway.
+ NET_CAPABILITY_DUN = 2;
+ // Indicates this is a network that has the ability to reach the
+ // carrier's FOTA portal, used for over the air updates.
+ NET_CAPABILITY_FOTA = 3;
+ // Indicates this is a network that has the ability to reach the
+ // carrier's IMS servers, used for network registration and signaling.
+ NET_CAPABILITY_IMS = 4;
+ // Indicates this is a network that has the ability to reach the
+ // carrier's CBS servers, used for carrier specific services.
+ NET_CAPABILITY_CBS = 5;
+ // Indicates this is a network that has the ability to reach a Wi-Fi
+ // direct peer.
+ NET_CAPABILITY_WIFI_P2P = 6;
+ // Indicates this is a network that has the ability to reach a carrier's
+ // Initial Attach servers.
+ NET_CAPABILITY_IA = 7;
+ // Indicates this is a network that has the ability to reach a carrier's
+ // RCS servers, used for Rich Communication Services.
+ NET_CAPABILITY_RCS = 8;
+ // Indicates this is a network that has the ability to reach a carrier's
+ // XCAP servers, used for configuration and control.
+ NET_CAPABILITY_XCAP = 9;
+ // Indicates this is a network that has the ability to reach a carrier's
+ // Emergency IMS servers or other services, used for network signaling
+ // during emergency calls.
+ NET_CAPABILITY_EIMS = 10;
+ // Indicates that this network is unmetered.
+ NET_CAPABILITY_NOT_METERED = 11;
+ // Indicates that this network should be able to reach the internet.
+ NET_CAPABILITY_INTERNET = 12;
+ // Indicates that this network is available for general use. If this is
+ // not set applications should not attempt to communicate on this
+ // network. Note that this is simply informative and not enforcement -
+ // enforcement is handled via other means. Set by default.
+ NET_CAPABILITY_NOT_RESTRICTED = 13;
+ // Indicates that the user has indicated implicit trust of this network.
+ // This generally means it's a sim-selected carrier, a plugged in
+ // ethernet, a paired BT device or a wifi the user asked to connect to.
+ // Untrusted networks are probably limited to unknown wifi AP. Set by
+ // default.
+ NET_CAPABILITY_TRUSTED = 14;
+ // Indicates that this network is not a VPN. This capability is set by
+ // default and should be explicitly cleared for VPN networks.
+ NET_CAPABILITY_NOT_VPN = 15;
+ // Indicates that connectivity on this network was successfully
+ // validated. For example, for a network with NET_CAPABILITY_INTERNET,
+ // it means that Internet connectivity was successfully detected.
+ NET_CAPABILITY_VALIDATED = 16;
+ // Indicates that this network was found to have a captive portal in
+ // place last time it was probed.
+ NET_CAPABILITY_CAPTIVE_PORTAL = 17;
+ // Indicates that this network is not roaming.
+ NET_CAPABILITY_NOT_ROAMING = 18;
+ // Indicates that this network is available for use by apps, and not a
+ // network that is being kept up in the background to facilitate fast
+ // network switching.
+ NET_CAPABILITY_FOREGROUND = 19;
+ }
+ repeated NetCapability capabilities = 2;
+
+ // Passive link bandwidth. This is a rough guide of the expected peak
+ // bandwidth for the first hop on the given transport. It is not measured,
+ // but may take into account link parameters (Radio technology, allocated
+ // channels, etc).
+ optional int32 link_up_bandwidth_kbps = 3;
+ optional int32 link_down_bandwidth_kbps = 4;
+
+ optional string network_specifier = 5 [ (.android.privacy).dest = DEST_EXPLICIT ];
+
+ // True if this object specifies a signal strength.
+ optional bool can_report_signal_strength = 6;
+ // This is a signed integer, and higher values indicate better signal. The
+ // exact units are bearer-dependent. For example, Wi-Fi uses RSSI.
+ // Only valid if can_report_signal_strength is true.
+ optional sint32 signal_strength = 7;
+}
diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto
index 0041f19..57b9f71 100644
--- a/core/proto/android/net/networkrequest.proto
+++ b/core/proto/android/net/networkrequest.proto
@@ -20,8 +20,8 @@
option java_multiple_files = true;
+import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/net/networkcapabilities.proto";
/**
* An android.net.NetworkRequest object.
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 2b665c0..998ec96 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -55,13 +55,13 @@
import "frameworks/base/core/proto/android/service/procstats.proto";
import "frameworks/base/core/proto/android/service/restricted_image.proto";
import "frameworks/base/core/proto/android/service/sensor_service.proto";
+import "frameworks/base/core/proto/android/service/usb.proto";
import "frameworks/base/core/proto/android/util/event_log_tags.proto";
import "frameworks/base/core/proto/android/util/log.proto";
import "frameworks/base/core/proto/android/util/textdump.proto";
import "frameworks/base/core/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/section.proto";
import "frameworks/base/proto/src/ipconnectivity.proto";
-import "frameworks/proto_logging/stats/enums/service/usb.proto";
import "packages/modules/Permission/service/proto/com/android/role/roleservice.proto";
package android.os;
@@ -513,17 +513,17 @@
optional com.android.server.powerstats.PowerStatsServiceMeterProto powerstats_meter = 3054 [
(section).type = SECTION_DUMPSYS,
- (section).args = "power_stats --proto meter"
+ (section).args = "powerstats --proto meter"
];
optional com.android.server.powerstats.PowerStatsServiceModelProto powerstats_model = 3055 [
(section).type = SECTION_DUMPSYS,
- (section).args = "power_stats --proto model"
+ (section).args = "powerstats --proto model"
];
optional com.android.server.powerstats.PowerStatsServiceResidencyProto powerstats_residency = 3056 [
(section).type = SECTION_DUMPSYS,
- (section).args = "power_stats --proto residency"
+ (section).args = "powerstats --proto residency"
];
// Dumps in text format (on userdebug and eng builds only): 4000 ~ 4999
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 632d372..dca6002 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -192,6 +192,12 @@
optional Camera camera = 12;
optional SettingProto carrier_apps_handled = 13 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ message Clipboard {
+ optional SettingProto show_access_notifications = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional Clipboard clipboard = 89;
+
optional SettingProto cmas_additional_broadcast_pkg = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
repeated SettingProto completed_categories = 15;
optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -647,5 +653,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 89;
+ // Next tag = 90;
}
diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto
index 900235e..bbb0edd 100644
--- a/core/proto/android/server/biometrics.proto
+++ b/core/proto/android/server/biometrics.proto
@@ -125,6 +125,13 @@
// User states for this sensor.
repeated UserStateProto user_states = 4;
+
+ // True if resetLockout requires a HAT to be verified in the TEE or equivalent.
+ optional bool reset_lockout_requires_hardware_auth_token = 5;
+
+ // True if a HAT is required (field above) AND a challenge needs to be generated by the
+ // biometric TEE (or equivalent), and wrapped within the HAT.
+ optional bool reset_lockout_requires_challenge = 6;
}
// State of a specific user for a specific sensor.
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index e32c07f..3a959f1 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -57,6 +57,8 @@
// Time attributes stored as an offset of the IntervalStats's beginTime.
optional int64 last_time_visible_ms = 10;
optional int64 total_time_visible_ms = 11;
+ // Time attributes stored as an offset of the IntervalStats's beginTime.
+ optional int64 last_time_component_used_ms = 12;
}
// Stores the relevant information an IntervalStats will have about a Configuration
diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto
index 664c22d..3e5cd92 100644
--- a/core/proto/android/server/usagestatsservice_v2.proto
+++ b/core/proto/android/server/usagestatsservice_v2.proto
@@ -81,6 +81,7 @@
optional int64 total_time_service_used_ms = 9;
optional int64 last_time_visible_ms = 10;
optional int64 total_time_visible_ms = 11;
+ optional int64 last_time_component_used_ms = 12;
}
/**
diff --git a/core/proto/android/service/enums.proto b/core/proto/android/service/enums.proto
deleted file mode 100644
index b64e685..0000000
--- a/core/proto/android/service/enums.proto
+++ /dev/null
@@ -1,40 +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.
- */
-
-syntax = "proto2";
-package android.service;
-
-option java_outer_classname = "ServiceProtoEnums";
-option java_multiple_files = true;
-
-enum UsbEndPointType {
- USB_ENDPOINT_TYPE_XFER_CONTROL = 0;
- USB_ENDPOINT_TYPE_XFER_ISOC = 1;
- USB_ENDPOINT_TYPE_XFER_BULK = 2;
- USB_ENDPOINT_TYPE_XFER_INT = 3;
-}
-
-enum UsbEndPointDirection {
- USB_ENDPOINT_DIR_OUT = 0;
- USB_ENDPOINT_DIR_IN = 0x80;
-}
-
-enum UsbConnectionRecordMode {
- USB_CONNECTION_RECORD_MODE_CONNECT = 0;
- USB_CONNECTION_RECORD_MODE_CONNECT_BADPARSE = 1;
- USB_CONNECTION_RECORD_MODE_CONNECT_BADDEVICE = 2;
- USB_CONNECTION_RECORD_MODE_DISCONNECT = -1;
-}
\ No newline at end of file
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
new file mode 100644
index 0000000..45f8c132
--- /dev/null
+++ b/core/proto/android/service/usb.proto
@@ -0,0 +1,432 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.service.usb;
+
+option java_multiple_files = true;
+option java_outer_classname = "UsbServiceProto";
+
+import "frameworks/base/core/proto/android/content/component_name.proto";
+import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/proto_logging/stats/enums/service/enums.proto";
+
+message UsbServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbDeviceManagerProto device_manager = 1;
+ optional UsbHostManagerProto host_manager = 2;
+ optional UsbPortManagerProto port_manager = 3;
+ optional UsbAlsaManagerProto alsa_manager = 4;
+ optional UsbSettingsManagerProto settings_manager = 5;
+ optional UsbPermissionsManagerProto permissions_manager = 6;
+}
+
+message UsbDeviceManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbHandlerProto handler = 1;
+ optional UsbDebuggingManagerProto debugging_manager = 2;
+}
+
+message UsbHandlerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ /* Same as android.hardware.usb.gadget.V1_0.GadgetFunction.* */
+ enum Function {
+ FUNCTION_ADB = 1;
+ FUNCTION_ACCESSORY = 2;
+ FUNCTION_MTP = 4;
+ FUNCTION_MIDI = 8;
+ FUNCTION_PTP = 16;
+ FUNCTION_RNDIS = 32;
+ FUNCTION_AUDIO_SOURCE = 64;
+ }
+
+ repeated Function current_functions = 1;
+ optional bool current_functions_applied = 2;
+ repeated Function screen_unlocked_functions = 3;
+ optional bool screen_locked = 4;
+ optional bool connected = 5;
+ optional bool configured = 6;
+ optional UsbAccessoryProto current_accessory = 7;
+ optional bool host_connected = 8;
+ optional bool source_power = 9;
+ optional bool sink_power = 10;
+ optional bool usb_charging = 11;
+ optional bool hide_usb_notification = 12;
+ optional bool audio_accessory_connected = 13;
+ optional bool adb_enabled = 14;
+ optional string kernel_state = 15;
+ optional string kernel_function_list = 16;
+}
+
+message UsbAccessoryProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string manufacturer = 1;
+ optional string model = 2;
+ // For "classical" USB-accessories the manufacturer bakes this into the
+ // firmware of the device. If an Android phone is configured as accessory, the
+ // app that sets up the accessory side of the connection set this. Either way,
+ // these are part of the detection protocol, and so they cannot be user set or
+ // unique.
+ optional string description = 3;
+ optional string version = 4;
+ optional string uri = 5 [ (android.privacy).dest = DEST_EXPLICIT ];
+ // Non-resettable hardware ID.
+ optional string serial = 6 [ (android.privacy).dest = DEST_LOCAL ];
+}
+
+message UsbDebuggingManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional bool connected_to_adb = 1;
+ // A workstation that connects to the phone for debugging is identified by
+ // this key.
+ optional string last_key_received = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
+ optional string user_keys = 3 [ (android.privacy).dest = DEST_LOCAL ];
+ optional string system_keys = 4 [ (android.privacy).dest = DEST_LOCAL ];
+}
+
+message UsbHostManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional android.content.ComponentNameProto default_usb_host_connection_handler = 1;
+ repeated UsbDeviceProto devices = 2;
+ optional int32 num_connects = 3;
+ repeated UsbConnectionRecordProto connections = 4;
+}
+
+message UsbDeviceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Generic USB name, not user-provided.
+ optional string name = 1;
+ // ID specific to the vendor, not the device.
+ optional int32 vendor_id = 2;
+ // ID of this product type: Each vendor gives each product a unique ID. E.g.
+ // all mice of the same model would have the same ID.
+ optional int32 product_id = 3;
+ optional int32 class = 4;
+ optional int32 subclass = 5;
+ optional int32 protocol = 6;
+ optional string manufacturer_name = 7;
+ optional string product_name = 8;
+ optional string version = 9;
+ // Non-resettable hardware ID.
+ optional string serial_number = 10 [ (android.privacy).dest = DEST_LOCAL ];
+ repeated UsbConfigurationProto configurations = 11;
+}
+
+message UsbConfigurationProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // A single USB device can have several configurations and the app accessing
+ // the USB device can switch between them. At any time only one can be active.
+ // Each configuration can present completely different interfaces end
+ // endpoints, i.e. a completely different behavior.
+ optional int32 id = 1;
+ // Hardware-defined name, not set by the user.
+ optional string name = 2;
+ optional uint32 attributes = 3;
+ optional int32 max_power = 4;
+ repeated UsbInterfaceProto interfaces = 5;
+}
+
+message UsbInterfaceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Hardware defined. This is the id used by the app to identify the interface.
+ optional int32 id = 1;
+ optional int32 alternate_settings = 2;
+ optional string name = 3;
+ optional int32 class = 4;
+ optional int32 subclass = 5;
+ optional int32 protocol = 6;
+ repeated UsbEndPointProto endpoints = 7;
+}
+
+message UsbEndPointProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 endpoint_number = 1;
+ optional android.service.UsbEndPointDirection direction = 2;
+ // The address of the endpoint. Needed to read and write to the endpoint.
+ optional int32 address = 3;
+ optional android.service.UsbEndPointType type = 4;
+ optional uint32 attributes = 5;
+ optional int32 max_packet_size = 6;
+ optional int32 interval = 7;
+}
+
+message UsbConnectionRecordProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // usb device's address, e.g. 001/002, nothing about the phone
+ optional string device_address = 1;
+ optional android.service.UsbConnectionRecordMode mode = 2;
+ optional int64 timestamp = 3;
+ optional int32 manufacturer = 4;
+ optional int32 product = 5;
+ optional UsbIsHeadsetProto is_headset = 6;
+}
+
+message UsbIsHeadsetProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional bool in = 1;
+ optional bool out = 2;
+}
+
+message UsbPortManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional bool is_simulation_active = 1;
+ repeated UsbPortInfoProto usb_ports = 2;
+ optional bool enable_usb_data_signaling = 3;
+}
+
+message UsbPortInfoProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbPortProto port = 1;
+ optional UsbPortStatusProto status = 2;
+ optional bool can_change_mode = 3;
+ optional bool can_change_power_role = 4;
+ optional bool can_change_data_role = 5;
+ optional int64 connected_at_millis = 6;
+ optional int64 last_connect_duration_millis = 7;
+}
+
+message UsbPortProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ /* Same as android.hardware.usb.V1_1.Constants.PortMode_1_1 */
+ enum Mode {
+ MODE_NONE = 0;
+ MODE_UFP = 1;
+ MODE_DFP = 2;
+ MODE_DRP = 3;
+ MODE_AUDIO_ACCESSORY = 4;
+ MODE_DEBUG_ACCESSORY = 8;
+ }
+
+ // ID of the port. A device (eg: Chromebooks) might have multiple ports.
+ optional string id = 1;
+ repeated Mode supported_modes = 2;
+}
+
+message UsbPortStatusProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ /* Same as android.hardware.usb.V1_0.Constants.PortPowerRole */
+ enum PowerRole {
+ POWER_ROLE_NONE = 0;
+ POWER_ROLE_SOURCE = 1;
+ POWER_ROLE_SINK = 2;
+ }
+
+ /* Same as android.hardware.usb.V1_0.Constants.PortDataRole */
+ enum DataRole {
+ DATA_ROLE_NONE = 0;
+ DATA_ROLE_HOST = 1;
+ DATA_ROLE_DEVICE = 2;
+ }
+
+ optional bool connected = 1;
+ optional UsbPortProto.Mode current_mode = 2;
+ optional PowerRole power_role = 3;
+ optional DataRole data_role = 4;
+ repeated UsbPortStatusRoleCombinationProto role_combinations = 5;
+ optional android.service.ContaminantPresenceStatus contaminant_presence_status = 6;
+}
+
+message UsbPortStatusRoleCombinationProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbPortStatusProto.PowerRole power_role = 1;
+ optional UsbPortStatusProto.DataRole data_role = 2;
+}
+
+message UsbAlsaManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 cards_parser = 1;
+ repeated UsbAlsaDeviceProto alsa_devices = 2;
+ repeated UsbMidiDeviceProto midi_devices = 3;
+}
+
+message UsbAlsaDeviceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 card = 1;
+ optional int32 device = 2;
+ optional string name = 3;
+ optional bool has_playback = 4;
+ optional bool has_capture = 5;
+ // usb device's address, e.g. 001/002, nothing about the phone
+ optional string address = 6;
+}
+
+message UsbMidiDeviceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 card = 1;
+ optional int32 device = 2;
+ // usb device's address, e.g. 001/002, nothing about the phone
+ optional string device_address = 3;
+}
+
+message UsbSettingsManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ repeated UsbUserSettingsManagerProto user_settings = 1;
+ repeated UsbProfileGroupSettingsManagerProto profile_group_settings = 2;
+}
+
+message UsbUserSettingsManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 user_id = 1;
+ reserved 2; // previously device_permissions, now unused
+ reserved 3; // previously accessory_permissions, now unused
+ repeated UsbDeviceAttachedActivities device_attached_activities = 4;
+ repeated UsbAccessoryAttachedActivities accessory_attached_activities = 5;
+}
+
+message UsbProfileGroupSettingsManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The user id of the personal profile if the device has a work profile.
+ optional int32 parent_user_id = 1;
+ repeated UsbSettingsDevicePreferenceProto device_preferences = 2;
+ repeated UsbSettingsAccessoryPreferenceProto accessory_preferences = 3;
+}
+
+message UsbSettingsDevicePreferenceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbDeviceFilterProto filter = 1;
+ optional UserPackageProto user_package = 2;
+}
+
+message UsbPermissionsManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ repeated UsbUserPermissionsManagerProto user_permissions = 1;
+}
+
+message UsbUserPermissionsManagerProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 user_id = 1;
+
+ repeated UsbDevicePermissionProto device_permissions = 2;
+ repeated UsbAccessoryPermissionProto accessory_permissions = 3;
+
+ repeated UsbDevicePersistentPermissionProto device_persistent_permissions = 4;
+ repeated UsbAccessoryPersistentPermissionProto accessory_persistent_permissions = 5;
+}
+
+message UsbDevicePermissionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Name of device set by manufacturer
+ // All devices of the same model have the same name
+ optional string device_name = 1;
+ repeated int32 uids = 2;
+}
+
+message UsbAccessoryPermissionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Description of accessory set by manufacturer
+ // All accessories of the same model have the same description
+ optional string accessory_description = 1;
+ repeated int32 uids = 2;
+}
+
+message UsbDevicePersistentPermissionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbDeviceFilterProto device_filter = 1;
+ repeated UsbUidPermissionProto permission_values = 2;
+}
+
+message UsbAccessoryPersistentPermissionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbAccessoryFilterProto accessory_filter = 1;
+ repeated UsbUidPermissionProto permission_values = 2;
+}
+
+message UsbUidPermissionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 uid = 1;
+ optional bool is_granted = 2;
+}
+
+message UsbDeviceFilterProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Mirrors the vendor_id of UsbDeviceProto.
+ optional int32 vendor_id = 1;
+ optional int32 product_id = 2;
+ optional int32 class = 3;
+ optional int32 subclass = 4;
+ optional int32 protocol = 5;
+ optional string manufacturer_name = 6;
+ optional string product_name = 7;
+ optional string serial_number = 8 [ (android.privacy).dest = DEST_EXPLICIT ];
+}
+
+message UserPackageProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 user_id = 1;
+ optional string package_name =2;
+}
+
+message UsbSettingsAccessoryPreferenceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UsbAccessoryFilterProto filter = 1;
+ optional UserPackageProto user_package = 2;
+}
+
+message UsbAccessoryFilterProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string manufacturer = 1;
+ optional string model = 2;
+ optional string version = 3;
+}
+
+message UsbDeviceAttachedActivities {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional android.content.ComponentNameProto activity = 1;
+ repeated UsbDeviceFilterProto filters = 2;
+}
+
+message UsbAccessoryAttachedActivities {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional android.content.ComponentNameProto activity = 1;
+ repeated UsbAccessoryFilterProto filters = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 99ad6d1..d88f259 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -694,6 +694,7 @@
<!-- Added in S -->
<protected-broadcast android:name="android.intent.action.REBOOT_READY" />
+ <protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -987,6 +988,23 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:protectionLevel="signature|appop|preinstalled" />
+ <!-- Allows an application to modify and delete media files on this device or any connected
+ storage device without user confirmation. Applications must already be granted the
+ {@link #READ_EXTERNAL_STORAGE} or {@link #MANAGE_EXTERNAL_STORAGE}} permissions for this
+ permission to take effect.
+ <p>Even if applications are granted this permission, if applications want to modify or
+ delete media files, they also must get the access by calling
+ {@link android.provider.MediaStore#createWriteRequest(ContentResolver, Collection)},
+ {@link android.provider.MediaStore#createDeleteRequest(ContentResolver, Collection)}, or
+ {@link android.provider.MediaStore#createTrashRequest(ContentResolver, Collection, boolean)}.
+ <p>This permission doesn't give read or write access directly. It only prevents the user
+ confirmation dialog for these requests.
+ <p>If applications are not granted {@link #ACCESS_MEDIA_LOCATION}, the system also pops up
+ the user confirmation dialog for the write request.
+ <p>Protection level: signature|appop|preinstalled -->
+ <permission android:name="android.permission.MANAGE_MEDIA"
+ android:protectionLevel="signature|appop|preinstalled" />
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device location -->
<!-- ====================================================================== -->
@@ -2292,11 +2310,6 @@
<permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows read access to privileged network state in the device config.
- @hide Used internally. -->
- <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
Often required in authentication to access the carrier's server and manage services
of the subscriber.
@@ -2775,11 +2788,11 @@
The app can check whether it has this authorization by calling
{@link android.provider.Settings#canDrawOverlays
Settings.canDrawOverlays()}.
- <p>Protection level: signature|appop|installer|appPredictor|pre23|development -->
+ <p>Protection level: signature|setup|appop|installer|appPredictor|pre23|development -->
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
- android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" />
+ android:protectionLevel="signature|setup|appop|installer|appPredictor|pre23|development" />
<!-- @SystemApi @hide Allows an application to create windows using the type
{@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
@@ -3690,6 +3703,13 @@
<permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage hotword detection on the device.
+ <p>Protection level: internal|preinstalled
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.MANAGE_HOTWORD_DETECTION"
+ android:protectionLevel="internal|preinstalled" />
+
<!-- Must be required by a {@link android.service.autofill.AutofillService},
to ensure that only the system can bind to it.
<p>Protection level: signature
@@ -4365,6 +4385,11 @@
<permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
android:protectionLevel="signature|privileged" />
+ <!-- @TestApi Allows an application to query audio related state.
+ @hide -->
+ <permission android:name="android.permission.QUERY_AUDIO_STATE"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to modify what effects are applied to all audio
(matching certain criteria) from any application.
<p>Not for use by third-party applications.</p>
@@ -5419,6 +5444,8 @@
intents}.
<p>Protection level: normal -->
<permission android:name="android.permission.USE_FULL_SCREEN_INTENT"
+ android:label="@string/permlab_fullScreenIntent"
+ android:description="@string/permdesc_fullScreenIntent"
android:protectionLevel="normal" />
<!-- @SystemApi Allows requesting the framework broadcast the
@@ -5519,6 +5546,10 @@
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
android:protectionLevel="signature|appPredictor" />
+ <!-- @hide @SystemApi Allows an application to manage app hibernation state. -->
+ <permission android:name="android.permission.MANAGE_APP_HIBERNATION"
+ android:protectionLevel="signature|installer" />
+
<!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
<p>CTS tests will use UiAutomation.adoptShellPermissionIdentity() to gain access. -->
<permission android:name="android.permission.RESET_APP_ERRORS"
@@ -5600,6 +5631,10 @@
<!-- Attribution for Gnss Time Update service. -->
<attribution android:tag="GnssTimeUpdateService"
android:label="@string/gnss_time_update_service"/>
+ <!-- Attribution for MusicRecognitionManagerService.
+ <p>Not for use by third-party applications.</p> -->
+ <attribution android:tag="MusicRecognitionManagerService"
+ android:label="@string/music_recognition_manager_service"/>
<application android:process="system"
android:persistent="true"
diff --git a/core/res/res/drawable/ic_accessibility_24dp.xml b/core/res/res/drawable/ic_accessibility_24dp.xml
new file mode 100644
index 0000000..51e6959
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_24dp.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="21dp"
+ android:height="21dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1S6.11,6.7 3.5,6L3,8c1.86,0.5 4,0.83 6,
+ 1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1L20.5,6zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2s-2,
+ 0.9 -2,2S10.9,6 12,6z"
+ android:fillColor="#FF000000"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index f92e6d6..b969fa4 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -39,7 +39,7 @@
android:layout_height="@dimen/notification_expand_button_pill_height"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:gravity="center_vertical"
- android:paddingLeft="8dp"
+ android:paddingStart="8dp"
/>
<ImageView
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 2d1c342..b9a3625 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -27,7 +27,7 @@
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_action_list_height"
+ android:layout_marginBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
diff --git a/core/res/res/layout/notification_template_material_big_call.xml b/core/res/res/layout/notification_template_material_big_call.xml
new file mode 100644
index 0000000..1d50467
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_big_call.xml
@@ -0,0 +1,107 @@
+<?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
+ -->
+<com.android.internal.widget.CallLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="call"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+ <include layout="@layout/notification_template_conversation_icon_container" />
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_content_margin"
+ android:orientation="vertical"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:orientation="vertical"
+ android:minHeight="68dp"
+ >
+
+ <include
+ layout="@layout/notification_template_conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <include layout="@layout/notification_template_text_multiline" />
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ layout="@layout/notification_template_progress"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ 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"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_material_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <include
+ layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ />
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 2954ba2..86e7dec 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -31,7 +31,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="@dimen/notification_content_margin_top"
- android:layout_marginBottom="@dimen/notification_action_list_height"
+ android:layout_marginBottom="@dimen/notification_content_margin"
android:clipToPadding="false"
android:orientation="vertical"
>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 7b52ec3..5d9e761 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?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");
@@ -19,7 +18,6 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
android:clipChildren="false"
android:tag="call"
android:theme="@style/Theme.DeviceDefault.Notification"
@@ -29,58 +27,46 @@
<include layout="@layout/notification_template_conversation_icon_container" />
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_action_list_height"
- android:orientation="vertical"
+ android:layout_height="80dp"
+ android:orientation="horizontal"
>
<LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_gravity="top"
- android:orientation="horizontal"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:orientation="vertical"
+ android:minHeight="68dp"
>
- <LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginStart="@dimen/conversation_content_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:orientation="vertical"
- android:minHeight="68dp"
- >
-
- <include
- layout="@layout/notification_template_conversation_header"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- />
-
- <include layout="@layout/notification_template_text" />
-
- <include
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_progress_bar_height"
- android:layout_marginTop="@dimen/notification_progress_margin_top"
- layout="@layout/notification_template_progress"
- />
- </LinearLayout>
-
- <!-- TODO(b/179178086): remove padding from main column when this is visible -->
- <include layout="@layout/notification_expand_button"
+ <include
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|end"
/>
+ <include layout="@layout/notification_template_text" />
+
</LinearLayout>
- <include layout="@layout/notification_material_action_list" />
+ <FrameLayout
+ 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"
+ />
+
+ </FrameLayout>
</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5412db6..9e1a085 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2194,6 +2194,7 @@
Note that even if no splashscreen content is set on the theme, the system may still
show a splash screen using the other attributes on the theme, like the
{@link android.R.attr#windowBackground}.
+ {@deprecated Use windowSplashscreenAnimatedIcon instead.}
-->
<attr name="windowSplashscreenContent" format="reference" />
@@ -3564,16 +3565,7 @@
<attr name="__removed2" format="boolean" />
<!-- Specifies whether the IME supports showing inline suggestions. -->
<attr name="supportsInlineSuggestions" format="boolean" />
- <!-- Specify one or more configuration changes that the IME will handle itself. If not
- specified, the IME will be restarted if any of these configuration changes happen in
- the system. Otherwise, the IME will remain running and its
- {@link android.inputmethodservice.InputMethodService#onConfigurationChanged}
- method is called with the new configuration.
- <p>Note that all of these configuration changes can impact the
- resource values seen by the application, so you will generally need
- to re-retrieve all resources (including view layouts, drawables, etc)
- to correctly handle any configuration change.-->
- <attr name="configChanges" />
+ <attr name="suppressesSpellChecker" format="boolean" />
</declare-styleable>
<!-- This is the subtype of InputMethod. Subtype can describe locales (for example, en_US and
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 601d66e..0318be7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2063,6 +2063,14 @@
requested. If it does support the feature, it will be as if the manifest didn't
request it at all. -->
<attr name="requiredNotFeature" format="string" />
+ <!-- Optional: set of flags that should apply to this permission request. -->
+ <attr name="usesPermissionFlags">
+ <!-- Strong assertion by a developer that they will never use this
+ permission to derive the physical location of the device, even
+ when the app has been granted the ACCESS_FINE_LOCATION and/or
+ ACCESS_COARSE_LOCATION permissions. -->
+ <flag name="neverForLocation" value="0x1" />
+ </attr>
</declare-styleable>
<!-- <code>required-feature</code> and <code>required-not-feature</code> elements inside
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 40c80db..ba21679 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2816,6 +2816,7 @@
<public type="attr" name="iconSpaceReserved" id="0x01010561"/>
<public type="attr" name="defaultFocusHighlightEnabled" id="0x01010562" />
<public type="attr" name="persistentWhenFeatureAvailable" id="0x01010563"/>
+ <!-- {@deprecated Use windowSplashscreenAnimatedIcon instead } -->
<public type="attr" name="windowSplashscreenContent" id="0x01010564" />
<!-- @hide @SystemApi -->
<public type="attr" name="requiredSystemPropertyName" id="0x01010565" />
@@ -3088,6 +3089,7 @@
<public name="selectableAsDefault"/>
<public name="isAccessibilityTool"/>
<public name="attributionTags"/>
+ <public name="suppressesSpellChecker" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 387c065..0228dfd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -453,6 +453,9 @@
<!-- Attribution for Gnss Time Update service. [CHAR LIMIT=NONE]-->
<string name="gnss_time_update_service">GNSS Time Update Service</string>
+ <!-- Attribution for MusicRecognitionManagerService. [CHAR LIMIT=NONE]-->
+ <string name="music_recognition_manager_service">Music Recognition Manager Service</string>
+
<!-- Factory reset warning dialog strings--> <skip />
<!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
<string name="factory_reset_warning">Your device will be erased</string>
@@ -740,6 +743,10 @@
magnification. [CHAR_LIMIT=NONE]-->
<string name="notification_channel_accessibility_magnification">Magnification</string>
+ <!-- Text shown when viewing channel settings for notifications related to accessibility
+ security policy. [CHAR_LIMIT=NONE]-->
+ <string name="notification_channel_accessibility_security_policy">Accessibility security policy</string>
+
<!-- Label for foreground service notification when one app is running.
[CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] -->
<string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is
@@ -889,6 +896,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_expandStatusBar">Allows the app to expand or collapse the status bar.</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_fullScreenIntent">display notifications as full screen activities on a locked device</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_fullScreenIntent">Allows the app to display notifications as full screen activities on a locked device</string>
+
<!-- Title of an application permission, listed so the user can install application shortcuts
in their Launcher -->
<string name="permlab_install_shortcut">install shortcuts</string>
@@ -5566,6 +5578,8 @@
<string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label">On-screen Accessibility Shortcut Chooser</string>
<!-- Label for triggering hardware accessibility shortcut action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_hardware_a11y_shortcut_label">Accessibility Shortcut</string>
+ <!-- Label for dismissing the notification shade [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dismiss_notification_shade">Dismiss Notification Shade</string>
<!-- Accessibility description of caption view -->
<string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
@@ -5879,9 +5893,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">New: Window Magnifier</string>
- <!-- Notification content to prompt the user that new magnification feature is available. [CHAR LIMIT=50] -->
- <string name="window_magnification_prompt_content">You can now magnify some or all of your screen</string>
+ <string name="window_magnification_prompt_title">Magnify part of your screen</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>
<!-- 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] -->
@@ -5900,4 +5914,9 @@
<string name="splash_screen_view_icon_description">Application icon</string>
<!-- Content description for the branding image on the splash screen. [CHAR LIMIT=50] -->
<string name="splash_screen_view_branding_description">Application branding image</string>
+
+ <!-- Notification title to prompt the user that some accessibility service has view and control access. [CHAR LIMIT=50] -->
+ <string name="view_and_control_notification_title">Check access settings</string>
+ <!-- Notification content to prompt the user that some accessibility service has view and control access. [CHAR LIMIT=none] -->
+ <string name="view_and_control_notification_content"><xliff:g id="service_name" example="TalkBack">%s</xliff:g> can view and control your screen. Tap to review.</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5a7b1fa..0f834933 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3115,6 +3115,7 @@
<!-- Notifications: CallStyle -->
<java-symbol type="layout" name="notification_template_material_call" />
+ <java-symbol type="layout" name="notification_template_material_big_call" />
<java-symbol type="string" name="call_notification_answer_action" />
<java-symbol type="string" name="call_notification_decline_action" />
<java-symbol type="string" name="call_notification_hang_up_action" />
@@ -3540,6 +3541,7 @@
<java-symbol type="string" name="notification_channel_system_changes" />
<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_defaultAutofillService" />
<java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
@@ -3986,6 +3988,7 @@
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_label" />
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" />
<java-symbol type="string" name="accessibility_system_action_hardware_a11y_shortcut_label" />
+ <java-symbol type="string" name="accessibility_system_action_dismiss_notification_shade" />
<java-symbol type="string" name="accessibility_freeform_caption" />
@@ -4320,4 +4323,8 @@
<java-symbol type="id" name="remote_views_next_child" />
<java-symbol type="id" name="remote_views_stable_id" />
+ <!-- View and control prompt -->
+ <java-symbol type="drawable" name="ic_accessibility_24dp" />
+ <java-symbol type="string" name="view_and_control_notification_title" />
+ <java-symbol type="string" name="view_and_control_notification_content" />
</resources>
diff --git a/core/res/res/values/vendor_allowed_personal_apps_org_owned_device.xml b/core/res/res/values/vendor_allowed_personal_apps_org_owned_device.xml
deleted file mode 100644
index 0435c30..0000000
--- a/core/res/res/values/vendor_allowed_personal_apps_org_owned_device.xml
+++ /dev/null
@@ -1,23 +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.
- */
--->
-<resources>
- <!-- A list of apps to be allowed in the personal profile of an organization-owned device. -->
- <string-array translatable="false" name="vendor_allowed_personal_apps_org_owned_device">
- </string-array>
-</resources>
diff --git a/core/tests/bluetoothtests/AndroidTest.xml b/core/tests/bluetoothtests/AndroidTest.xml
new file mode 100644
index 0000000..f93c4eb
--- /dev/null
+++ b/core/tests/bluetoothtests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for Bluetooth test cases">
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-suite-tag" value="apct-instrumentation"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="BluetoothTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="BluetoothTests"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.bluetooth.tests" />
+ <option name="hidden-api-checks" value="false"/>
+ <option name="runner" value="android.bluetooth.BluetoothTestRunner"/>
+ </test>
+</configuration>
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
index 8b3db7e..c287ea9 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
@@ -16,13 +16,18 @@
package android.bluetooth.le;
-import android.bluetooth.le.ScanRecord;
+import android.os.BytesMatcher;
import android.os.ParcelUuid;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.internal.util.HexDump;
+
import junit.framework.TestCase;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
/**
* Unit test cases for {@link ScanRecord}.
@@ -31,6 +36,66 @@
* 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
*/
public class ScanRecordTest extends TestCase {
+ /**
+ * Example raw beacons captured from a Blue Charm BC011
+ */
+ private static final String RECORD_URL = "0201060303AAFE1716AAFE10EE01626C7565636861726D626561636F6E730009168020691E0EFE13551109426C7565436861726D5F313639363835000000";
+ private static final String RECORD_UUID = "0201060303AAFE1716AAFE00EE626C7565636861726D31000000000001000009168020691E0EFE13551109426C7565436861726D5F313639363835000000";
+ private static final String RECORD_TLM = "0201060303AAFE1116AAFE20000BF017000008874803FB93540916802069080EFE13551109426C7565436861726D5F313639363835000000000000000000";
+ private static final String RECORD_IBEACON = "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C509168020691E0EFE13551109426C7565436861726D5F31363936383500000000";
+
+ @SmallTest
+ public void testMatchesAnyField_Eddystone_Parser() {
+ final List<String> found = new ArrayList<>();
+ final Predicate<byte[]> matcher = (v) -> {
+ found.add(HexDump.toHexString(v));
+ return false;
+ };
+ ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_URL))
+ .matchesAnyField(matcher);
+
+ assertEquals(Arrays.asList(
+ "020106",
+ "0303AAFE",
+ "1716AAFE10EE01626C7565636861726D626561636F6E7300",
+ "09168020691E0EFE1355",
+ "1109426C7565436861726D5F313639363835"), found);
+ }
+
+ @SmallTest
+ public void testMatchesAnyField_Eddystone() {
+ final BytesMatcher matcher = BytesMatcher.decode("⊆0016AAFE/00FFFFFF");
+ assertMatchesAnyField(RECORD_URL, matcher);
+ assertMatchesAnyField(RECORD_UUID, matcher);
+ assertMatchesAnyField(RECORD_TLM, matcher);
+ assertNotMatchesAnyField(RECORD_IBEACON, matcher);
+ }
+
+ @SmallTest
+ public void testMatchesAnyField_iBeacon_Parser() {
+ final List<String> found = new ArrayList<>();
+ final Predicate<byte[]> matcher = (v) -> {
+ found.add(HexDump.toHexString(v));
+ return false;
+ };
+ ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_IBEACON))
+ .matchesAnyField(matcher);
+
+ assertEquals(Arrays.asList(
+ "020106",
+ "1AFF4C000215426C7565436861726D426561636F6E730EFE1355C5",
+ "09168020691E0EFE1355",
+ "1109426C7565436861726D5F313639363835"), found);
+ }
+
+ @SmallTest
+ public void testMatchesAnyField_iBeacon() {
+ final BytesMatcher matcher = BytesMatcher.decode("⊆00FF4C0002/00FFFFFFFF");
+ assertNotMatchesAnyField(RECORD_URL, matcher);
+ assertNotMatchesAnyField(RECORD_UUID, matcher);
+ assertNotMatchesAnyField(RECORD_TLM, matcher);
+ assertMatchesAnyField(RECORD_IBEACON, matcher);
+ }
@SmallTest
public void testParser() {
@@ -70,4 +135,14 @@
}
}
+
+ private static void assertMatchesAnyField(String record, BytesMatcher matcher) {
+ assertTrue(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record))
+ .matchesAnyField(matcher));
+ }
+
+ private static void assertNotMatchesAnyField(String record, BytesMatcher matcher) {
+ assertFalse(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record))
+ .matchesAnyField(matcher));
+ }
}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index fe31b90..7ef1d5e 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
@@ -61,7 +61,7 @@
public void testPutDocument_throwsNullException() throws Exception {
// Create a document
AppSearchEmail inEmail =
- new AppSearchEmail.Builder("uri1")
+ new AppSearchEmail.Builder("namespace", "uri1")
.setFrom("from@example.com")
.setTo("to1@example.com", "to2@example.com")
.setSubject("testPut example")
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
index 119b70a..ed53d5f 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchEmailTest.java
@@ -25,7 +25,7 @@
@Test
public void testBuildEmailAndGetValue() {
AppSearchEmail email =
- new AppSearchEmail.Builder("uri")
+ new AppSearchEmail.Builder("namespace", "uri")
.setFrom("FakeFromAddress")
.setCc("CC1", "CC2")
// Score and Property are mixed into the middle to make sure
@@ -37,6 +37,7 @@
.setBody("EmailBody")
.build();
+ assertThat(email.getNamespace()).isEqualTo("namespace");
assertThat(email.getUri()).isEqualTo("uri");
assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
assertThat(email.getTo()).isNull();
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
index af77b6c..b884ddc 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
@@ -27,13 +27,13 @@
@Test
public void testRecreateFromParcel() {
GenericDocument inDoc =
- new GenericDocument.Builder<>("uri1", "schema1")
+ new GenericDocument.Builder<>("namespace", "uri1", "schema1")
.setScore(42)
.setPropertyString("propString", "Hello")
.setPropertyBytes("propBytes", new byte[][] {{1, 2}})
.setPropertyDocument(
"propDocument",
- new GenericDocument.Builder<>("uri2", "schema2")
+ new GenericDocument.Builder<>("namespace", "uri2", "schema2")
.setPropertyString("propString", "Goodbye")
.setPropertyBytes("propBytes", new byte[][] {{3, 4}})
.build())
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
index 7d175d9..7637214 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
@@ -31,8 +31,8 @@
public void addGenericDocument_byCollection() {
Set<AppSearchEmail> emails =
ImmutableSet.of(
- new AppSearchEmail.Builder("test1").build(),
- new AppSearchEmail.Builder("test2").build());
+ new AppSearchEmail.Builder("namespace", "test1").build(),
+ new AppSearchEmail.Builder("namespace", "test2").build());
PutDocumentsRequest request =
new PutDocumentsRequest.Builder().addGenericDocuments(emails).build();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 6c8b941..9915e38 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -133,7 +133,7 @@
int ident = 57;
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.flags = 42;
- activityInfo.maxAspectRatio = 2.4f;
+ activityInfo.setMaxAspectRatio(2.4f);
activityInfo.launchToken = "token";
activityInfo.applicationInfo = new ApplicationInfo();
activityInfo.packageName = "packageName";
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 6f3d7ae..f47fa39 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -182,7 +182,7 @@
int ident = 57;
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.flags = 42;
- activityInfo.maxAspectRatio = 2.4f;
+ activityInfo.setMaxAspectRatio(2.4f);
activityInfo.launchToken = "token";
activityInfo.applicationInfo = new ApplicationInfo();
activityInfo.packageName = "packageName";
diff --git a/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
new file mode 100644
index 0000000..85073b0
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeCapabilitiesTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.time;
+
+
+import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TimeCapabilitiesTest {
+
+ private static final UserHandle USER_HANDLE = UserHandle.of(332211);
+
+ @Test
+ public void testBuilder() {
+ TimeCapabilities capabilities = new TimeCapabilities.Builder(USER_HANDLE)
+ .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE)
+ .setSuggestTimeManuallyCapability(CAPABILITY_NOT_SUPPORTED)
+ .build();
+
+ assertThat(capabilities.getConfigureAutoTimeDetectionEnabledCapability())
+ .isEqualTo(CAPABILITY_NOT_APPLICABLE);
+ assertThat(capabilities.getSuggestTimeManuallyCapability())
+ .isEqualTo(CAPABILITY_NOT_SUPPORTED);
+
+ try {
+ new TimeCapabilities.Builder(USER_HANDLE)
+ .build();
+ fail("Should throw IllegalStateException");
+ } catch (IllegalStateException ignored) {
+ // expected
+ }
+
+ try {
+ new TimeCapabilities.Builder(USER_HANDLE)
+ .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_APPLICABLE)
+ .build();
+ fail("Should throw IllegalStateException");
+ } catch (IllegalStateException ignored) {
+ // expected
+ }
+
+ try {
+ new TimeCapabilities.Builder(USER_HANDLE)
+ .setSuggestTimeManuallyCapability(CAPABILITY_NOT_APPLICABLE)
+ .build();
+ fail("Should throw IllegalStateException");
+ } catch (IllegalStateException ignored) {
+ // expected
+ }
+ }
+
+ @Test
+ public void userHandle_notIgnoredInEquals() {
+ TimeCapabilities firstUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(1))
+ .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED)
+ .build();
+
+ TimeCapabilities secondUserCapabilities = new TimeCapabilities.Builder(UserHandle.of(2))
+ .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED)
+ .build();
+
+ assertThat(firstUserCapabilities).isNotEqualTo(secondUserCapabilities);
+ }
+
+ @Test
+ public void testParcelable() {
+ TimeCapabilities.Builder builder = new TimeCapabilities.Builder(USER_HANDLE)
+ .setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_NOT_SUPPORTED)
+ .setSuggestTimeManuallyCapability(CAPABILITY_NOT_SUPPORTED);
+
+ assertRoundTripParcelable(builder.build());
+
+ builder.setSuggestTimeManuallyCapability(CAPABILITY_POSSESSED);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setConfigureAutoTimeDetectionEnabledCapability(CAPABILITY_POSSESSED);
+ assertRoundTripParcelable(builder.build());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java
new file mode 100644
index 0000000..7c7cd12
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.time;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TimeConfigurationTest {
+
+ @Test
+ public void testBuilder() {
+ TimeConfiguration first = new TimeConfiguration.Builder()
+ .setAutoDetectionEnabled(true)
+ .build();
+
+ assertThat(first.isAutoDetectionEnabled()).isTrue();
+
+ TimeConfiguration copyFromBuilderConfiguration = new TimeConfiguration.Builder(first)
+ .build();
+
+ assertThat(first).isEqualTo(copyFromBuilderConfiguration);
+ }
+
+ @Test
+ public void testParcelable() {
+ TimeConfiguration.Builder builder = new TimeConfiguration.Builder();
+
+ assertRoundTripParcelable(builder.setAutoDetectionEnabled(true).build());
+
+ assertRoundTripParcelable(builder.setAutoDetectionEnabled(false).build());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
index 01a25b2..dd93997 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -16,8 +16,8 @@
package android.app.time;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static org.junit.Assert.assertEquals;
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
index 8de9454..083e37a 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsPersistenceTest.java
@@ -46,7 +46,8 @@
private static final String[] USAGESTATS_PERSISTED_FIELDS = {"mBeginTimeStamp", "mEndTimeStamp",
"mPackageName", "mPackageToken", "mLastEvent", "mAppLaunchCount", "mChooserCounts",
"mLastTimeUsed", "mTotalTimeInForeground", "mLastTimeForegroundServiceUsed",
- "mTotalTimeForegroundServiceUsed", "mLastTimeVisible", "mTotalTimeVisible"};
+ "mTotalTimeForegroundServiceUsed", "mLastTimeVisible", "mTotalTimeVisible",
+ "mLastTimeComponentUsed"};
// All fields in this list are defined in UsageStats but not persisted
private static final String[] USAGESTATS_IGNORED_FIELDS = {"CREATOR", "mActivities",
"mForegroundServices", "mLaunchCount", "mChooserCountsObfuscated"};
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
index 0ac00b8..858bbd2 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
@@ -20,6 +20,7 @@
import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
+import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED;
import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
import static android.app.usage.UsageEvents.Event.END_OF_DAY;
@@ -137,6 +138,7 @@
left.mPackageName = "com.test";
left.mBeginTimeStamp = 100000;
left.mTotalTimeInForeground = 10;
+ left.mLastTimeComponentUsed = 200000;
left.mActivities.put(1, Event.ACTIVITY_RESUMED);
left.mActivities.put(2, Event.ACTIVITY_RESUMED);
@@ -542,6 +544,19 @@
}
@Test
+ public void testEvent_APP_COMPONENT_USED() {
+ left.mPackageName = "com.test";
+ left.mBeginTimeStamp = 100000;
+ final String className = "com.test.component1";
+
+ left.update(className, 200000, APP_COMPONENT_USED, 0);
+ assertEquals(left.mLastTimeComponentUsed, 200000);
+
+ left.update(className, 300000, APP_COMPONENT_USED, 0);
+ assertEquals(left.mLastTimeComponentUsed, 300000);
+ }
+
+ @Test
public void testEvent_DEVICE_SHUTDOWN() {
testClosingEvent(DEVICE_SHUTDOWN);
}
@@ -586,6 +601,7 @@
assertEquals(us1.mBeginTimeStamp, us2.mBeginTimeStamp);
assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed);
assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible);
+ assertEquals(us1.mLastTimeComponentUsed, us2.mLastTimeComponentUsed);
assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed);
assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground);
assertEquals(us1.mTotalTimeForegroundServiceUsed, us2.mTotalTimeForegroundServiceUsed);
diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java
index 8895f9b..b282064 100644
--- a/core/tests/coretests/src/android/content/ContentProviderTest.java
+++ b/core/tests/coretests/src/android/content/ContentProviderTest.java
@@ -23,6 +23,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.net.Uri;
+import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
@@ -86,4 +87,11 @@
mCp.validateIncomingUri(
Uri.parse("content://com.example/foo/bar?foo=b//ar#foo=b//ar")));
}
+
+ @Test
+ public void testCreateContentUriAsUser() {
+ Uri uri = Uri.parse("content://com.example/foo/bar");
+ Uri expectedUri = Uri.parse("content://7@com.example/foo/bar");
+ assertEquals(expectedUri, ContentProvider.createContentUriAsUser(uri, UserHandle.of(7)));
+ }
}
diff --git a/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java b/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java
deleted file mode 100644
index 4863cfe..0000000
--- a/core/tests/coretests/src/android/inputmethodservice/InputMethodServiceTest.java
+++ /dev/null
@@ -1,83 +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 android.inputmethodservice;
-
-import static android.content.res.Configuration.KEYBOARD_12KEY;
-import static android.content.res.Configuration.NAVIGATION_NONAV;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-
-import static junit.framework.Assert.assertFalse;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.os.Build;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.rule.ServiceTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeoutException;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodServiceTest {
- private InputMethodService mService;
- private Context mContext;
- @Rule
- public final ServiceTestRule serviceRule = new ServiceTestRule();
-
- @Before
- public void setUp() throws TimeoutException {
- mContext = getInstrumentation().getContext();
- mService = new InputMethodService();
- }
-
- @Test
- public void testShouldImeRestartForConfig() throws Exception {
- // Make sure we preserve Pre-S behavior i.e. Service restarts.
- mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.R;
- Configuration config = mContext.getResources().getConfiguration();
- mService.setLastKnownConfig(config);
- assertTrue("IME should restart for Pre-S",
- mService.shouldImeRestartForConfig(config));
-
- // IME shouldn't restart on targetSdk S+ (with no config changes).
- mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.S;
- assertFalse("IME shouldn't restart for S+",
- mService.shouldImeRestartForConfig(config));
-
- // Screen density changed but IME doesn't handle congfigChanges
- config.densityDpi = 99;
- assertTrue("IME should restart for unhandled configChanges",
- mService.shouldImeRestartForConfig(config));
-
- // opt-in IME to handle config changes.
- mService.setHandledConfigChanges(ActivityInfo.CONFIG_DENSITY);
- assertFalse("IME shouldn't restart for S+ since it handles configChanges",
- mService.shouldImeRestartForConfig(config));
- }
-}
diff --git a/core/tests/coretests/src/android/os/BytesMatcherTest.java b/core/tests/coretests/src/android/os/BytesMatcherTest.java
index 67c1b3c9..b28e309 100644
--- a/core/tests/coretests/src/android/os/BytesMatcherTest.java
+++ b/core/tests/coretests/src/android/os/BytesMatcherTest.java
@@ -59,6 +59,19 @@
}
@Test
+ public void testPrefix() throws Exception {
+ BytesMatcher matcher = BytesMatcher.decode("⊆cafe,⊆beef/ff00");
+ assertTrue(matcher.test(hexStringToByteArray("cafe")));
+ assertFalse(matcher.test(hexStringToByteArray("caff")));
+ assertTrue(matcher.test(hexStringToByteArray("cafecafe")));
+ assertFalse(matcher.test(hexStringToByteArray("ca")));
+ assertTrue(matcher.test(hexStringToByteArray("beef")));
+ assertTrue(matcher.test(hexStringToByteArray("beff")));
+ assertTrue(matcher.test(hexStringToByteArray("beffbeff")));
+ assertFalse(matcher.test(hexStringToByteArray("be")));
+ }
+
+ @Test
public void testMacAddress() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("+cafe00112233/ffffff000000");
assertTrue(matcher.testMacAddress(
@@ -94,13 +107,23 @@
}
@Test
- public void testSerialize() throws Exception {
+ public void testSerialize_Empty() throws Exception {
BytesMatcher matcher = new BytesMatcher();
- matcher.addRejectRule(hexStringToByteArray("cafe00112233"),
+ matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
+
+ // Also very empty and null values
+ BytesMatcher.decode("");
+ BytesMatcher.decode(null);
+ }
+
+ @Test
+ public void testSerialize_Exact() throws Exception {
+ BytesMatcher matcher = new BytesMatcher();
+ matcher.addExactRejectRule(hexStringToByteArray("cafe00112233"),
hexStringToByteArray("ffffff000000"));
- matcher.addRejectRule(hexStringToByteArray("beef00112233"),
+ matcher.addExactRejectRule(hexStringToByteArray("beef00112233"),
null);
- matcher.addAcceptRule(hexStringToByteArray("000000000000"),
+ matcher.addExactAcceptRule(hexStringToByteArray("000000000000"),
hexStringToByteArray("000000000000"));
assertFalse(matcher.test(hexStringToByteArray("cafe00ffffff")));
@@ -116,6 +139,28 @@
}
@Test
+ public void testSerialize_Prefix() throws Exception {
+ BytesMatcher matcher = new BytesMatcher();
+ matcher.addExactRejectRule(hexStringToByteArray("aa"), null);
+ matcher.addExactAcceptRule(hexStringToByteArray("bb"), null);
+ matcher.addPrefixAcceptRule(hexStringToByteArray("aa"), null);
+ matcher.addPrefixRejectRule(hexStringToByteArray("bb"), null);
+
+ assertFalse(matcher.test(hexStringToByteArray("aa")));
+ assertTrue(matcher.test(hexStringToByteArray("bb")));
+ assertTrue(matcher.test(hexStringToByteArray("aaaa")));
+ assertFalse(matcher.test(hexStringToByteArray("bbbb")));
+
+ // Bounce through serialization pass and confirm it still works
+ matcher = BytesMatcher.decode(BytesMatcher.encode(matcher));
+
+ assertFalse(matcher.test(hexStringToByteArray("aa")));
+ assertTrue(matcher.test(hexStringToByteArray("bb")));
+ assertTrue(matcher.test(hexStringToByteArray("aaaa")));
+ assertFalse(matcher.test(hexStringToByteArray("bbbb")));
+ }
+
+ @Test
public void testOrdering_RejectFirst() throws Exception {
BytesMatcher matcher = BytesMatcher.decode("-ff/0f,+ff/f0");
assertFalse(matcher.test(hexStringToByteArray("ff")));
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index c06405a..09c36dd 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -16,9 +16,10 @@
package android.os;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import android.hardware.vibrator.IVibrator;
import android.platform.test.annotations.Presubmit;
@@ -33,72 +34,128 @@
@Test
public void testHasAmplitudeControl() {
- assertFalse(createInfo(/* capabilities= */ 0).hasAmplitudeControl());
- assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS
- | IVibrator.CAP_AMPLITUDE_CONTROL).hasAmplitudeControl());
+ VibratorInfo noCapabilities = new InfoBuilder().build();
+ assertFalse(noCapabilities.hasAmplitudeControl());
+ VibratorInfo composeAndAmplitudeControl = new InfoBuilder()
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS
+ | IVibrator.CAP_AMPLITUDE_CONTROL)
+ .build();
+ assertTrue(composeAndAmplitudeControl.hasAmplitudeControl());
}
@Test
public void testHasCapabilities() {
- assertTrue(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
- .hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
- assertFalse(createInfo(IVibrator.CAP_COMPOSE_EFFECTS)
- .hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
+ VibratorInfo info = new InfoBuilder()
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .build();
+ assertTrue(info.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS));
+ assertFalse(info.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL));
}
@Test
public void testIsEffectSupported() {
- VibratorInfo info = new VibratorInfo(/* id= */ 0, /* capabilities= */0,
- new int[]{VibrationEffect.EFFECT_CLICK}, null);
+ VibratorInfo noEffects = new InfoBuilder().build();
+ VibratorInfo canClick = new InfoBuilder()
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .build();
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
- createInfo(/* capabilities= */ 0).isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ noEffects.isEffectSupported(VibrationEffect.EFFECT_CLICK));
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
- info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
+ canClick.isEffectSupported(VibrationEffect.EFFECT_CLICK));
assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
- info.isEffectSupported(VibrationEffect.EFFECT_TICK));
+ canClick.isEffectSupported(VibrationEffect.EFFECT_TICK));
}
@Test
public void testIsPrimitiveSupported() {
- VibratorInfo info = new VibratorInfo(/* id= */ 0, IVibrator.CAP_COMPOSE_EFFECTS,
- null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
+ VibratorInfo info = new InfoBuilder()
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .build();
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
// Returns false when there is no compose capability.
- info = new VibratorInfo(/* id= */ 0, /* capabilities= */ 0,
- null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
+ info = new InfoBuilder()
+ .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .build();
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
}
@Test
public void testEquals() {
- VibratorInfo empty = new VibratorInfo(1, 0, null, null);
- VibratorInfo complete = new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
- new int[]{VibrationEffect.EFFECT_CLICK},
- new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK});
+ InfoBuilder completeBuilder = new InfoBuilder()
+ .setId(1)
+ .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .setQFactor(2f)
+ .setResonantFrequency(150f);
+ VibratorInfo complete = completeBuilder.build();
assertEquals(complete, complete);
- assertEquals(complete, new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
- new int[]{VibrationEffect.EFFECT_CLICK},
- new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK}));
+ assertEquals(complete, completeBuilder.build());
- assertFalse(empty.equals(new VibratorInfo(1, 0, new int[]{}, new int[]{})));
- assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
- new int[]{VibrationEffect.EFFECT_CLICK},
- new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
- assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
- new int[]{}, new int[]{})));
- assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
- null, new int[]{VibrationEffect.Composition.PRIMITIVE_CLICK})));
- assertFalse(complete.equals(new VibratorInfo(1, IVibrator.CAP_AMPLITUDE_CONTROL,
- new int[]{VibrationEffect.EFFECT_CLICK}, null)));
+ VibratorInfo completeWithComposeControl = completeBuilder
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .build();
+ assertNotEquals(complete, completeWithComposeControl);
+
+ VibratorInfo completeWithNoEffects = completeBuilder
+ .setSupportedEffects()
+ .setSupportedPrimitives()
+ .build();
+ assertNotEquals(complete, completeWithNoEffects);
+
+ VibratorInfo completeWithUnknownEffects = completeBuilder
+ .setSupportedEffects(null)
+ .build();
+ assertNotEquals(complete, completeWithNoEffects);
+
+ VibratorInfo completeWithUnknownPrimitives = completeBuilder
+ .setSupportedPrimitives(null)
+ .build();
+ assertNotEquals(complete, completeWithUnknownPrimitives);
+
+ VibratorInfo completeWithDifferentF0 = completeBuilder
+ .setResonantFrequency(complete.getResonantFrequency() + 3f)
+ .build();
+ assertNotEquals(complete, completeWithDifferentF0);
+
+ VibratorInfo completeWithUnknownF0 = completeBuilder
+ .setResonantFrequency(Float.NaN)
+ .build();
+ assertNotEquals(complete, completeWithUnknownF0);
+
+ VibratorInfo completeWithUnknownQFactor = completeBuilder
+ .setQFactor(Float.NaN)
+ .build();
+ assertNotEquals(complete, completeWithUnknownQFactor);
+
+ VibratorInfo completeWithDifferentQFactor = completeBuilder
+ .setQFactor(complete.getQFactor() + 3f)
+ .build();
+ assertNotEquals(complete, completeWithDifferentQFactor);
+
+ VibratorInfo empty = new InfoBuilder().setId(1).build();
+ VibratorInfo emptyWithKnownSupport = new InfoBuilder()
+ .setId(1)
+ .setSupportedEffects()
+ .setSupportedPrimitives()
+ .build();
+ assertNotEquals(empty, emptyWithKnownSupport);
}
@Test
- public void testSerialization() {
- VibratorInfo original = new VibratorInfo(1, IVibrator.CAP_COMPOSE_EFFECTS,
- new int[]{VibrationEffect.EFFECT_CLICK}, null);
+ public void testParceling() {
+ VibratorInfo original = new InfoBuilder()
+ .setId(1)
+ .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
+ .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
+ .setSupportedPrimitives(null)
+ .setResonantFrequency(1.3f)
+ .setQFactor(Float.NaN)
+ .build();
Parcel parcel = Parcel.obtain();
original.writeToParcel(parcel, 0);
@@ -107,7 +164,47 @@
assertEquals(original, restored);
}
- private static VibratorInfo createInfo(long capabilities) {
- return new VibratorInfo(/* id= */ 0, capabilities, null, null);
+ private static class InfoBuilder {
+ private int mId = 0;
+ private int mCapabilities = 0;
+ private int[] mSupportedEffects = null;
+ private int[] mSupportedPrimitives = null;
+ private float mResonantFrequency = Float.NaN;
+ private float mQFactor = Float.NaN;
+
+ public InfoBuilder setId(int id) {
+ mId = id;
+ return this;
+ }
+
+ public InfoBuilder setCapabilities(int capabilities) {
+ mCapabilities = capabilities;
+ return this;
+ }
+
+ public InfoBuilder setSupportedEffects(int... supportedEffects) {
+ mSupportedEffects = supportedEffects;
+ return this;
+ }
+
+ public InfoBuilder setSupportedPrimitives(int... supportedPrimitives) {
+ mSupportedPrimitives = supportedPrimitives;
+ return this;
+ }
+
+ public InfoBuilder setResonantFrequency(float resonantFrequency) {
+ mResonantFrequency = resonantFrequency;
+ return this;
+ }
+
+ public InfoBuilder setQFactor(float qFactor) {
+ mQFactor = qFactor;
+ return this;
+ }
+
+ public VibratorInfo build() {
+ return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedPrimitives,
+ mResonantFrequency, mQFactor);
+ }
}
}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 7a2e6b7..5de55d7 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -91,7 +91,8 @@
@Test
public void testImeVisibility() {
- final InsetsSourceControl ime = new InsetsSourceControl(ITYPE_IME, mLeash, new Point());
+ final InsetsSourceControl ime =
+ new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -119,7 +120,8 @@
mImeConsumer.applyImeVisibility(true /* setVisible */);
// set control and verify visibility is applied.
- InsetsSourceControl control = new InsetsSourceControl(ITYPE_IME, mLeash, new Point());
+ InsetsSourceControl control =
+ new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
@@ -138,7 +140,7 @@
// set control and verify visibility is applied.
InsetsSourceControl control = Mockito.spy(
- new InsetsSourceControl(ITYPE_IME, mLeash, new Point()));
+ new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE));
// Simulate IME source control set this flag when the target has starting window.
control.setSkipAnimationOnce(true);
mController.onControlsChanged(new InsetsSourceControl[] { control });
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 873627e..613232f 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -94,13 +94,14 @@
InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState,
() -> mMockTransaction, mMockController);
topConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mTopLeash, new Point(0, 0)),
+ new InsetsSourceControl(
+ ITYPE_STATUS_BAR, mTopLeash, new Point(0, 0), Insets.of(0, 100, 0, 0)),
new int[1], new int[1]);
InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR,
mInsetsState, () -> mMockTransaction, mMockController);
navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash,
- new Point(400, 0)), new int[1], new int[1]);
+ new Point(400, 0), Insets.of(0, 0, 100, 0)), new int[1], new int[1]);
navConsumer.hide();
SparseArray<InsetsSourceControl> controls = new SparseArray<>();
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 2770ed8..ff505c4 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -213,7 +213,8 @@
mController.onFrameChanged(new Rect(0, 0, 100, 100));
mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
InsetsSourceControl control =
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
+ new InsetsSourceControl(
+ ITYPE_STATUS_BAR, mLeash, new Point(), Insets.of(0, 10, 0, 0));
mController.onControlsChanged(new InsetsSourceControl[] { control });
WindowInsetsAnimationControlListener controlListener =
mock(WindowInsetsAnimationControlListener.class);
@@ -814,7 +815,7 @@
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
- return new InsetsSourceControl(type, copy, new Point());
+ return new InsetsSourceControl(type, copy, new Point(), Insets.NONE);
}
private InsetsSourceControl[] createSingletonControl(@InternalInsetsType int type) {
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 7efd616..b3aa7e8 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -34,6 +34,7 @@
import android.app.Instrumentation;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
@@ -108,7 +109,8 @@
});
instrumentation.waitForIdleSync();
- mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()),
+ mConsumer.setControl(
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
new int[1], new int[1]);
}
@@ -177,7 +179,8 @@
mConsumer.hide();
verifyZeroInteractions(mMockTransaction);
int[] hideTypes = new int[1];
- mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()),
+ mConsumer.setControl(
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
new int[1], hideTypes);
assertEquals(statusBars(), hideTypes[0]);
assertFalse(mRemoveSurfaceCalled);
@@ -194,7 +197,8 @@
verifyZeroInteractions(mMockTransaction);
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
- mConsumer.setControl(new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()),
+ mConsumer.setControl(
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
new int[1], hideTypes);
assertTrue(mRemoveSurfaceCalled);
assertEquals(0, hideTypes[0]);
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
index 8eb13bc..4349021 100644
--- a/core/tests/coretests/src/android/view/RoundedCornerTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -62,6 +62,13 @@
}
@Test
+ public void testIsEmpty_negativeCenter() {
+ RoundedCorner roundedCorner =
+ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT, 1, -2, -3);
+ assertThat(roundedCorner.isEmpty(), is(true));
+ }
+
+ @Test
public void testEquals() {
RoundedCorner roundedCorner = new RoundedCorner(
RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index 516fb76..f3a6f9e 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -74,7 +74,7 @@
mTarget = new ScrollCaptureTarget(mView, mLocalVisibleRect, mPositionInWindow, mCallback);
mTarget.setScrollBounds(mScrollBounds);
- mConnection = new ScrollCaptureConnection(Runnable::run, mTarget, mRemote);
+ mConnection = new ScrollCaptureConnection(Runnable::run, mTarget);
}
/** Test creating a client with valid info */
@@ -83,7 +83,7 @@
ScrollCaptureTarget target = new ScrollCaptureTarget(
mView, mLocalVisibleRect, mPositionInWindow, mCallback);
target.setScrollBounds(new Rect(1, 2, 3, 4));
- new ScrollCaptureConnection(Runnable::run, target, mRemote);
+ new ScrollCaptureConnection(Runnable::run, target);
}
/** Test creating a client fails if arguments are not valid. */
@@ -91,20 +91,20 @@
public void testConstruction_requiresScrollBounds() {
try {
mTarget.setScrollBounds(null);
- new ScrollCaptureConnection(Runnable::run, mTarget, mRemote);
+ new ScrollCaptureConnection(Runnable::run, mTarget);
fail("An exception was expected.");
} catch (RuntimeException ex) {
// Ignore, expected.
}
}
- /** @see ScrollCaptureConnection#startCapture(Surface) */
+ /** @see ScrollCaptureConnection#startCapture(Surface, IScrollCaptureCallbacks) */
@Test
public void testStartCapture() throws Exception {
- mConnection.startCapture(mSurface);
+ mConnection.startCapture(mSurface, mRemote);
mCallback.completeStartRequest();
- assertTrue(mConnection.isStarted());
+ assertTrue(mConnection.isActive());
verify(mRemote, times(1)).onCaptureStarted();
verifyNoMoreInteractions(mRemote);
@@ -112,11 +112,11 @@
@Test
public void testStartCapture_cancellation() throws Exception {
- ICancellationSignal signal = mConnection.startCapture(mSurface);
+ ICancellationSignal signal = mConnection.startCapture(mSurface, mRemote);
signal.cancel();
mCallback.completeStartRequest();
- assertFalse(mConnection.isStarted());
+ assertFalse(mConnection.isActive());
verifyNoMoreInteractions(mRemote);
}
@@ -124,7 +124,7 @@
/** @see ScrollCaptureConnection#requestImage(Rect) */
@Test
public void testRequestImage() throws Exception {
- mConnection.startCapture(mSurface);
+ mConnection.startCapture(mSurface, mRemote);
mCallback.completeStartRequest();
reset(mRemote);
@@ -138,7 +138,7 @@
@Test
public void testRequestImage_cancellation() throws Exception {
- mConnection.startCapture(mSurface);
+ mConnection.startCapture(mSurface, mRemote);
mCallback.completeStartRequest();
reset(mRemote);
@@ -152,7 +152,7 @@
/** @see ScrollCaptureConnection#endCapture() */
@Test
public void testEndCapture() throws Exception {
- mConnection.startCapture(mSurface);
+ mConnection.startCapture(mSurface, mRemote);
mCallback.completeStartRequest();
reset(mRemote);
@@ -167,7 +167,7 @@
/** @see ScrollCaptureConnection#endCapture() */
@Test
public void testEndCapture_cancellation() throws Exception {
- mConnection.startCapture(mSurface);
+ mConnection.startCapture(mSurface, mRemote);
mCallback.completeStartRequest();
reset(mRemote);
@@ -179,9 +179,9 @@
}
@Test
- public void testClose() throws Exception {
+ public void testClose() {
mConnection.close();
- assertFalse(mConnection.isConnected());
+ assertFalse(mConnection.isActive());
verifyNoMoreInteractions(mRemote);
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 7746bc2..e0d9ecfc 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -205,7 +205,7 @@
@Test
public void requestScrollCapture_withoutContentRoot() {
final CountDownLatch latch = new CountDownLatch(1);
- mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() {
+ mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureResponseListener.Default() {
@Override
public void onScrollCaptureResponse(ScrollCaptureResponse response) {
latch.countDown();
@@ -237,7 +237,7 @@
final CountDownLatch latch = new CountDownLatch(1);
mViewRootImpl.setScrollCaptureRequestTimeout(100);
- mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() {
+ mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureResponseListener.Default() {
@Override
public void onScrollCaptureResponse(ScrollCaptureResponse response) {
latch.countDown();
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 4319740..623e77e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -29,6 +29,8 @@
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.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -74,10 +76,9 @@
*
* or
*
- * bit FrameworksCoreTests:com.android.internal.os.BatteryStatsCpuTimesTest
+ * atest FrameworksCoreTests:com.android.internal.os.BatteryStatsCpuTimesTest
*/
@SmallTest
-@SkipPresubmit("b/180015146")
@RunWith(AndroidJUnit4.class)
public class BatteryStatsCpuTimesTest {
@Mock
@@ -89,6 +90,8 @@
@Mock
KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader;
@Mock
+ SystemServerCpuThreadReader mSystemServerCpuThreadReader;
+ @Mock
BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
@Mock
PowerProfile mPowerProfile;
@@ -107,6 +110,7 @@
.setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader)
.setKernelCpuUidActiveTimeReader(mCpuUidActiveTimeReader)
.setKernelCpuUidClusterTimeReader(mCpuUidClusterTimeReader)
+ .setSystemServerCpuThreadReader(mSystemServerCpuThreadReader)
.setUserInfoProvider(mUserInfoProvider);
}
@@ -125,8 +129,8 @@
// VERIFY
assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
- verify(mCpuUidUserSysTimeReader).readDelta(null);
- verify(mCpuUidFreqTimeReader).readDelta(null);
+ verify(mCpuUidUserSysTimeReader).readDelta(anyBoolean(), isNull());
+ verify(mCpuUidFreqTimeReader).readDelta(anyBoolean(), isNull());
for (int i = 0; i < numClusters; ++i) {
verify(mKernelCpuSpeedReaders[i]).readDelta();
}
@@ -145,16 +149,16 @@
// VERIFY
verify(mUserInfoProvider).refreshUserIds();
- verify(mCpuUidUserSysTimeReader).readDelta(
+ verify(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidUserSysTimeReader.Callback.class));
// perClusterTimesAvailable is called twice, once in updateCpuTimeLocked() and the other
// in readKernelUidCpuFreqTimesLocked.
verify(mCpuUidFreqTimeReader, times(2)).perClusterTimesAvailable();
- verify(mCpuUidFreqTimeReader).readDelta(
+ verify(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
- verify(mCpuUidActiveTimeReader).readDelta(
+ verify(mCpuUidActiveTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidActiveTimeReader.Callback.class));
- verify(mCpuUidClusterTimeReader).readDelta(
+ verify(mCpuUidClusterTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidClusterTimeReader.Callback.class));
verifyNoMoreInteractions(mCpuUidFreqTimeReader);
for (int i = 0; i < numClusters; ++i) {
@@ -256,16 +260,16 @@
FIRST_APPLICATION_UID + 33
});
final long[][] uidTimesUs = {
- {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+ {12, 34}, {34897394, 3123983}, {79775429834L, 8430434903489L}
};
doAnswer(invocation -> {
final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
- (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mCpuUidUserSysTimeReader).readDelta(
+ }).when(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
@@ -291,16 +295,16 @@
// PRECONDITIONS
final long[][] deltasUs = {
- {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
+ {9379, 3332409833484L}, {493247, 723234}, {3247819, 123348}
};
doAnswer(invocation -> {
final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
- (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasUs[i]);
}
return null;
- }).when(mCpuUidUserSysTimeReader).readDelta(
+ }).when(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
@@ -331,16 +335,16 @@
FIRST_APPLICATION_UID + 33
});
final long[][] uidTimesUs = {
- {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+ {12, 34}, {34897394, 3123983}, {79775429834L, 8430434903489L}
};
doAnswer(invocation -> {
final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
- (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mCpuUidUserSysTimeReader).readDelta(
+ }).when(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
@@ -367,16 +371,16 @@
final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42);
mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid);
final long[][] deltasUs = {
- {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
+ {9379, 3332409833484L}, {493247, 723234}, {3247819, 123348}
};
doAnswer(invocation -> {
final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
- (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasUs[i]);
}
return null;
- }).when(mCpuUidUserSysTimeReader).readDelta(
+ }).when(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
@@ -421,18 +425,18 @@
FIRST_APPLICATION_UID + 33
});
final long[][] uidTimesUs = {
- {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+ {12, 34}, {34897394, 3123983}, {79775429834L, 8430434903489L}
};
doAnswer(invocation -> {
final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
- (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
// And one for the invalid uid
callback.onUidCpuTime(invalidUid, new long[]{3879, 239});
return null;
- }).when(mCpuUidUserSysTimeReader).readDelta(
+ }).when(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
@@ -474,12 +478,12 @@
};
doAnswer(invocation -> {
final KernelCpuUidUserSysTimeReader.Callback<long[]> callback =
- (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0];
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesUs[i]);
}
return null;
- }).when(mCpuUidUserSysTimeReader).readDelta(
+ }).when(mCpuUidUserSysTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidUserSysTimeReader.Callback.class));
// RUN
@@ -553,13 +557,13 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -582,17 +586,17 @@
updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
final long[][] deltasMs = {
{3, 12, 55, 100, 32},
- {3248327490475l, 232349349845043l, 123, 2398, 0},
+ {3248327490475L, 232349349845043L, 123, 2398, 0},
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -636,13 +640,13 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
@@ -676,17 +680,17 @@
updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
final long[][] deltasMs = {
{3, 12, 55, 100, 32},
- {3248327490475l, 232349349845043l, 123, 2398, 0},
+ {3248327490475L, 232349349845043L, 123, 2398, 0},
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -746,13 +750,13 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true);
@@ -836,13 +840,13 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -865,17 +869,17 @@
updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
final long[][] deltasMs = {
{3, 12, 55, 100, 32, 34984, 27983},
- {3248327490475l, 232349349845043l, 123, 2398, 0, 398, 0},
- {43, 3345, 2143, 123, 4554, 9374983794839l, 979875}
+ {3248327490475L, 232349349845043L, 123, 2398, 0, 398, 0},
+ {43, 3345, 2143, 123, 4554, 9374983794839L, 979875}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -913,13 +917,13 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -953,13 +957,13 @@
{43, 3345, 2143, 123, 4554}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -1008,15 +1012,15 @@
{8, 25, 3, 0, 42}
};
doAnswer(invocation -> {
- final KernelCpuUidFreqTimeReader.Callback callback =
- (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidFreqTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
callback.onUidCpuTime(invalidUid, new long[]{12, 839, 32, 34, 21});
return null;
- }).when(mCpuUidFreqTimeReader).readDelta(
+ }).when(mCpuUidFreqTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidFreqTimeReader.Callback.class));
// RUN
@@ -1051,13 +1055,13 @@
});
final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
doAnswer(invocation -> {
- final KernelCpuUidActiveTimeReader.Callback callback =
- (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback<Long> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mCpuUidActiveTimeReader).readDelta(
+ }).when(mCpuUidActiveTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
@@ -1077,13 +1081,13 @@
updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
final long[] deltasMs = {43000, 3345000, 2143000, 123000, 4554000};
doAnswer(invocation -> {
- final KernelCpuUidActiveTimeReader.Callback callback =
- (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback<Long> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mCpuUidActiveTimeReader).readDelta(
+ }).when(mCpuUidActiveTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
@@ -1115,15 +1119,15 @@
});
final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000};
doAnswer(invocation -> {
- final KernelCpuUidActiveTimeReader.Callback callback =
- (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidActiveTimeReader.Callback<Long> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
callback.onUidCpuTime(invalidUid, 1200L);
return null;
- }).when(mCpuUidActiveTimeReader).readDelta(
+ }).when(mCpuUidActiveTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidActiveTimeReader.Callback.class));
// RUN
@@ -1159,13 +1163,13 @@
{8000, 0}
};
doAnswer(invocation -> {
- final KernelCpuUidClusterTimeReader.Callback callback =
- (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
return null;
- }).when(mCpuUidClusterTimeReader).readDelta(
+ }).when(mCpuUidClusterTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
@@ -1189,13 +1193,13 @@
{43000, 3345000}
};
doAnswer(invocation -> {
- final KernelCpuUidClusterTimeReader.Callback callback =
- (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], deltasMs[i]);
}
return null;
- }).when(mCpuUidClusterTimeReader).readDelta(
+ }).when(mCpuUidClusterTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
@@ -1232,15 +1236,15 @@
{8000, 0}
};
doAnswer(invocation -> {
- final KernelCpuUidClusterTimeReader.Callback callback =
- (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0];
+ final KernelCpuUidClusterTimeReader.Callback<long[]> callback =
+ invocation.getArgument(1);
for (int i = 0; i < testUids.length; ++i) {
callback.onUidCpuTime(testUids[i], uidTimesMs[i]);
}
// And one for the invalid uid
callback.onUidCpuTime(invalidUid, new long[]{400, 1000});
return null;
- }).when(mCpuUidClusterTimeReader).readDelta(
+ }).when(mCpuUidClusterTimeReader).readDelta(anyBoolean(),
any(KernelCpuUidClusterTimeReader.Callback.class));
// RUN
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index ee472880..46e2772 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -60,6 +60,7 @@
KernelCpuUidFreqTimeReaderTest.class,
KernelCpuUidUserSysTimeReaderTest.class,
KernelMemoryBandwidthStatsTest.class,
+ KernelSingleProcessCpuThreadReaderTest.class,
KernelSingleUidTimeReaderTest.class,
KernelWakelockReaderTest.class,
LongSamplingCounterTest.class,
@@ -69,6 +70,7 @@
PowerProfileTest.class,
ScreenPowerCalculatorTest.class,
SensorPowerCalculatorTest.class,
+ SystemServerCpuThreadReaderTest.class,
SystemServicePowerCalculatorTest.class,
UserPowerCalculatorTest.class,
VideoPowerCalculatorTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 8aeb761..80ab36e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -33,11 +33,15 @@
import androidx.test.InstrumentationRegistry;
+import com.android.internal.power.MeasuredEnergyStats;
+
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+
public class BatteryUsageStatsRule implements TestRule {
private final PowerProfile mPowerProfile;
private final MockClocks mMockClocks = new MockClocks();
@@ -98,6 +102,16 @@
return this;
}
+ /** Call only after setting the power profile information. */
+ public BatteryUsageStatsRule initMeasuredEnergyStatsLocked(int numCustom) {
+ final boolean[] supportedStandardBuckets =
+ new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
+ Arrays.fill(supportedStandardBuckets, true);
+ mBatteryStats.initMeasuredEnergyStatsLocked(supportedStandardBuckets, numCustom);
+ mBatteryStats.informThatAllExternalStatsAreFlushed();
+ return this;
+ }
+
public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) {
mScreenOn = screenOn;
return this;
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 23ea508..33b8aed 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -35,7 +35,6 @@
import java.util.List;
@SmallTest
-@SkipPresubmit("b/180015146")
@RunWith(AndroidJUnit4.class)
public class BatteryUsageStatsTest {
@@ -102,7 +101,7 @@
}
public void validateBatteryUsageStats(BatteryUsageStats batteryUsageStats) {
- assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(100);
+ assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(21500);
assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20);
assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000);
assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000);
@@ -128,7 +127,7 @@
BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND)).isEqualTo(700);
assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(800);
- assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1710);
+ assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1200);
} else {
fail("Unexpected UID " + uidBatteryConsumer.getUid());
}
@@ -146,7 +145,7 @@
BatteryConsumer.TIME_COMPONENT_CPU)).isEqualTo(10300);
assertThat(systemBatteryConsumer.getUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(10400);
- assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(30510);
+ assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(20300);
} else {
fail("Unexpected drain type " + systemBatteryConsumer.getDrainType());
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index f6aa08b..71d7668 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.os.BatteryConsumer;
+import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.SystemBatteryConsumer;
@@ -43,7 +44,6 @@
.setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0);
@Test
- @SkipPresubmit("b/180015146")
public void testTimerBasedModel() {
setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
.getOrCreateBluetoothControllerActivityLocked(),
@@ -60,11 +60,10 @@
BluetoothPowerCalculator calculator =
new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
- mStatsRule.apply(calculator);
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
- assertBluetoothPowerAndDuration(
- mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.11388, 6000);
+ assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
0.24722, 15000);
@@ -74,7 +73,6 @@
}
@Test
- @SkipPresubmit("b/180015146")
public void testReportedPowerBasedModel() {
setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
.getOrCreateBluetoothControllerActivityLocked(),
@@ -91,11 +89,10 @@
BluetoothPowerCalculator calculator =
new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
- mStatsRule.apply(calculator);
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
- assertBluetoothPowerAndDuration(
- mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.1, 6000);
+ assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
0.2, 15000);
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 7088890..496415a 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -19,18 +19,22 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.os.BatteryConsumer;
+import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.UidBatteryConsumer;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.power.MeasuredEnergyStats;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -81,6 +85,10 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
+ final boolean[] supportedPowerBuckets =
+ new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
+ supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+
mStatsRule.getBatteryStats()
.setUserInfoProvider(mMockUserInfoProvider)
.setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
@@ -88,11 +96,11 @@
.setKernelCpuUidClusterTimeReader(mMockKernelCpuUidClusterTimeReader)
.setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader)
.setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
- .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader);
+ .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
+ .initMeasuredEnergyStatsLocked(supportedPowerBuckets, 0);
}
@Test
- @SkipPresubmit("b/180015146")
public void testTimerBasedModel() {
when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
@@ -103,28 +111,28 @@
// User/System CPU time
doAnswer(invocation -> {
- final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+ final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1);
// User/system time in microseconds
callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000});
callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000});
return null;
- }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(any());
+ }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(anyBoolean(), any());
// Active CPU time
doAnswer(invocation -> {
- final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(0);
+ final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(1);
callback.onUidCpuTime(APP_UID1, 1111L);
callback.onUidCpuTime(APP_UID2, 3333L);
return null;
- }).when(mMockKerneCpuUidActiveTimeReader).readDelta(any());
+ }).when(mMockKerneCpuUidActiveTimeReader).readDelta(anyBoolean(), any());
// Per-cluster CPU time
doAnswer(invocation -> {
- final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+ final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1);
callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222});
callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444});
return null;
- }).when(mMockKernelCpuUidClusterTimeReader).readDelta(any());
+ }).when(mMockKernelCpuUidClusterTimeReader).readDelta(anyBoolean(), any());
mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
@@ -134,7 +142,8 @@
CpuPowerCalculator calculator =
new CpuPowerCalculator(mStatsRule.getPowerProfile());
- mStatsRule.apply(calculator);
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
@@ -150,4 +159,64 @@
.isWithin(PRECISION).of(2.672322);
assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
}
+
+ @Test
+ public void testMeasuredEnergyBasedModel() {
+ when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
+
+ when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000});
+ when(mMockKernelCpuSpeedReaders[1].readDelta()).thenReturn(new long[]{3000, 4000});
+
+ when(mMockCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(false);
+
+ // User/System CPU time
+ doAnswer(invocation -> {
+ final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1);
+ // User/system time in microseconds
+ callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000});
+ callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000});
+ return null;
+ }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(anyBoolean(), any());
+
+ // Active CPU time
+ doAnswer(invocation -> {
+ final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(1);
+ callback.onUidCpuTime(APP_UID1, 1111L);
+ callback.onUidCpuTime(APP_UID2, 3333L);
+ return null;
+ }).when(mMockKerneCpuUidActiveTimeReader).readDelta(anyBoolean(), any());
+
+ // Per-cluster CPU time
+ doAnswer(invocation -> {
+ final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(1);
+ callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222});
+ callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444});
+ return null;
+ }).when(mMockKernelCpuUidClusterTimeReader).readDelta(anyBoolean(), any());
+
+ final long[] clusterChargesUC = new long[]{13577531, 24688642};
+ mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, clusterChargesUC);
+
+ mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234);
+ mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345);
+
+ CpuPowerCalculator calculator =
+ new CpuPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+ .isEqualTo(3333);
+ assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(3.18877);
+ assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
+
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+ .isEqualTo(7777);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(7.44072);
+ assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
index 0c91b29..f011117 100644
--- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
@@ -42,9 +42,12 @@
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
@Test
- @SkipPresubmit("b/180015146")
public void testMeasuredEnergyCopiedIntoBatteryConsumers() {
final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ // For side-effect of creating a BatteryStats.Uid
+ batteryStats.getUidStatsLocked(APP_UID);
+
SparseLongArray uidEnergies = new SparseLongArray();
uidEnergies.put(APP_UID, 30_000_000);
batteryStats.updateCustomMeasuredEnergyStatsLocked(0, 100_000_000, uidEnergies);
@@ -60,18 +63,18 @@
UidBatteryConsumer uid = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uid.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
- .isWithin(PRECISION).of(2.252252);
+ .isWithin(PRECISION).of(8.333333);
assertThat(uid.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
- .isWithin(PRECISION).of(9.009009);
+ .isWithin(PRECISION).of(33.33333);
SystemBatteryConsumer systemConsumer = mStatsRule.getSystemBatteryConsumer(
SystemBatteryConsumer.DRAIN_TYPE_CUSTOM);
assertThat(systemConsumer.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
- .isWithin(PRECISION).of(7.5075075);
+ .isWithin(PRECISION).of(27.77777);
assertThat(systemConsumer.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
- .isWithin(PRECISION).of(15.015015);
+ .isWithin(PRECISION).of(55.55555);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index a47c4d8..26adbe9 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -46,6 +46,7 @@
initTimersAndCounters();
setExternalStatsSyncLocked(new DummyExternalStatsSync());
+ informThatAllExternalStatsAreFlushed();
final boolean[] supportedStandardBuckets =
new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index 6edbbb0..58e2513 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -78,7 +78,6 @@
}
@Test
- @SkipPresubmit("b/180015146")
public void testPowerProfileBasedModel() {
when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
@@ -154,7 +153,7 @@
}
@Override
- public void readDelta(@Nullable Callback<long[]> cb) {
+ public void readDelta(boolean forcedRead, @Nullable Callback<long[]> cb) {
if (cb != null) {
cb.onUidCpuTime(Process.SYSTEM_UID, mSystemServerCpuTimes);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
index e100545..2e23dc8 100644
--- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
@@ -17,11 +17,14 @@
package com.android.internal.os;
+import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
+
import static com.google.common.truth.Truth.assertThat;
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;
@@ -50,10 +53,11 @@
.setAveragePower(PowerProfile.POWER_WIFI_ON, 360.0)
.setAveragePower(PowerProfile.POWER_WIFI_SCAN, 480.0)
.setAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, 720.0)
- .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0);
+ .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0)
+ .initMeasuredEnergyStatsLocked(0);
- @Test
- public void testPowerControllerBasedModel() {
+ /** Sets up a batterystats object with pre-populated network values. */
+ private BatteryStatsImpl setupTestNetworkNumbers() {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.noteNetworkInterfaceForTransports("wifi",
@@ -64,13 +68,25 @@
.insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111);
mStatsRule.setNetworkStats(networkStats);
- WifiActivityEnergyInfo energyInfo = new WifiActivityEnergyInfo(10000,
- WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000);
+ return batteryStats;
+ }
- batteryStats.updateWifiState(energyInfo, 1000, 1000);
+ /** Sets up an WifiActivityEnergyInfo for ActivityController-model-based tests. */
+ private WifiActivityEnergyInfo setupPowerControllerBasedModelEnergyNumbersInfo() {
+ return new WifiActivityEnergyInfo(10000,
+ WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000);
+ }
+
+ @Test
+ public void testPowerControllerBasedModel_nonMeasured() {
+ final BatteryStatsImpl batteryStats = setupTestNetworkNumbers();
+ final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo();
+
+ batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 1000, 1000);
WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
- mStatsRule.apply(calculator);
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI))
@@ -87,30 +103,54 @@
}
@Test
- public void testTimerBasedModel() {
- BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ public void testPowerControllerBasedModel_measured() {
+ final BatteryStatsImpl batteryStats = setupTestNetworkNumbers();
+ final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo();
- batteryStats.noteNetworkInterfaceForTransports("wifi",
- new int[]{NetworkCapabilities.TRANSPORT_WIFI});
+ batteryStats.updateWifiState(energyInfo, 1_000_000, 1000, 1000);
- NetworkStats networkStats = new NetworkStats(10000, 1)
- .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100)
- .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111);
- mStatsRule.setNetworkStats(networkStats);
+ WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
+ mStatsRule.apply(calculator);
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI))
+ .isEqualTo(1423);
+ /* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.2214666 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
+
+ SystemBatteryConsumer systemConsumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
+ assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI))
+ .isEqualTo(5577);
+ /* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */
+ assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.645200 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
+ }
+
+ /** Sets up batterystats object with prepopulated network & timer data for Timer-model tests. */
+ private BatteryStatsImpl setupTimerBasedModelTestNumbers() {
+ final BatteryStatsImpl batteryStats = setupTestNetworkNumbers();
batteryStats.noteWifiScanStartedLocked(APP_UID, 1000, 1000);
batteryStats.noteWifiScanStoppedLocked(APP_UID, 2000, 2000);
batteryStats.noteWifiRunningLocked(new WorkSource(APP_UID), 3000, 3000);
batteryStats.noteWifiStoppedLocked(new WorkSource(APP_UID), 4000, 4000);
batteryStats.noteWifiRunningLocked(new WorkSource(Process.WIFI_UID), 1111, 2222);
batteryStats.noteWifiStoppedLocked(new WorkSource(Process.WIFI_UID), 3333, 4444);
+ return batteryStats;
+ }
+
+ @Test
+ public void testTimerBasedModel_nonMeasured() {
+ final BatteryStatsImpl batteryStats = setupTimerBasedModelTestNumbers();
// Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively
// on the packet counts.
- batteryStats.updateWifiState(/* energyInfo */ null, 1000, 1000);
+ batteryStats.updateWifiState(/* energyInfo */ null, POWER_DATA_UNAVAILABLE, 1000, 1000);
WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
- mStatsRule.apply(calculator);
+ mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+ calculator);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI))
@@ -125,4 +165,31 @@
assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.8759216);
}
+
+ @Test
+ public void testTimerBasedModel_measured() {
+ final BatteryStatsImpl batteryStats = setupTimerBasedModelTestNumbers();
+
+ // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively
+ // on the packet counts.
+ batteryStats.updateWifiState(/* energyInfo */ null, 1_000_000, 1000, 1000);
+
+ WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
+ mStatsRule.apply(calculator);
+
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI))
+ .isEqualTo(1000);
+ /* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
+
+ SystemBatteryConsumer systemConsumer =
+ mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
+ assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI))
+ .isEqualTo(2222);
+ /* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */
+ assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.8759216 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
+ }
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
similarity index 98%
rename from core/tests/mockingcoretests/src/android/view/DisplayTests.java
rename to core/tests/mockingcoretests/src/android/view/DisplayTest.java
index a036db2..f8dd153 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTests.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -56,12 +56,15 @@
*
* <p>Build/Install/Run:
*
- * atest FrameworksMockingCoreTests:android.view.DisplayTests
+ * atest FrameworksMockingCoreTests:android.view.DisplayTest
+ *
+ * <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 DisplayTests {
+public class DisplayTest {
private static final int APP_WIDTH = 272;
private static final int APP_HEIGHT = 700;
diff --git a/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java b/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java
deleted file mode 100644
index 7175f56..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/LocationPermissionCheckerTest.java
+++ /dev/null
@@ -1,296 +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.internal.util;
-
-import static android.Manifest.permission.NETWORK_SETTINGS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.location.LocationManager;
-import android.os.Binder;
-import android.os.Build;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.HashMap;
-
-/** Unit tests for {@link LocationPermissionChecker}. */
-public class LocationPermissionCheckerTest {
-
- public static final String TAG = "ConnectivityUtilTest";
-
- // Mock objects for testing
- @Mock private Context mMockContext;
- @Mock private PackageManager mMockPkgMgr;
- @Mock private ApplicationInfo mMockApplInfo;
- @Mock private AppOpsManager mMockAppOps;
- @Mock private UserManager mMockUserManager;
- @Mock private LocationManager mLocationManager;
-
- private static final String TEST_PKG_NAME = "com.google.somePackage";
- private static final String TEST_FEATURE_ID = "com.google.someFeature";
- private static final int MANAGED_PROFILE_UID = 1100000;
- private static final int OTHER_USER_UID = 1200000;
-
- private final String mInteractAcrossUsersFullPermission =
- "android.permission.INTERACT_ACROSS_USERS_FULL";
- private final String mManifestStringCoarse =
- Manifest.permission.ACCESS_COARSE_LOCATION;
- private final String mManifestStringFine =
- Manifest.permission.ACCESS_FINE_LOCATION;
-
- // Test variables
- private int mWifiScanAllowApps;
- private int mUid;
- private int mCoarseLocationPermission;
- private int mAllowCoarseLocationApps;
- private int mFineLocationPermission;
- private int mAllowFineLocationApps;
- private int mNetworkSettingsPermission;
- private int mCurrentUser;
- private boolean mIsLocationEnabled;
- private boolean mThrowSecurityException;
- private Answer<Integer> mReturnPermission;
- private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
- private LocationPermissionChecker mChecker;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- initTestVars();
- }
-
- private void setupMocks() throws Exception {
- when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any()))
- .thenReturn(mMockApplInfo);
- when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
- when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME,
- TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps);
- when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid),
- eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
- .thenReturn(mAllowCoarseLocationApps);
- when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid),
- eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
- .thenReturn(mAllowFineLocationApps);
- if (mThrowSecurityException) {
- doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong"
- + " to application bound to user " + mUid))
- .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME);
- }
- when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
- .thenReturn(mMockAppOps);
- when(mMockContext.getSystemService(Context.USER_SERVICE))
- .thenReturn(mMockUserManager);
- when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
- }
-
- private void setupTestCase() throws Exception {
- setupMocks();
- setupMockInterface();
- mChecker = new LocationPermissionChecker(mMockContext);
- }
-
- private void initTestVars() {
- mPermissionsList.clear();
- mReturnPermission = createPermissionAnswer();
- mWifiScanAllowApps = AppOpsManager.MODE_ERRORED;
- mUid = OTHER_USER_UID;
- mThrowSecurityException = true;
- mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
- mIsLocationEnabled = false;
- mCurrentUser = ActivityManager.getCurrentUser();
- mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
- mFineLocationPermission = PackageManager.PERMISSION_DENIED;
- mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
- mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
- mNetworkSettingsPermission = PackageManager.PERMISSION_DENIED;
- }
-
- private void setupMockInterface() {
- Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid());
- doAnswer(mReturnPermission).when(mMockContext).checkPermission(
- anyString(), anyInt(), anyInt());
- when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM,
- UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID)))
- .thenReturn(true);
- when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid))
- .thenReturn(mCoarseLocationPermission);
- when(mMockContext.checkPermission(mManifestStringFine, -1, mUid))
- .thenReturn(mFineLocationPermission);
- when(mMockContext.checkPermission(NETWORK_SETTINGS, -1, mUid))
- .thenReturn(mNetworkSettingsPermission);
- when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled);
- }
-
- private Answer<Integer> createPermissionAnswer() {
- return new Answer<Integer>() {
- @Override
- public Integer answer(InvocationOnMock invocation) {
- int myUid = (int) invocation.getArguments()[1];
- String myPermission = (String) invocation.getArguments()[0];
- mPermissionsList.get(myPermission);
- if (mPermissionsList.containsKey(myPermission)) {
- int uid = mPermissionsList.get(myPermission);
- if (myUid == uid) {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- return PackageManager.PERMISSION_DENIED;
- }
- };
- }
-
- @Test
- public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception {
- mIsLocationEnabled = true;
- mThrowSecurityException = false;
- mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
- mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
- mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
- mUid = mCurrentUser;
- setupTestCase();
-
- final int result =
- mChecker.checkLocationPermissionWithDetailInfo(
- TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
- assertEquals(LocationPermissionChecker.SUCCEEDED, result);
- }
-
- @Test
- public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception {
- mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
- mIsLocationEnabled = true;
- mThrowSecurityException = false;
- mUid = mCurrentUser;
- mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
- mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
- mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
- setupTestCase();
-
- final int result =
- mChecker.checkLocationPermissionWithDetailInfo(
- TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
- assertEquals(LocationPermissionChecker.SUCCEEDED, result);
- }
-
- @Test
- public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception {
- mThrowSecurityException = true;
- mIsLocationEnabled = true;
- mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
- mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
- mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
- setupTestCase();
-
- assertThrows(SecurityException.class,
- () -> mChecker.checkLocationPermissionWithDetailInfo(
- TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
- }
-
- @Test
- public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception {
- mThrowSecurityException = false;
- mIsLocationEnabled = true;
- setupTestCase();
-
- final int result =
- mChecker.checkLocationPermissionWithDetailInfo(
- TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
- assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
- }
-
- @Test
- public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception {
- mThrowSecurityException = false;
- mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
- mIsLocationEnabled = true;
- mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
- mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
- mUid = MANAGED_PROFILE_UID;
- setupTestCase();
-
- final int result =
- mChecker.checkLocationPermissionWithDetailInfo(
- TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
- assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
- verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
- }
-
- @Test
- public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception {
- mThrowSecurityException = false;
- mUid = MANAGED_PROFILE_UID;
- mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
- mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
- mIsLocationEnabled = false;
-
- setupTestCase();
-
- final int result =
- mChecker.checkLocationPermissionWithDetailInfo(
- TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
- assertEquals(LocationPermissionChecker.ERROR_LOCATION_MODE_OFF, result);
- }
-
- @Test
- public void testenforceCanAccessScanResults_LocationModeDisabledHasNetworkSettings()
- throws Exception {
- mThrowSecurityException = false;
- mIsLocationEnabled = false;
- mNetworkSettingsPermission = PackageManager.PERMISSION_GRANTED;
- setupTestCase();
-
- final int result =
- mChecker.checkLocationPermissionWithDetailInfo(
- TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
- assertEquals(LocationPermissionChecker.SUCCEEDED, result);
- }
-
-
- private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
- try {
- r.run();
- Assert.fail("Expected " + exceptionClass + " to be thrown.");
- } catch (Exception exception) {
- assertTrue(exceptionClass.isInstance(exception));
- }
- }
-}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 89644e2..8991a61 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-
-
// Privapp permission whitelist files
package {
@@ -26,6 +24,14 @@
}
prebuilt_etc {
+ name: "allowed_privapp_com.android.carsystemui",
+ system_ext_specific: true,
+ sub_dir: "permissions",
+ src: "com.android.carsystemui.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
name: "allowed_privapp_android.car.cluster.loggingrenderer",
sub_dir: "permissions",
src: "android.car.cluster.loggingrenderer.xml",
@@ -187,3 +193,17 @@
src: "com.android.car.activityresolver.xml",
filename_from_src: true,
}
+
+prebuilt_etc {
+ name: "allowed_privapp_com.android.car.cluster.home",
+ sub_dir: "permissions",
+ src: "com.android.car.cluster.home.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
+ name: "allowed_privapp_com.android.car.messenger",
+ sub_dir: "permissions",
+ src: "com.android.car.messenger.xml",
+ filename_from_src: true,
+}
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/styles.xml b/data/etc/car/com.android.car.cluster.home.xml
similarity index 65%
rename from packages/SystemUI/res-keyguard/values-sw600dp/styles.xml
rename to data/etc/car/com.android.car.cluster.home.xml
index e632e76..4c2d614 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp/styles.xml
+++ b/data/etc/car/com.android.car.cluster.home.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 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.
@@ -13,9 +14,8 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<resources>
- <style name="BouncerSecurityContainer">
- <item name="android:layout_gravity">center</item>
- </style>
-</resources>
\ No newline at end of file
+<permissions>
+ <privapp-permissions package="com.android.car.cluster.home">
+ <permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
+ </privapp-permissions>
+</permissions>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/styles.xml b/data/etc/car/com.android.car.messenger.xml
similarity index 60%
copy from packages/SystemUI/res-keyguard/values-sw600dp/styles.xml
copy to data/etc/car/com.android.car.messenger.xml
index e632e76..9e5f339 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp/styles.xml
+++ b/data/etc/car/com.android.car.messenger.xml
@@ -1,5 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 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.
@@ -13,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<resources>
- <style name="BouncerSecurityContainer">
- <item name="android:layout_gravity">center</item>
- </style>
-</resources>
\ No newline at end of file
+<permissions>
+ <privapp-permissions package="com.android.car.messenger">
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/car/com.android.car.shell.xml b/data/etc/car/com.android.car.shell.xml
index 6132d53..58306be 100644
--- a/data/etc/car/com.android.car.shell.xml
+++ b/data/etc/car/com.android.car.shell.xml
@@ -20,5 +20,11 @@
<privapp-permissions package="com.android.shell">
<permission name="android.permission.INSTALL_PACKAGES" />
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <permission name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"/>
+ <permission name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
+ <permission name="android.car.permission.CAR_DIAGNOSTICS"/>
+ <permission name="android.car.permission.CAR_DRIVING_STATE"/>
+ <permission name="android.car.permission.CAR_POWER"/>
+ <permission name="android.car.permission.CONTROL_CAR_CLIMATE"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/car/com.android.carsystemui.xml b/data/etc/car/com.android.carsystemui.xml
new file mode 100644
index 0000000..4e2f294
--- /dev/null
+++ b/data/etc/car/com.android.carsystemui.xml
@@ -0,0 +1,25 @@
+<?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
+ -->
+<permissions>
+ <privapp-permissions package="com.android.systemui">
+ <permission name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
+ <permission name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
+ <permission name="android.car.permission.CAR_ENROLL_TRUST"/>
+ <permission name="android.car.permission.CAR_POWER"/>
+ <permission name="android.car.permission.CONTROL_CAR_CLIMATE"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 77a38a9..27bf4ef 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -60,10 +60,6 @@
<group gid="log" />
</permission>
- <permission name="android.permission.MANAGE_EXTERNAL_STORAGE" >
- <group gid="external_storage" />
- </permission>
-
<permission name="android.permission.ACCESS_MTP" >
<group gid="mtp" />
</permission>
@@ -154,6 +150,8 @@
<assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" />
<assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" />
+ <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="media" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="media" />
<assign-permission name="android.permission.INTERNET" uid="media" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a7b6636..9b124ce 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -490,6 +490,9 @@
<permission name="android.permission.SET_CLIP_SOURCE" />
<!-- Permission required for CTS test - FontManagerTest -->
<permission name="android.permission.UPDATE_FONTS" />
+ <!-- Permission required for hotword detection service CTS tests -->
+ <permission name="android.permission.MANAGE_HOTWORD_DETECTION" />
+ <permission name="android.permission.MANAGE_APP_HIBERNATION"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 4a3bd99..c70d6b0 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -823,12 +823,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/Task.java"
},
- "-1159577965": {
- "message": "Focus requested for input consumer=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_FOCUS_LIGHT",
- "at": "com\/android\/server\/wm\/InputMonitor.java"
- },
"-1156118957": {
"message": "Updated config=%s",
"level": "DEBUG",
@@ -1237,6 +1231,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "-672355406": {
+ "message": " Rejecting as no-op: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-672228342": {
"message": "resumeTopActivityLocked: Top activity resumed %s",
"level": "DEBUG",
@@ -2479,12 +2479,6 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
- "690411811": {
- "message": "goodToGo(): No apps to animate",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
- },
"691515534": {
"message": " Commit wallpaper becoming invisible: %s",
"level": "VERBOSE",
@@ -2605,12 +2599,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "883475718": {
- "message": "Report configuration: %s %s %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/ActivityClientController.java"
- },
"892244061": {
"message": "Waiting for drawn %s: removed=%b visible=%b mHasSurface=%b drawState=%d",
"level": "INFO",
@@ -2917,6 +2905,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "1305412562": {
+ "message": "Report configuration: %s %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONFIGURATION",
+ "at": "com\/android\/server\/wm\/ActivityClientController.java"
+ },
"1316533291": {
"message": "State movement: %s from:%s to:%s reason:%s",
"level": "VERBOSE",
@@ -3319,6 +3313,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/TransitionController.java"
},
+ "1804245629": {
+ "message": "Attempted to add starting window to token but already cleaned",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"1810019902": {
"message": "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
"level": "DEBUG",
@@ -3445,6 +3445,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
+ "1931178855": {
+ "message": "\tnonApp=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+ "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+ },
"1947239194": {
"message": "Deferring rotation, still finishing previous rotation",
"level": "VERBOSE",
@@ -3493,6 +3499,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1999594750": {
+ "message": "startAnimation",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+ "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
+ },
"2018454757": {
"message": "WS.removeImmediately: %s Already removed...",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
index f3bf63b..b57f7af 100644
--- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -38,8 +38,7 @@
*/
public final class RippleAnimationSession {
private static final String TAG = "RippleAnimationSession";
- private static final int ENTER_ANIM_DURATION = 300;
- private static final int SLIDE_ANIM_DURATION = 450;
+ private static final int ENTER_ANIM_DURATION = 450;
private static final int EXIT_ANIM_DURATION = 300;
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private static final TimeInterpolator PATH_INTERPOLATOR =
@@ -50,26 +49,21 @@
private Runnable mOnUpdate;
private long mStartTime;
private boolean mForceSoftware;
- private final float mWidth, mHeight;
private final ValueAnimator mSparkle = ValueAnimator.ofFloat(0, 1);
- private final ArraySet<Animator> mActiveAnimations = new ArraySet<>(3);
RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties,
- boolean forceSoftware, float width, float height) {
+ boolean forceSoftware) {
mProperties = properties;
mForceSoftware = forceSoftware;
- mWidth = width;
- mHeight = height;
mSparkle.addUpdateListener(anim -> {
final long now = AnimationUtils.currentAnimationTimeMillis();
final long elapsed = now - mStartTime - ENTER_ANIM_DURATION;
- final float phase = (float) elapsed / 1000f;
- mProperties.getShader().setSecondsOffset(phase);
+ final float phase = (float) elapsed / 30000f;
+ mProperties.getShader().setNoisePhase(phase);
notifyUpdate();
});
mSparkle.setDuration(ENTER_ANIM_DURATION);
- mSparkle.setStartDelay(ENTER_ANIM_DURATION);
mSparkle.setInterpolator(LINEAR_INTERPOLATOR);
mSparkle.setRepeatCount(ValueAnimator.INFINITE);
}
@@ -85,7 +79,6 @@
}
@NonNull RippleAnimationSession exit(Canvas canvas) {
- mSparkle.end();
if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas);
else exitSoftware();
return this;
@@ -93,7 +86,6 @@
private void onAnimationEnd(Animator anim) {
notifyUpdate();
- mActiveAnimations.remove(anim);
}
@NonNull RippleAnimationSession setOnSessionEnd(
@@ -123,18 +115,18 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
+ mSparkle.end();
Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
}
});
expand.setInterpolator(LINEAR_INTERPOLATOR);
expand.start();
- mActiveAnimations.add(expand);
}
private long computeDelay() {
final long timePassed = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
- return Math.max((long) SLIDE_ANIM_DURATION - timePassed, 0);
+ return Math.max((long) ENTER_ANIM_DURATION - timePassed, 0);
}
private void notifyUpdate() {
@@ -157,6 +149,7 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
+ mSparkle.end();
Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
}
@@ -167,7 +160,6 @@
long delay = computeDelay();
exit.setStartDelay(delay);
exit.start();
- mActiveAnimations.add(exit);
}
private void enterHardware(RecordingCanvas canvas) {
@@ -175,54 +167,27 @@
props = getCanvasProperties();
RenderNodeAnimator expand =
new RenderNodeAnimator(props.getProgress(), .5f);
- RenderNodeAnimator slideX =
- new RenderNodeAnimator(props.getX(), mWidth / 2);
- RenderNodeAnimator slideY =
- new RenderNodeAnimator(props.getY(), mHeight / 2);
expand.setTarget(canvas);
- slideX.setTarget(canvas);
- slideY.setTarget(canvas);
- startAnimation(expand, slideX, slideY);
+ startAnimation(expand);
}
- private void startAnimation(Animator expand,
- Animator slideX, Animator slideY) {
- expand.setDuration(SLIDE_ANIM_DURATION);
- slideX.setDuration(SLIDE_ANIM_DURATION);
- slideY.setDuration(SLIDE_ANIM_DURATION);
- slideX.addListener(new AnimatorListener(this));
+ private void startAnimation(Animator expand) {
+ expand.setDuration(ENTER_ANIM_DURATION);
+ expand.addListener(new AnimatorListener(this));
expand.setInterpolator(LINEAR_INTERPOLATOR);
- slideX.setInterpolator(PATH_INTERPOLATOR);
- slideY.setInterpolator(PATH_INTERPOLATOR);
expand.start();
- slideX.start();
- slideY.start();
if (!mSparkle.isRunning()) {
mSparkle.start();
- mActiveAnimations.add(mSparkle);
}
- mActiveAnimations.add(expand);
- mActiveAnimations.add(slideX);
- mActiveAnimations.add(slideY);
}
private void enterSoftware() {
ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f);
- ValueAnimator slideX = ValueAnimator.ofFloat(
- mProperties.getX(), mWidth / 2);
- ValueAnimator slideY = ValueAnimator.ofFloat(
- mProperties.getY(), mHeight / 2);
expand.addUpdateListener(updatedAnimation -> {
notifyUpdate();
mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
});
- slideX.addUpdateListener(anim -> {
- float x = (float) slideX.getAnimatedValue();
- float y = (float) slideY.getAnimatedValue();
- mProperties.setOrigin(x, y);
- mProperties.getShader().setOrigin(x, y);
- });
- startAnimation(expand, slideX, slideY);
+ startAnimation(expand);
}
@NonNull AnimationProperties<Float, Paint> getProperties() {
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index d6bbee9..fb2b9b3 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -842,7 +842,7 @@
if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
RippleAnimationSession.AnimationProperties<Float, Paint> properties =
createAnimationProperties(x, y, w, h);
- mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps, w, h)
+ mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps)
.setOnAnimationUpdated(() -> invalidateSelf(false))
.setOnSessionEnd(session -> {
mRunningAnimations.remove(session);
@@ -912,14 +912,14 @@
? mState.mColor.getColorForState(getState(), Color.BLACK)
: mMaskColorFilter.getColor();
shader.setColor(color);
- shader.setOrigin(x, y);
+ shader.setOrigin(w / 2, y / 2);
+ shader.setTouch(x, y);
shader.setResolution(w, h);
- shader.setSecondsOffset(0);
+ shader.setNoisePhase(0);
shader.setRadius(radius);
shader.setProgress(.0f);
properties = new RippleAnimationSession.AnimationProperties<>(
- x, y, radius, p, 0f,
- shader);
+ w / 2, h / 2, radius, p, 0f, shader);
if (mMaskShader == null) {
shader.setShader(null);
} else {
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 657a32c..ea9ba32 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -23,11 +23,12 @@
final class RippleShader extends RuntimeShader {
private static final String SHADER_UNIFORMS = "uniform vec2 in_origin;\n"
+ + "uniform vec2 in_touch;\n"
+ "uniform float in_progress;\n"
+ "uniform float in_maxRadius;\n"
+ "uniform vec2 in_resolution;\n"
+ "uniform float in_hasMask;\n"
- + "uniform float in_secondsOffset;\n"
+ + "uniform float in_noisePhase;\n"
+ "uniform vec4 in_color;\n"
+ "uniform shader in_shader;\n";
private static final String SHADER_LIB =
@@ -48,7 +49,7 @@
+ " float s = 0.0;\n"
+ " for (float i = 0; i < 4; i += 1) {\n"
+ " float l = i * 0.25;\n"
- + " float h = l + 0.025;\n"
+ + " float h = l + 0.005;\n"
+ " float o = abs(sin(0.1 * PI * (t + i)));\n"
+ " s += threshold(n + o, l, h);\n"
+ " }\n"
@@ -79,12 +80,12 @@
+ " float fadeIn = subProgress(0., 0.175, in_progress);\n"
+ " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n"
+ " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n"
- + " float ring = getRingMask(p, in_origin, in_maxRadius, fadeIn);\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 sparkle = sparkles(p, in_progress * 0.25 + in_secondsOffset)\n"
- + " * ring * alpha;\n"
- + " float fade = min(fadeIn, 1.-fadeOutRipple);\n"
- + " vec4 circle = in_color * (softCircle(p, in_origin, in_maxRadius "
+ + " float sparkle = sparkles(p, in_noisePhase) * ring * alpha;\n"
+ + " float fade = min(fadeIn, 1. - fadeOutRipple);\n"
+ + " vec4 circle = in_color * (softCircle(p, center, in_maxRadius "
+ " * fadeIn, 0.2) * fade);\n"
+ " float mask = in_hasMask == 1. ? sample(in_shader).a > 0. ? 1. : 0. : 1.;\n"
+ " return mix(circle, vec4(sparkle), sparkle) * mask;\n"
@@ -109,14 +110,18 @@
/**
* Continuous offset used as noise phase.
*/
- public void setSecondsOffset(float t) {
- setUniform("in_secondsOffset", t);
+ public void setNoisePhase(float t) {
+ setUniform("in_noisePhase", t);
}
public void setOrigin(float x, float y) {
setUniform("in_origin", new float[] {x, y});
}
+ public void setTouch(float x, float y) {
+ setUniform("in_touch", new float[] {x, y});
+ }
+
public void setProgress(float progress) {
setUniform("in_progress", progress);
}
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 5501569..35b1c16 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -121,4 +121,22 @@
return SYSTEM_ERROR;
}
}
+
+ /**
+ * Queries user state from Keystore 2.0.
+ *
+ * @param userId - Android user id of the user.
+ * @return UserState enum variant as integer if successful or an error
+ */
+ public static int getState(int userId) {
+ try {
+ return getService().getState(userId);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "getState failed", e);
+ return e.errorCode;
+ } catch (Exception e) {
+ Log.e(TAG, "Can not connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 11cb2b7..7c80f70 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -601,7 +601,7 @@
}
/**
- * Check whether the caller is the credential management app {@link CredentialManagementApp}.
+ * Check whether the caller is the credential management app {@code CredentialManagementApp}.
* The credential management app has the ability to manage the user's KeyChain credentials
* on unmanaged devices.
*
@@ -611,6 +611,7 @@
*
* @return {@code true} if the caller is the credential management app.
*/
+ @WorkerThread
public static boolean isCredentialManagementApp(@NonNull Context context) {
boolean isCredentialManagementApp = false;
try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
@@ -634,6 +635,7 @@
* @return the credential management app's authentication policy.
* @throws SecurityException if the caller is not the credential management app.
*/
+ @WorkerThread
@NonNull
public static AppUriAuthenticationPolicy getCredentialManagementAppPolicy(
@NonNull Context context) throws SecurityException {
@@ -665,6 +667,7 @@
* @hide
*/
@TestApi
+ @WorkerThread
@RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
public static boolean setCredentialManagementApp(@NonNull Context context,
@NonNull String packageName, @NonNull AppUriAuthenticationPolicy authenticationPolicy) {
@@ -680,13 +683,21 @@
}
/**
- * Remove the user's KeyChain credentials on unmanaged devices.
+ * Called by the credential management app {@code CredentialManagementApp} to unregister as
+ * the credential management app and stop managing the user's credentials.
+ *
+ * <p> All credentials previously installed by the credential management app will be removed
+ * from the user's device.
+ *
+ * <p> An app holding {@code MANAGE_CREDENTIAL_MANAGEMENT_APP} permission can also call this
+ * method to remove the current credential management app, even if it's not the current
+ * credential management app itself.
*
* @return {@code true} if the credential management app was successfully removed.
- * @hide
*/
- @TestApi
- @RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
+ @WorkerThread
+ @RequiresPermission(value = Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP,
+ conditional = true)
public static boolean removeCredentialManagementApp(@NonNull Context context) {
try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
keyChainConnection.getService().removeCredentialManagementApp();
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 93658e6..a08f390 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -43,6 +43,7 @@
import android.security.keystore.KeyProperties;
import android.security.keystore.KeystoreResponse;
import android.security.keystore.UserNotAuthenticatedException;
+import android.security.maintenance.UserState;
import android.system.keystore2.Domain;
import android.util.Log;
@@ -196,6 +197,19 @@
public State state(int userId) {
final int ret;
try {
+ if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) {
+ int userState = AndroidKeyStoreMaintenance.getState(userId);
+ switch (userState) {
+ case UserState.UNINITIALIZED:
+ return KeyStore.State.UNINITIALIZED;
+ case UserState.LSKF_UNLOCKED:
+ return KeyStore.State.UNLOCKED;
+ case UserState.LSKF_LOCKED:
+ return KeyStore.State.LOCKED;
+ default:
+ throw new AssertionError(userState);
+ }
+ }
ret = mBinder.getState(userId);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
index 2ee952c..d9d5300 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -123,8 +123,9 @@
throws InvalidKeyException {
resetAll();
- if (!(key instanceof AndroidKeyStorePrivateKey
- || key instanceof AndroidKeyStoreSecretKey)) {
+ // Public key operations get diverted to the default provider.
+ if (opmode == Cipher.ENCRYPT_MODE
+ && (key instanceof PrivateKey || key instanceof PublicKey)) {
try {
mCipher = Cipher.getInstance(getTransform());
String transform = getTransform();
@@ -184,8 +185,9 @@
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
- if (!(key instanceof AndroidKeyStorePrivateKey
- || key instanceof AndroidKeyStoreSecretKey)) {
+ // Public key operations get diverted to the default provider.
+ if (opmode == Cipher.ENCRYPT_MODE
+ && (key instanceof PrivateKey || key instanceof PublicKey)) {
try {
mCipher = Cipher.getInstance(getTransform());
mCipher.init(opmode, key, params, random);
@@ -213,8 +215,9 @@
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
- if (!(key instanceof AndroidKeyStorePrivateKey
- || key instanceof AndroidKeyStoreSecretKey)) {
+ // Public key operations get diverted to the default provider.
+ if (opmode == Cipher.ENCRYPT_MODE
+ && (key instanceof PrivateKey || key instanceof PublicKey)) {
try {
mCipher = Cipher.getInstance(getTransform());
mCipher.init(opmode, key, params, random);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index fa852e3..ba6d22f 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -145,23 +145,15 @@
sInstalled = true;
Security.addProvider(new AndroidKeyStoreProvider());
- Security.addProvider(
- new android.security.keystore.AndroidKeyStoreProvider(
- "AndroidKeyStoreLegacy"));
Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider();
- Provider legacyWorkaroundProvider =
- new android.security.keystore.AndroidKeyStoreBCWorkaroundProvider(
- "AndroidKeyStoreBCWorkaroundLegacy");
if (bcProviderIndex != -1) {
// Bouncy Castle provider found -- install the workaround provider above it.
// insertProviderAt uses 1-based positions.
- Security.insertProviderAt(legacyWorkaroundProvider, bcProviderIndex + 1);
Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1);
} else {
// Bouncy Castle provider not found -- install the workaround provider at lowest
// priority.
Security.addProvider(workaroundProvider);
- Security.addProvider(legacyWorkaroundProvider);
}
}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1b5dc8b..3f03302 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -38,6 +38,14 @@
path: "src",
}
+filegroup {
+ name: "wm_shell-aidls",
+ srcs: [
+ "src/**/*.aidl",
+ ],
+ path: "src",
+}
+
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
filegroup {
name: "wm_shell-sources-kt",
@@ -98,7 +106,7 @@
":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
- "src/**/I*.aidl",
+ ":wm_shell-aidls",
],
resource_dirs: [
"res",
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 cb54021..3708e15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -119,7 +119,7 @@
listeners.get(i).onDisplayAreaAppeared(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
@Override
@@ -161,24 +161,27 @@
listeners.get(i).onDisplayAreaInfoChanged(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
/**
- * Applies the {@link Configuration} to the {@link DisplayAreaContext} specified by
- * {@code displayId}.
- *
- * @param displayId The ID of the {@link Display} which the {@link DisplayAreaContext} is
- * associated with
- * @param newConfig The propagated configuration
+ * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
+ * {@link DisplayAreaInfo#displayId}.
*/
- private void applyConfigChangesToContext(int displayId, @NonNull Configuration newConfig) {
+ private void applyConfigChangesToContext(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ final Display display = mContext.getSystemService(DisplayManager.class)
+ .getDisplay(displayId);
+ if (display == null) {
+ throw new UnsupportedOperationException("The display #" + displayId + " is invalid."
+ + "displayAreaInfo:" + displayAreaInfo);
+ }
DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
if (daContext == null) {
- daContext = new DisplayAreaContext(mContext, displayId);
+ daContext = new DisplayAreaContext(mContext, display);
mDisplayAreaContexts.put(displayId, daContext);
}
- daContext.updateConfigurationChanges(newConfig);
+ daContext.updateConfigurationChanges(displayAreaInfo.configuration);
}
/**
@@ -228,10 +231,8 @@
private final IBinder mToken = new Binder();
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
- public DisplayAreaContext(@NonNull Context context, int displayId) {
+ public DisplayAreaContext(@NonNull Context context, @NonNull Display display) {
super(null);
- final Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(displayId);
attachBaseContext(context.createTokenContext(mToken, display));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index eaed24d..d451f4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -47,21 +47,7 @@
private final ShellExecutor mMainExecutor;
private final HandlerImpl mImpl = new HandlerImpl();
- public static ShellCommandHandler create(
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<Pip> pipOptional,
- Optional<OneHandedController> oneHandedOptional,
- Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<AppPairsController> appPairsOptional,
- ShellExecutor mainExecutor) {
- return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
- splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
- appPairsOptional, mainExecutor).mImpl;
- }
-
- private ShellCommandHandlerImpl(
+ public ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
@@ -80,6 +66,10 @@
mMainExecutor = mainExecutor;
}
+ public ShellCommandHandler asShellCommandHandler() {
+ return mImpl;
+ }
+
/** Dumps WM Shell internal state. */
private void dump(PrintWriter pw) {
mShellTaskOrganizer.dump(pw, "");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 85bd24c..6f4550c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -26,7 +26,7 @@
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingSurface;
+import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -47,44 +47,20 @@
private final FullscreenTaskListener mFullscreenTaskListener;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
- private final Optional<StartingSurface> mStartingSurfaceOptional;
+ private final StartingWindowController mStartingWindow;
private final InitImpl mImpl = new InitImpl();
- public static ShellInit create(DisplayImeController displayImeController,
+ public ShellInitImpl(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
- Optional<StartingSurface> startingSurfaceOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
- ShellExecutor mainExecutor) {
- return new ShellInitImpl(displayImeController,
- dragAndDropController,
- shellTaskOrganizer,
- legacySplitScreenOptional,
- splitScreenOptional,
- appPairsOptional,
- startingSurfaceOptional,
- pipTouchHandlerOptional,
- fullscreenTaskListener,
- transitions,
- mainExecutor).mImpl;
- }
-
- private ShellInitImpl(DisplayImeController displayImeController,
- DragAndDropController dragAndDropController,
- ShellTaskOrganizer shellTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairsController> appPairsOptional,
- Optional<StartingSurface> startingSurfaceOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional,
- FullscreenTaskListener fullscreenTaskListener,
- Transitions transitions,
+ StartingWindowController startingWindow,
ShellExecutor mainExecutor) {
mDisplayImeController = displayImeController;
mDragAndDropController = dragAndDropController;
@@ -96,17 +72,21 @@
mPipTouchHandlerOptional = pipTouchHandlerOptional;
mTransitions = transitions;
mMainExecutor = mainExecutor;
- mStartingSurfaceOptional = startingSurfaceOptional;
+ mStartingWindow = startingWindow;
+ }
+
+ public ShellInit asShellInit() {
+ return mImpl;
}
private void init() {
// Start listening for display changes
mDisplayImeController.startMonitorDisplays();
+ // Setup the shell organizer
mShellTaskOrganizer.addListenerForType(
mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mStartingSurfaceOptional.ifPresent(mShellTaskOrganizer::initStartingSurface);
- // Register the shell organizer
+ mShellTaskOrganizer.initStartingWindow(mStartingWindow);
mShellTaskOrganizer.registerOrganizer();
mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index fcb53cd..94d13ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -48,7 +48,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
-import com.android.wm.shell.startingsurface.StartingSurface;
+import com.android.wm.shell.startingsurface.StartingWindowController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -133,7 +133,7 @@
private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>();
private final Object mLock = new Object();
- private StartingSurface mStartingSurface;
+ private StartingWindowController mStartingWindow;
/**
* In charge of showing size compat UI. Can be {@code null} if device doesn't support size
@@ -184,8 +184,8 @@
/**
* @hide
*/
- public void initStartingSurface(StartingSurface startingSurface) {
- mStartingSurface = startingSurface;
+ public void initStartingWindow(StartingWindowController startingWindow) {
+ mStartingWindow = startingWindow;
}
/**
@@ -302,23 +302,23 @@
@Override
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
- if (mStartingSurface != null) {
- mStartingSurface.addStartingWindow(info, appToken);
+ if (mStartingWindow != null) {
+ mStartingWindow.addStartingWindow(info, appToken);
}
}
@Override
public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
boolean playRevealAnimation) {
- if (mStartingSurface != null) {
- mStartingSurface.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+ if (mStartingWindow != null) {
+ mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
}
}
@Override
public void copySplashScreenView(int taskId) {
- if (mStartingSurface != null) {
- mStartingSurface.copySplashScreenView(taskId);
+ if (mStartingWindow != null) {
+ mStartingWindow.copySplashScreenView(taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index 562b32b..b6d408a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -23,6 +23,7 @@
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
@@ -88,7 +89,8 @@
ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s",
task1.taskId, task2.taskId, this);
- if (!task1.isResizeable || !task2.isResizeable) {
+ if ((!task1.isResizeable || !task2.isResizeable)
+ && !ActivityTaskManager.supportsNonResizableMultiWindow()) {
ProtoLog.e(WM_SHELL_TASK_ORG,
"Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
task1.isResizeable, task2.isResizeable);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 16ede73..e5c9e25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -151,6 +151,7 @@
* starting a new animation.
*/
private final ShellExecutor mDelayedAnimationExecutor;
+ private Runnable mDelayedAnimation;
/**
* Interface to synchronize {@link View} state and the screen.
@@ -1865,7 +1866,7 @@
mExpandedBubble.getExpandedView().setAlphaAnimating(true);
}
- mDelayedAnimationExecutor.executeDelayed(() -> {
+ mDelayedAnimation = () -> {
mExpandedViewAlphaAnimator.start();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
@@ -1898,7 +1899,8 @@
}
})
.start();
- }, startDelay);
+ };
+ mDelayedAnimationExecutor.executeDelayed(mDelayedAnimation, startDelay);
}
private void animateCollapse() {
@@ -2097,7 +2099,7 @@
* animating flags for those animations.
*/
private void cancelDelayedExpandCollapseSwitchAnimations() {
- mDelayedAnimationExecutor.removeAllCallbacks();
+ mDelayedAnimationExecutor.removeCallbacks(mDelayedAnimation);
mIsExpansionAnimating = false;
mIsBubbleSwitchAnimating = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java
new file mode 100644
index 0000000..b29058b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExecutorUtils.java
@@ -0,0 +1,64 @@
+/*
+ * 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.wm.shell.common;
+
+import android.Manifest;
+import android.util.Slog;
+
+import java.util.function.Consumer;
+
+/**
+ * Helpers for working with executors
+ */
+public class ExecutorUtils {
+
+ /**
+ * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given
+ * callback.
+ */
+ public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance,
+ String log, Consumer<T> callback) {
+ executeRemoteCallWithTaskPermission(controllerInstance, log, callback,
+ false /* blocking */);
+ }
+
+ /**
+ * Checks that the caller has the MANAGE_ACTIVITY_TASKS permission and executes the given
+ * callback.
+ */
+ public static <T> void executeRemoteCallWithTaskPermission(RemoteCallable<T> controllerInstance,
+ String log, Consumer<T> callback, boolean blocking) {
+ if (controllerInstance == null) return;
+
+ final RemoteCallable<T> controller = controllerInstance;
+ controllerInstance.getContext().enforceCallingPermission(
+ Manifest.permission.MANAGE_ACTIVITY_TASKS, log);
+ if (blocking) {
+ try {
+ controllerInstance.getRemoteCallExecutor().executeBlocking(() -> {
+ callback.accept((T) controller);
+ });
+ } catch (InterruptedException e) {
+ Slog.e("ExecutorUtils", "Remote call failed", e);
+ }
+ } else {
+ controllerInstance.getRemoteCallExecutor().execute(() -> {
+ callback.accept((T) controller);
+ });
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index a4cd3c5..bfee820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.os.Handler;
-import android.os.Looper;
/** Executor implementation which is backed by a Handler. */
public class HandlerExecutor implements ShellExecutor {
@@ -47,11 +46,6 @@
}
@Override
- public void removeAllCallbacks() {
- mHandler.removeCallbacksAndMessages(null);
- }
-
- @Override
public void removeCallbacks(@NonNull Runnable r) {
mHandler.removeCallbacks(r);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java
similarity index 61%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java
index 54242be..30f535b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/RemoteCallable.java
@@ -14,12 +14,21 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.wm.shell.common;
+
+import android.content.Context;
/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ * An interface for controllers that can receive remote calls.
*/
-oneway interface ISplitScreenListener {
- void onStagePositionChanged(int stage, int position);
- void onTaskStageChanged(int taskId, int stage, boolean visible);
-}
+public interface RemoteCallable<T> {
+ /**
+ * Returns a context used for permission checking.
+ */
+ Context getContext();
+
+ /**
+ * Returns the executor to post the handler callback to.
+ */
+ ShellExecutor getRemoteCallExecutor();
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index 6abc8f6..f729164 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -16,16 +16,10 @@
package com.android.wm.shell.common;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.Trace;
-
import java.lang.reflect.Array;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
-import java.util.function.BooleanSupplier;
-import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@@ -94,11 +88,6 @@
void executeDelayed(Runnable runnable, long delayMillis);
/**
- * Removes all pending callbacks.
- */
- void removeAllCallbacks();
-
- /**
* See {@link android.os.Handler#removeCallbacks}.
*/
void removeCallbacks(Runnable runnable);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 4bb8e9b..9dabec7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -32,7 +32,7 @@
import android.util.SparseArray;
import android.view.Display;
import android.view.DragEvent;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindow;
import android.view.IWindowManager;
import android.view.IWindowSession;
@@ -369,9 +369,9 @@
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {}
@Override
- public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
+ public void requestScrollCapture(IScrollCaptureResponseListener listener) {
try {
- callbacks.onScrollCaptureResponse(
+ listener.onScrollCaptureResponse(
new ScrollCaptureResponse.Builder()
.setDescription("Not Implemented")
.build());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 1770943..58bf22a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -85,7 +85,8 @@
@Override
public void onDisplayAdded(int displayId) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display added: %d", displayId);
- final Context context = mDisplayController.getDisplayContext(displayId);
+ final Context context = mDisplayController.getDisplayContext(displayId)
+ .createWindowContext(TYPE_APPLICATION_OVERLAY, null);
final WindowManager wm = context.getSystemService(WindowManager.class);
// TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index aab2334..9a09ca4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -235,7 +235,7 @@
mStarter.startShortcut(packageName, id, stage, position, opts, user);
} else {
mStarter.startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT),
- mContext, null, stage, position, opts);
+ null, stage, position, opts);
}
}
@@ -295,7 +295,7 @@
@Nullable Bundle options);
void startShortcut(String packageName, String shortcutId, @StageType int stage,
@StagePosition int position, @Nullable Bundle options, UserHandle user);
- void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
+ void startIntent(PendingIntent intent, Intent fillInIntent,
@StageType int stage, @StagePosition int position,
@Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
@@ -337,9 +337,8 @@
}
@Override
- public void startIntent(PendingIntent intent, Context context,
- @Nullable Intent fillInIntent, int stage, int position,
- @Nullable Bundle options) {
+ public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, int stage,
+ int position, @Nullable Bundle options) {
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
index eea5c08..d06064a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java
@@ -30,6 +30,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.os.IBinder;
@@ -91,9 +92,11 @@
// is nothing behind it.
((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK)
&& triggerTask.parentTaskId == mListener.mPrimary.taskId)
- // if a non-resizable is launched, we also need to leave split-screen.
+ // if a non-resizable is launched when it is not supported in multi window,
+ // we also need to leave split-screen.
|| ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
- && !triggerTask.isResizeable);
+ && !triggerTask.isResizeable
+ && !ActivityTaskManager.supportsNonResizableMultiWindow());
// In both cases, dismiss the primary
if (shouldDismiss) {
WindowManagerProxy.buildDismissSplit(out, mListener,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 82468ad..5a2ef56 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -46,6 +46,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.BooleanSupplier;
/**
* Proxy to simplify calls into window manager/activity manager
@@ -208,11 +209,17 @@
return false;
}
ActivityManager.RunningTaskInfo topHomeTask = null;
+ // One-time lazy wrapper to avoid duplicated IPC in loop. Not store as class variable
+ // because the value can be changed at runtime.
+ final BooleanSupplier supportsNonResizableMultiWindow =
+ createSupportsNonResizableMultiWindowSupplier();
for (int i = rootTasks.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
- // Only move resizeable task to split secondary. However, we have an exception
- // for non-resizable home because we will minimize to show it.
- if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME) {
+ // Check whether to move resizeable task to split secondary.
+ // Also, we have an exception for non-resizable home because we will minimize to show
+ // it.
+ if (!rootTask.isResizeable && rootTask.topActivityType != ACTIVITY_TYPE_HOME
+ && !supportsNonResizableMultiWindow.getAsBoolean()) {
continue;
}
// Only move fullscreen tasks to split secondary.
@@ -357,6 +364,21 @@
outWct.setFocusable(tiles.mPrimary.token, true /* focusable */);
}
+ /** Creates a lazy wrapper to get whether it supports non-resizable in multi window. */
+ private static BooleanSupplier createSupportsNonResizableMultiWindowSupplier() {
+ return new BooleanSupplier() {
+ private Boolean mSupportsNonResizableMultiWindow;
+ @Override
+ public boolean getAsBoolean() {
+ if (mSupportsNonResizableMultiWindow == null) {
+ mSupportsNonResizableMultiWindow =
+ ActivityTaskManager.supportsNonResizableMultiWindow();
+ }
+ return mSupportsNonResizableMultiWindow;
+ }
+ };
+ }
+
/**
* Utility to apply a sync transaction serially with other sync transactions.
*
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
similarity index 66%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
index 54242be..008b508 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
@@ -14,12 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.wm.shell.onehanded;
/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ * Interface that is exposed to remote callers to manipulate the OneHanded feature.
*/
-oneway interface ISplitScreenListener {
- void onStagePositionChanged(int stage, int position);
- void onTaskStageChanged(int taskId, int stage, boolean visible);
+interface IOneHanded {
+
+ /**
+ * Enters one handed mode.
+ */
+ oneway void startOneHanded() = 1;
+
+ /**
+ * Exits one handed mode.
+ */
+ oneway void stopOneHanded() = 2;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 4f31c37..a7e9a01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -26,6 +26,14 @@
*/
@ExternalThread
public interface OneHanded {
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate OneHanded.
+ */
+ default IOneHanded createExternalInterface() {
+ return null;
+ }
+
/**
* Return one handed settings enabled or not.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 5fc7c98..25968eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -18,6 +18,10 @@
import static android.os.UserHandle.USER_CURRENT;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
+import android.Manifest;
+import android.annotation.BinderThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.om.IOverlayManager;
@@ -43,6 +47,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExecutorUtils;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -54,7 +60,7 @@
/**
* Manages and manipulates the one handed states, transitions, and gesture for phones.
*/
-public class OneHandedController {
+public class OneHandedController implements RemoteCallable<OneHandedController> {
private static final String TAG = "OneHandedController";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -73,6 +79,7 @@
private final Context mContext;
private final DisplayController mDisplayController;
private final OneHandedGestureHandler mGestureHandler;
+ private final OneHandedSettingsUtil mOneHandedSettingsUtil;
private final OneHandedTimeoutHandler mTimeoutHandler;
private final OneHandedTouchHandler mTouchHandler;
private final OneHandedTutorialHandler mTutorialHandler;
@@ -108,8 +115,12 @@
new AccessibilityManager.AccessibilityStateChangeListener() {
@Override
public void onAccessibilityStateChanged(boolean enabled) {
+ if (mOneHandedSettingsUtil == null) {
+ Slog.w(TAG, "mOneHandedSettingsUtil may not instantiate yet");
+ return;
+ }
if (enabled) {
- final int mOneHandedTimeout = OneHandedSettingsUtil
+ final int mOneHandedTimeout = mOneHandedSettingsUtil
.getSettingsOneHandedModeTimeout(mContext.getContentResolver());
final int timeout = mAccessibilityManager
.getRecommendedTimeoutMillis(mOneHandedTimeout * 1000
@@ -117,7 +128,7 @@
AccessibilityManager.FLAG_CONTENT_CONTROLS);
mTimeoutHandler.setTimeout(timeout / 1000);
} else {
- mTimeoutHandler.setTimeout(OneHandedSettingsUtil
+ mTimeoutHandler.setTimeout(mOneHandedSettingsUtil
.getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
}
}
@@ -164,15 +175,16 @@
new OneHandedBackgroundPanelOrganizer(context, windowManager, displayController,
mainExecutor);
OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
- context, windowManager, displayController, animationController, tutorialHandler,
+ context, windowManager, animationController, tutorialHandler,
oneHandedBackgroundPanelOrganizer, mainExecutor);
+ OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
return new OneHandedController(context, windowManager, displayController,
oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
- gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
- taskStackListener, mainExecutor, mainHandler);
+ gestureHandler, settingsUtil, timeoutHandler, oneHandedUiEventsLogger,
+ overlayManager, taskStackListener, mainExecutor, mainHandler);
}
@VisibleForTesting
@@ -184,6 +196,7 @@
OneHandedTouchHandler touchHandler,
OneHandedTutorialHandler tutorialHandler,
OneHandedGestureHandler gestureHandler,
+ OneHandedSettingsUtil settingsUtil,
OneHandedTimeoutHandler timeoutHandler,
OneHandedUiEventLogger uiEventsLogger,
IOverlayManager overlayManager,
@@ -191,6 +204,7 @@
ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
+ mOneHandedSettingsUtil = settingsUtil;
mWindowManager = windowManager;
mBackgroundPanelOrganizer = backgroundPanelOrganizer;
mDisplayAreaOrganizer = displayAreaOrganizer;
@@ -209,10 +223,10 @@
final int sysPropPercentageConfig = SystemProperties.getInt(
ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
mOffSetFraction = sysPropPercentageConfig / 100.0f;
- mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ mIsOneHandedEnabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
context.getContentResolver());
mIsSwipeToNotificationEnabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
context.getContentResolver());
mTimeoutHandler = timeoutHandler;
@@ -239,6 +253,16 @@
return mImpl;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
/**
* Set one handed enabled or disabled when user update settings
*/
@@ -325,25 +349,25 @@
}
private void setupSettingObservers() {
- OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
mContext.getContentResolver(), mEnabledObserver);
- OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
mContext.getContentResolver(), mTimeoutObserver);
- OneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
mContext.getContentResolver(), mTaskChangeExitObserver);
- OneHandedSettingsUtil.registerSettingsKeyObserver(
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(
Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
mContext.getContentResolver(), mSwipeToNotificationEnabledObserver);
}
private void updateSettings() {
- setOneHandedEnabled(OneHandedSettingsUtil
+ setOneHandedEnabled(mOneHandedSettingsUtil
.getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
- mTimeoutHandler.setTimeout(OneHandedSettingsUtil
+ mTimeoutHandler.setTimeout(mOneHandedSettingsUtil
.getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
- setTaskChangeToExit(OneHandedSettingsUtil
+ setTaskChangeToExit(mOneHandedSettingsUtil
.getSettingsTapsAppToExit(mContext.getContentResolver()));
- setSwipeToNotificationEnabled(OneHandedSettingsUtil
+ setSwipeToNotificationEnabled(mOneHandedSettingsUtil
.getSettingsSwipeToNotificationEnabled(mContext.getContentResolver()));
}
@@ -358,7 +382,7 @@
@VisibleForTesting
void onEnabledSettingChanged() {
- final boolean enabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ final boolean enabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
mContext.getContentResolver());
mOneHandedUiEventLogger.writeEvent(enabled
? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_ENABLED_ON
@@ -368,13 +392,13 @@
// Also checks swipe to notification settings since they all need gesture overlay.
setEnabledGesturalOverlay(
- enabled || OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
mContext.getContentResolver()));
}
@VisibleForTesting
void onTimeoutSettingChanged() {
- final int newTimeout = OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
+ final int newTimeout = mOneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
mContext.getContentResolver());
int metricsId = OneHandedUiEventLogger.OneHandedSettingsTogglesEvent.INVALID.getId();
switch (newTimeout) {
@@ -403,7 +427,7 @@
@VisibleForTesting
void onTaskChangeExitSettingChanged() {
- final boolean enabled = OneHandedSettingsUtil.getSettingsTapsAppToExit(
+ final boolean enabled = mOneHandedSettingsUtil.getSettingsTapsAppToExit(
mContext.getContentResolver());
mOneHandedUiEventLogger.writeEvent(enabled
? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON
@@ -415,13 +439,13 @@
@VisibleForTesting
void onSwipeToNotificationEnabledSettingChanged() {
final boolean enabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
mContext.getContentResolver());
setSwipeToNotificationEnabled(enabled);
// Also checks one handed mode settings since they all need gesture overlay.
setEnabledGesturalOverlay(
- enabled || OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ enabled || mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
mContext.getContentResolver()));
}
@@ -480,7 +504,8 @@
}
private void setupGesturalOverlay() {
- if (!OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver())) {
+ if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ mContext.getContentResolver())) {
return;
}
@@ -551,7 +576,7 @@
mTutorialHandler.dump(pw);
}
- OneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver());
+ mOneHandedSettingsUtil.dump(pw, innerPrefix, mContext.getContentResolver());
if (mOverlayManager != null) {
OverlayInfo info = null;
@@ -567,8 +592,22 @@
}
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
@ExternalThread
private class OneHandedImpl implements OneHanded {
+ private IOneHandedImpl mIOneHanded;
+
+ @Override
+ public IOneHanded createExternalInterface() {
+ if (mIOneHanded != null) {
+ mIOneHanded.invalidate();
+ }
+ mIOneHanded = new IOneHandedImpl(OneHandedController.this);
+ return mIOneHanded;
+ }
+
@Override
public boolean isOneHandedEnabled() {
// This is volatile so return directly
@@ -637,4 +676,39 @@
});
}
}
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IOneHandedImpl extends IOneHanded.Stub {
+ private OneHandedController mController;
+
+ IOneHandedImpl(OneHandedController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void startOneHanded() {
+ executeRemoteCallWithTaskPermission(mController, "startOneHanded",
+ (controller) -> {
+ controller.startOneHanded();
+ });
+ }
+
+ @Override
+ public void stopOneHanded() {
+ executeRemoteCallWithTaskPermission(mController, "stopOneHanded",
+ (controller) -> {
+ controller.stopOneHanded();
+ });
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 0238fa8..5eec231 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -37,7 +37,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
@@ -67,7 +66,6 @@
private int mEnterExitAnimationDurationMs;
private ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap();
- private DisplayController mDisplayController;
private OneHandedAnimationController mAnimationController;
private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -111,7 +109,6 @@
*/
public OneHandedDisplayAreaOrganizer(Context context,
WindowManager windowManager,
- DisplayController displayController,
OneHandedAnimationController animationController,
OneHandedTutorialHandler tutorialHandler,
OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer,
@@ -119,7 +116,6 @@
super(mainExecutor);
mWindowManager = windowManager;
mAnimationController = animationController;
- mDisplayController = displayController;
mLastVisualDisplayBounds.set(getDisplayBounds());
final int animationDurationConfig = context.getResources().getInteger(
R.integer.config_one_handed_translate_animation_duration);
@@ -171,10 +167,14 @@
*/
public void onRotateDisplay(int fromRotation, int toRotation, WindowContainerTransaction wct) {
// Stop one handed without animation and reset cropped size immediately
- final Rect newBounds = new Rect(mDefaultDisplayBounds);
+ final Rect newBounds = new Rect(getDisplayBounds());
+ // This diff rule will only filter the cases portrait <-> landscape
final boolean isOrientationDiff = Math.abs(fromRotation - toRotation) % 2 == 1;
if (isOrientationDiff) {
+ // getDisplayBounds() will return window metrics bounds which dose not update to
+ // corresponding display orientation yet, we have to manual rotate bounds
+ newBounds.set(0, 0, newBounds.bottom, newBounds.right);
resetWindowsOffset(wct);
mDefaultDisplayBounds.set(newBounds);
mLastVisualDisplayBounds.set(newBounds);
@@ -293,7 +293,8 @@
}
@Nullable
- private Rect getDisplayBounds() {
+ @VisibleForTesting
+ Rect getDisplayBounds() {
if (mWindowManager == null) {
Slog.e(TAG, "WindowManager instance is null! Can not get display size!");
return new Rect();
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 f8217c6..4768cf5 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
@@ -67,7 +67,7 @@
* @param observer Observer from caller
* @return uri key for observing
*/
- public static Uri registerSettingsKeyObserver(String key, ContentResolver resolver,
+ public Uri registerSettingsKeyObserver(String key, ContentResolver resolver,
ContentObserver observer) {
Uri uriKey = null;
uriKey = Settings.Secure.getUriFor(key);
@@ -83,7 +83,7 @@
* @param resolver ContentResolver of context
* @param observer preference key change observer
*/
- public static void unregisterSettingsKeyObserver(ContentResolver resolver,
+ public void unregisterSettingsKeyObserver(ContentResolver resolver,
ContentObserver observer) {
if (resolver != null) {
resolver.unregisterContentObserver(observer);
@@ -95,7 +95,7 @@
*
* @return enable or disable one handed mode flag.
*/
- public static boolean getSettingsOneHandedModeEnabled(ContentResolver resolver) {
+ public boolean getSettingsOneHandedModeEnabled(ContentResolver resolver) {
return Settings.Secure.getInt(resolver,
Settings.Secure.ONE_HANDED_MODE_ENABLED, 0 /* Disabled */) == 1;
}
@@ -105,7 +105,7 @@
*
* @return enable or disable taps app exit.
*/
- public static boolean getSettingsTapsAppToExit(ContentResolver resolver) {
+ public boolean getSettingsTapsAppToExit(ContentResolver resolver) {
return Settings.Secure.getInt(resolver,
Settings.Secure.TAPS_APP_TO_EXIT, 0) == 1;
}
@@ -116,7 +116,7 @@
*
* @return timeout value in seconds.
*/
- public static @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver) {
+ public @OneHandedTimeout int getSettingsOneHandedModeTimeout(ContentResolver resolver) {
return Settings.Secure.getInt(resolver,
Settings.Secure.ONE_HANDED_MODE_TIMEOUT, ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
}
@@ -124,12 +124,12 @@
/**
* Returns whether swipe bottom to notification gesture enabled or not.
*/
- public static boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
+ public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver) {
return Settings.Secure.getInt(resolver,
Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1) == 1;
}
- protected static void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
+ void dump(PrintWriter pw, String prefix, ContentResolver resolver) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
pw.print(innerPrefix + "isOneHandedModeEnable=");
@@ -139,6 +139,6 @@
pw.print(innerPrefix + "tapsAppToExit=");
pw.println(getSettingsTapsAppToExit(resolver));
}
-
- private OneHandedSettingsUtil() {}
+ public OneHandedSettingsUtil() {
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
new file mode 100644
index 0000000..a6ffa6e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip;
+
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+
+import com.android.wm.shell.pip.IPipAnimationListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the Pip feature.
+ */
+interface IPip {
+
+ /**
+ * Notifies that Activity is about to be swiped to home with entering PiP transition and
+ * queries the destination bounds for PiP depends on Launcher's rotation and shelf height.
+ *
+ * @param componentName ComponentName represents the Activity
+ * @param activityInfo ActivityInfo tied to the Activity
+ * @param pictureInPictureParams PictureInPictureParams tied to the Activity
+ * @param launcherRotation Launcher rotation to calculate the PiP destination bounds
+ * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
+ * @return destination bounds the PiP window should land into
+ */
+ Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
+ in PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, int shelfHeight) = 1;
+
+ /**
+ * Notifies the swiping Activity to PiP onto home transition is finished
+ *
+ * @param componentName ComponentName represents the Activity
+ * @param destinationBounds the destination bounds the PiP window lands into
+ */
+ oneway void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 2;
+
+ /**
+ * Sets listener to get pinned stack animation callbacks.
+ */
+ oneway void setPinnedStackAnimationListener(IPipAnimationListener listener) = 3;
+
+ /**
+ * Sets the shelf height and visibility.
+ */
+ oneway void setShelfHeight(boolean visible, int shelfHeight) = 4;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
similarity index 68%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl
rename to libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
index 97aa512..2569b78 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IPinnedStackAnimationListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.wm.shell.pip;
/**
- * Listener interface that Launcher attaches to SystemUI to get
- * pinned stack animation callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get Pip animation callbacks.
*/
-oneway interface IPinnedStackAnimationListener {
+oneway interface IPipAnimationListener {
/**
- * Notifies the pinned stack animation is started.
+ * Notifies the listener that the Pip animation is started.
*/
- void onPinnedStackAnimationStarted();
+ void onPipAnimationStarted();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index d14c3e3c..6d4773b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,15 +16,10 @@
package com.android.wm.shell.pip;
-import android.annotation.Nullable;
-import android.app.PictureInPictureParams;
-import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -34,6 +29,14 @@
*/
@ExternalThread
public interface Pip {
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate PIP.
+ */
+ default IPip createExternalInterface() {
+ return null;
+ }
+
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
@@ -109,30 +112,6 @@
default void showPictureInPictureMenu() {}
/**
- * Called by Launcher when swiping an auto-pip enabled Activity to home starts
- * @param componentName {@link ComponentName} represents the Activity entering PiP
- * @param activityInfo {@link ActivityInfo} tied to the Activity
- * @param pictureInPictureParams {@link PictureInPictureParams} tied to the Activity
- * @param launcherRotation Rotation Launcher is in
- * @param shelfHeight Shelf height when landing PiP window onto Launcher
- * @return Destination bounds of PiP window based on the parameters passed in
- */
- default Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams,
- int launcherRotation, int shelfHeight) {
- return null;
- }
-
- /**
- * Called by Launcher when swiping an auto-pip enable Activity to home finishes
- * @param componentName {@link ComponentName} represents the Activity entering PiP
- * @param destinationBounds Destination bounds of PiP window
- */
- default void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- return;
- }
-
- /**
* Called by NavigationBar in order to listen in for PiP bounds change. This is mostly used
* for times where the PiP bounds could conflict with SystemUI elements, such as a stashed
* PiP and the Back-from-Edge gesture.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index cb39b4e..e3594d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -35,6 +35,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
+import java.util.function.Consumer;
/**
* Singleton source of truth for the current state of PIP bounds.
@@ -84,6 +85,7 @@
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
+ private @Nullable Consumer<Rect> mOnPipExclusionBoundsChangeCallback;
public PipBoundsState(@NonNull Context context) {
mContext = context;
@@ -102,6 +104,9 @@
/** Set the current PIP bounds. */
public void setBounds(@NonNull Rect bounds) {
mBounds.set(bounds);
+ if (mOnPipExclusionBoundsChangeCallback != null) {
+ mOnPipExclusionBoundsChangeCallback.accept(bounds);
+ }
}
/** Get the current PIP bounds. */
@@ -386,6 +391,18 @@
mOnShelfVisibilityChangeCallback = onShelfVisibilityChangeCallback;
}
+ /**
+ * Set a callback to watch out for PiP bounds. This is mostly used by SystemUI's
+ * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
+ */
+ public void setPipExclusionBoundsChangeCallback(
+ @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) {
+ mOnPipExclusionBoundsChangeCallback = onPipExclusionBoundsChangeCallback;
+ if (mOnPipExclusionBoundsChangeCallback != null) {
+ mOnPipExclusionBoundsChangeCallback.accept(getBounds());
+ }
+ }
+
/** Source of truth for the current bounds of PIP that may be in motion. */
public static class MotionBoundsState {
/** The bounds used when PIP is in motion (e.g. during a drag or animation) */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 9ec7c0d..fa31a0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -41,6 +41,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PictureInPictureParams;
@@ -423,12 +424,16 @@
if (mInSwipePipToHomeTransition) {
final Rect destinationBounds = mPipBoundsState.getBounds();
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ mSurfaceTransactionHelper.resetScale(tx, mLeash, destinationBounds);
+ mSurfaceTransactionHelper.crop(tx, mLeash, destinationBounds);
// animation is finished in the Launcher and here we directly apply the final touch.
applyEnterPipSyncTransaction(destinationBounds, () -> {
// ensure menu's settled in its final bounds first
finishResizeForMenu(destinationBounds);
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
- });
+ }, tx);
mInSwipePipToHomeTransition = false;
return;
}
@@ -490,16 +495,20 @@
// mState is set right after the animation is kicked off to block any resize
// requests such as offsetPip that may have been called prior to the transition.
mState = State.ENTERING_PIP;
- });
+ }, null /* boundsChangeTransaction */);
}
- private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable) {
+ private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable,
+ @Nullable SurfaceControl.Transaction boundsChangeTransaction) {
// PiP menu is attached late in the process here to avoid any artifacts on the leash
// caused by addShellRoot when in gesture navigation mode.
mPipMenuController.attach(mLeash);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.setBounds(mToken, destinationBounds);
+ if (boundsChangeTransaction != null) {
+ wct.setBoundsChangeTransaction(mToken, boundsChangeTransaction);
+ }
wct.scheduleFinishEnterPip(mToken, destinationBounds);
mSyncTransactionQueue.queue(wct);
if (runnable != null) {
@@ -557,6 +566,8 @@
mInSwipePipToHomeTransition = false;
mPictureInPictureParams = null;
mState = State.UNDEFINED;
+ // Re-set the PIP bounds to none.
+ mPipBoundsState.setBounds(new Rect());
mPipUiEventLoggerLogger.setTaskInfo(null);
mPipMenuController.detach();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 9a584c6..debdceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -21,8 +21,10 @@
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PictureInPictureParams;
@@ -33,6 +35,7 @@
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -44,6 +47,7 @@
import android.view.WindowManagerGlobal;
import android.window.WindowContainerTransaction;
+import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -51,9 +55,13 @@
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExecutorUtils;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.pip.IPip;
+import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -71,7 +79,8 @@
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
-public class PipController implements PipTransitionController.PipTransitionCallback {
+public class PipController implements PipTransitionController.PipTransitionCallback,
+ RemoteCallable<PipController> {
private static final String TAG = "PipController";
private Context mContext;
@@ -85,12 +94,12 @@
private PipBoundsState mPipBoundsState;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
- protected final PipImpl mImpl = new PipImpl();
+ protected final PipImpl mImpl;
private final Rect mTmpInsetBounds = new Rect();
private boolean mIsInFixedRotation;
- private Consumer<Boolean> mPinnedStackAnimationRecentsCallback;
+ private IPipAnimationListener mPinnedStackAnimationRecentsCallback;
protected PhonePipMenuController mMenuController;
protected PipTaskOrganizer mPipTaskOrganizer;
@@ -264,6 +273,7 @@
}
mContext = context;
+ mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
@@ -366,6 +376,16 @@
});
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
private void onConfigurationChanged(Configuration newConfig) {
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
@@ -474,7 +494,7 @@
mPipTaskOrganizer.setOneShotAnimationType(animationType);
}
- private void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+ private void setPinnedStackAnimationListener(IPipAnimationListener callback) {
mPinnedStackAnimationRecentsCallback = callback;
}
@@ -494,15 +514,6 @@
mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds);
}
- /**
- * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
- * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
- */
- private void setPipExclusionBoundsChangeListener(
- Consumer<Rect> pipExclusionBoundsChangeListener) {
- mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
- }
-
@Override
public void onPipTransitionStarted(int direction, Rect pipBounds) {
if (isOutPipDirection(direction)) {
@@ -512,7 +523,11 @@
// Disable touches while the animation is running
mTouchHandler.setTouchEnabled(false);
if (mPinnedStackAnimationRecentsCallback != null) {
- mPinnedStackAnimationRecentsCallback.accept(true);
+ try {
+ mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call onPinnedStackAnimationStarted()", e);
+ }
}
}
@@ -638,7 +653,21 @@
mPipInputConsumer.dump(pw, innerPrefix);
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
private class PipImpl implements Pip {
+ private IPipImpl mIPip;
+
+ @Override
+ public IPip createExternalInterface() {
+ if (mIPip != null) {
+ mIPip.invalidate();
+ }
+ mIPip = new IPipImpl(PipController.this);
+ return mIPip;
+ }
+
@Override
public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
mMainExecutor.execute(() -> {
@@ -696,13 +725,6 @@
}
@Override
- public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
- mMainExecutor.execute(() -> {
- PipController.this.setPinnedStackAnimationListener(callback);
- });
- }
-
- @Override
public void setPinnedStackAnimationType(int animationType) {
mMainExecutor.execute(() -> {
PipController.this.setPinnedStackAnimationType(animationType);
@@ -712,7 +734,7 @@
@Override
public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
mMainExecutor.execute(() -> {
- PipController.this.setPipExclusionBoundsChangeListener(listener);
+ mPipBoundsState.setPipExclusionBoundsChangeCallback(listener);
});
}
@@ -724,29 +746,6 @@
}
@Override
- public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams, int launcherRotation,
- int shelfHeight) {
- Rect[] result = new Rect[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, shelfHeight);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to start swipe pip to home");
- }
- return result[0];
- }
-
- @Override
- public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- mMainExecutor.execute(() -> {
- PipController.this.stopSwipePipToHome(componentName, destinationBounds);
- });
- }
-
- @Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
@@ -757,4 +756,89 @@
}
}
}
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IPipImpl extends IPip.Stub {
+ private PipController mController;
+ private IPipAnimationListener mListener;
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final PipController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.setPinnedStackAnimationListener(null);
+ });
+ }
+ };
+
+ IPipImpl(PipController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams, int launcherRotation,
+ int shelfHeight) {
+ Rect[] result = new Rect[1];
+ executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
+ (controller) -> {
+ result[0] = controller.startSwipePipToHome(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, shelfHeight);
+ }, true /* blocking */);
+ return result[0];
+ }
+
+ @Override
+ public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+ executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
+ (controller) -> {
+ controller.stopSwipePipToHome(componentName, destinationBounds);
+ });
+ }
+
+ @Override
+ public void setShelfHeight(boolean visible, int height) {
+ executeRemoteCallWithTaskPermission(mController, "setShelfHeight",
+ (controller) -> {
+ controller.setShelfHeight(visible, height);
+ });
+ }
+
+ @Override
+ public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
+ (controller) -> {
+ if (mListener != null) {
+ // Reset the old death recipient
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ // Register the death recipient for the new listener to clear the listener
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.setPinnedStackAnimationListener(listener);
+ });
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 81a7ae1..402846f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -29,7 +29,6 @@
import android.util.Log;
import android.view.Choreographer;
-import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.AnimationHandler;
import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler;
import androidx.dynamicanimation.animation.SpringForce;
@@ -489,8 +488,7 @@
/**
* Animates the PiP to offset it from the IME or shelf.
*/
- @VisibleForTesting
- public void animateToOffset(Rect originalBounds, int offset) {
+ void animateToOffset(Rect originalBounds, int offset) {
if (DEBUG) {
Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset
+ " callers=\n" + Debug.getCallers(5, " "));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 543ecfc..b0a7319 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -57,8 +57,6 @@
import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.function.Consumer;
/**
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
@@ -80,7 +78,6 @@
private final ShellExecutor mMainExecutor;
private PipResizeGestureHandler mPipResizeGestureHandler;
- private WeakReference<Consumer<Rect>> mPipExclusionBoundsChangeListener;
private final PhonePipMenuController mMenuController;
private final AccessibilityManager mAccessibilityManager;
@@ -288,11 +285,6 @@
mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
}
- // Reset exclusion to none.
- if (mPipExclusionBoundsChangeListener != null
- && mPipExclusionBoundsChangeListener.get() != null) {
- mPipExclusionBoundsChangeListener.get().accept(new Rect());
- }
mPipResizeGestureHandler.onActivityUnpinned();
}
@@ -929,10 +921,6 @@
}
private void stashEndAction() {
- if (mPipExclusionBoundsChangeListener != null
- && mPipExclusionBoundsChangeListener.get() != null) {
- mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds());
- }
if (mPipBoundsState.getBounds().left < 0
&& mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
mPipUiEventLogger.log(
@@ -952,11 +940,6 @@
// dismiss overlay, so just finish it after the animation completes
mMenuController.hideMenu();
}
- // Reset exclusion to none.
- if (mPipExclusionBoundsChangeListener != null
- && mPipExclusionBoundsChangeListener.get() != null) {
- mPipExclusionBoundsChangeListener.get().accept(new Rect());
- }
}
private boolean shouldStash(PointF vel, Rect motionBounds) {
@@ -980,11 +963,6 @@
}
}
- void setPipExclusionBoundsChangeListener(Consumer<Rect> pipExclusionBoundsChangeListener) {
- mPipExclusionBoundsChangeListener = new WeakReference<>(pipExclusionBoundsChangeListener);
- pipExclusionBoundsChangeListener.accept(mPipBoundsState.getBounds());
- }
-
/**
* Updates the current movement bounds based on whether the menu is currently visible and
* resized.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 2b0a0cd..963a3dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -28,7 +28,7 @@
// with those in the framework ProtoLogGroup
WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
new file mode 100644
index 0000000..0c46eab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -0,0 +1,77 @@
+/*
+ * 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.wm.shell.splitscreen;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the splitscreen feature.
+ */
+interface ISplitScreen {
+
+ /**
+ * Registers a split screen listener.
+ */
+ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
+
+ /**
+ * Unregisters a split screen listener.
+ */
+ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
+
+ /**
+ * Hides the side-stage if it is currently visible.
+ */
+ oneway void setSideStageVisibility(boolean visible) = 3;
+
+ /**
+ * Removes a task from the side stage.
+ */
+ oneway void removeFromSideStage(int taskId) = 4;
+
+ /**
+ * Removes the split-screen stages.
+ */
+ oneway void exitSplitScreen() = 5;
+
+ /**
+ * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
+ */
+ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
+
+ /**
+ * Starts a task in a stage.
+ */
+ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
+
+ /**
+ * Starts a shortcut in a stage.
+ */
+ oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
+ in Bundle options, in UserHandle user) = 8;
+
+ /**
+ * Starts an activity in a stage.
+ */
+ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
+ int position, in Bundle options) = 9;
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
similarity index 83%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
rename to libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
index 54242be..faab4c2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreenListener.aidl
@@ -14,12 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.wm.shell.splitscreen;
/**
* Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
*/
oneway interface ISplitScreenListener {
+
+ /**
+ * Called when the stage position changes.
+ */
void onStagePositionChanged(int stage, int position);
+
+ /**
+ * Called when a task changes stages.
+ */
void onTaskStageChanged(int taskId, int stage, boolean visible);
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 25a84bd..340b55d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -35,7 +35,7 @@
* TODO: Figure out which of these are actually needed outside of the Shell
*/
@ExternalThread
-public interface SplitScreen extends DragAndDropPolicy.Starter {
+public interface SplitScreen {
/**
* Stage position isn't specified normally meaning to use what ever it is currently set to.
*/
@@ -89,35 +89,10 @@
void onTaskStageChanged(int taskId, @StageType int stage, boolean visible);
}
- /** @return {@code true} if split-screen is currently visible. */
- boolean isSplitScreenVisible();
- /** Moves a task in the side-stage of split-screen. */
- boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition);
- /** Moves a task in the side-stage of split-screen. */
- boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @StagePosition int sideStagePosition);
- /** Removes a task from the side-stage of split-screen. */
- boolean removeFromSideStage(int taskId);
- /** Sets the position of the side-stage. */
- void setSideStagePosition(@StagePosition int sideStagePosition);
- /** Hides the side-stage if it is currently visible. */
- void setSideStageVisibility(boolean visible);
-
- /** Removes the split-screen stages. */
- void exitSplitScreen();
- /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
- void exitSplitScreenOnHide(boolean exitSplitScreenOnHide);
- /** Gets the stage bounds. */
- void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
-
- void registerSplitScreenListener(SplitScreenListener listener);
- void unregisterSplitScreenListener(SplitScreenListener listener);
-
- void startTask(int taskId,
- @StageType int stage, @StagePosition int position, @Nullable Bundle options);
- void startShortcut(String packageName, String shortcutId, @StageType int stage,
- @StagePosition int position, @Nullable Bundle options, UserHandle user);
- void startIntent(PendingIntent intent, Context context,
- @Nullable Intent fillInIntent, @StageType int stage,
- @StagePosition int position, @Nullable Bundle options);
+ /**
+ * Returns a binder that can be passed to an external process to manipulate SplitScreen.
+ */
+ default ISplitScreen createExternalInterface() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index bb6f6f2..d4362ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
@@ -34,19 +35,24 @@
import android.content.pm.LauncherApps;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
+import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
import java.io.PrintWriter;
@@ -55,7 +61,8 @@
* {@link SplitScreen}.
* @see StageCoordinator
*/
-public class SplitScreenController implements DragAndDropPolicy.Starter {
+public class SplitScreenController implements DragAndDropPolicy.Starter,
+ RemoteCallable<SplitScreenController> {
private static final String TAG = SplitScreenController.class.getSimpleName();
private final ShellTaskOrganizer mTaskOrganizer;
@@ -84,6 +91,16 @@
return mImpl;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
public void onOrganizerRegistered() {
if (mStageCoordinator == null) {
// TODO: Multi-display
@@ -172,13 +189,13 @@
}
}
- public void startIntent(PendingIntent intent, Context context,
- Intent fillInIntent, @SplitScreen.StageType int stage,
- @SplitScreen.StagePosition int position, @Nullable Bundle options) {
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
+ @Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
try {
- intent.send(context, 0, fillInIntent, null, null, null, options);
+ intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
}
@@ -242,121 +259,170 @@
}
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
private class SplitScreenImpl implements SplitScreen {
+ private ISplitScreenImpl mISplitScreen;
+
@Override
- public boolean isSplitScreenVisible() {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.isSplitScreenVisible();
- }, Boolean.class);
+ public ISplitScreen createExternalInterface() {
+ if (mISplitScreen != null) {
+ mISplitScreen.invalidate();
+ }
+ mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
+ return mISplitScreen;
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private SplitScreenController mController;
+ private ISplitScreenListener mListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mListener != null) {
+ mListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onStagePositionChanged", e);
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ try {
+ if (mListener != null) {
+ mListener.onTaskStageChanged(taskId, stage, visible);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onTaskStageChanged", e);
+ }
+ }
+ };
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final SplitScreenController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+ };
+
+ public ISplitScreenImpl(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
}
@Override
- public boolean moveToSideStage(int taskId, int sideStagePosition) {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.moveToSideStage(taskId, sideStagePosition);
- }, Boolean.class);
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.registerSplitScreenListener(mSplitScreenListener);
+ });
}
@Override
- public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- int sideStagePosition) {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.moveToSideStage(task, sideStagePosition);
- }, Boolean.class);
- }
-
- @Override
- public boolean removeFromSideStage(int taskId) {
- return mMainExecutor.executeBlockingForResult(() -> {
- return SplitScreenController.this.removeFromSideStage(taskId);
- }, Boolean.class);
- }
-
- @Override
- public void setSideStagePosition(int sideStagePosition) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.setSideStagePosition(sideStagePosition);
- });
- }
-
- @Override
- public void setSideStageVisibility(boolean visible) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.setSideStageVisibility(visible);
- });
- }
-
- @Override
- public void enterSplitScreen(int taskId, boolean leftOrTop) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.enterSplitScreen(taskId, leftOrTop);
- });
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
}
@Override
public void exitSplitScreen() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.exitSplitScreen();
- });
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
+ (controller) -> {
+ controller.exitSplitScreen();
+ });
}
@Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
+ (controller) -> {
+ controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
}
@Override
- public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
- try {
- mMainExecutor.executeBlocking(() -> {
- SplitScreenController.this.getStageBounds(outTopOrLeftBounds,
- outBottomOrRightBounds);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to get stage bounds in 2s");
- }
+ public void setSideStageVisibility(boolean visible) {
+ executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
+ (controller) -> {
+ controller.setSideStageVisibility(visible);
+ });
}
@Override
- public void registerSplitScreenListener(SplitScreenListener listener) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.registerSplitScreenListener(listener);
- });
- }
-
- @Override
- public void unregisterSplitScreenListener(SplitScreenListener listener) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.unregisterSplitScreenListener(listener);
- });
+ public void removeFromSideStage(int taskId) {
+ executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
+ (controller) -> {
+ controller.removeFromSideStage(taskId);
+ });
}
@Override
public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.startTask(taskId, stage, position, options);
- });
+ executeRemoteCallWithTaskPermission(mController, "startTask",
+ (controller) -> {
+ controller.startTask(taskId, stage, position, options);
+ });
}
@Override
public void startShortcut(String packageName, String shortcutId, int stage, int position,
@Nullable Bundle options, UserHandle user) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.startShortcut(packageName, shortcutId, stage, position,
- options, user);
- });
+ executeRemoteCallWithTaskPermission(mController, "startShortcut",
+ (controller) -> {
+ controller.startShortcut(packageName, shortcutId, stage, position,
+ options, user);
+ });
}
@Override
- public void startIntent(PendingIntent intent, Context context, Intent fillInIntent,
- int stage, int position, @Nullable Bundle options) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.startIntent(intent, context, fillInIntent, stage,
- position, options);
- });
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startIntent",
+ (controller) -> {
+ controller.startIntent(intent, fillInIntent, stage, position, options);
+ });
}
}
-
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl
similarity index 62%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl
index 54242be..546c406 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindow.aidl
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.wm.shell.startingsurface;
+
+import com.android.wm.shell.startingsurface.IStartingWindowListener;
/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ * Interface that is exposed to remote callers to manipulate starting windows.
*/
-oneway interface ISplitScreenListener {
- void onStagePositionChanged(int stage, int position);
- void onTaskStageChanged(int taskId, int stage, boolean visible);
+interface IStartingWindow {
+ /**
+ * Sets listener to get task launching callbacks.
+ */
+ oneway void setStartingWindowListener(IStartingWindowListener listener) = 43;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl
similarity index 90%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl
rename to libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl
index eb3e60c..f562c8f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IStartingWindowListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/IStartingWindowListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.wm.shell.startingsurface;
/**
* Listener interface that Launcher attaches to SystemUI to get
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index f258286..079d689 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -16,35 +16,15 @@
package com.android.wm.shell.startingsurface;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.StartingWindowInfo;
-
-import java.util.function.BiConsumer;
/**
* Interface to engage starting window feature.
*/
public interface StartingSurface {
- /**
- * Called when a task need a starting window.
- */
- void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken);
- /**
- * Called when the content of a task is ready to show, starting window can be removed.
- */
- void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation);
- /**
- * Called when the Task wants to copy the splash screen.
- * @param taskId
- */
- void copySplashScreenView(int taskId);
/**
- * Registers the starting window listener.
- *
- * @param listener The callback when need a starting window.
+ * Returns a binder that can be passed to an external process to manipulate starting windows.
*/
- void setStartingWindowListener(BiConsumer<Integer, Integer> listener);
+ default IStartingWindow createExternalInterface() {
+ return null;
+ }
}
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 14fbaac..9212c4b 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
@@ -36,6 +36,7 @@
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -68,9 +69,6 @@
private final ShellExecutor mSplashScreenExecutor;
private final SplashscreenContentDrawer mSplashscreenContentDrawer;
- // TODO(b/131727939) remove this when clearing ActivityRecord
- private static final int REMOVE_WHEN_TIMEOUT = 2000;
-
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
TransactionPool pool) {
mContext = context;
@@ -149,11 +147,12 @@
context = displayContext;
if (theme != context.getThemeResId() || labelRes != 0) {
try {
- context = context.createPackageContext(
- activityInfo.packageName, CONTEXT_RESTRICTED);
+ context = context.createPackageContextAsUser(activityInfo.packageName,
+ CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
context.setTheme(theme);
} catch (PackageManager.NameNotFoundException e) {
- // Ignore
+ Slog.w(TAG, "Failed creating package context with package name "
+ + activityInfo.packageName + " for user " + taskInfo.userId, e);
}
}
@@ -293,12 +292,8 @@
TaskSnapshot snapshot) {
final int taskId = startingWindowInfo.taskInfo.taskId;
final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
- snapshot, mSplashScreenExecutor,
- () -> removeWindowNoAnimate(taskId));
- mSplashScreenExecutor.executeDelayed(() -> removeWindowNoAnimate(taskId),
- REMOVE_WHEN_TIMEOUT);
- final StartingWindowRecord tView =
- new StartingWindowRecord(null/* decorView */, surface);
+ snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
+ final StartingWindowRecord tView = new StartingWindowRecord(null/* decorView */, surface);
mStartingWindowRecords.put(taskId, tView);
}
@@ -352,8 +347,6 @@
}
if (shouldSaveView) {
removeWindowNoAnimate(taskId);
- mSplashScreenExecutor.executeDelayed(
- () -> removeWindowNoAnimate(taskId), REMOVE_WHEN_TIMEOUT);
saveSplashScreenRecord(taskId, view);
}
return shouldSaveView;
@@ -390,7 +383,6 @@
if (leash != null || playRevealAnimation) {
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
leash, frame, record.isEarlyExit(), exitFinish);
- mSplashScreenExecutor.executeDelayed(exitFinish, REMOVE_WHEN_TIMEOUT);
} else {
// the SplashScreenView has been copied to client, skip default exit
// animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index a694e52..a06068d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -25,6 +25,8 @@
import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
@@ -37,6 +39,9 @@
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -58,7 +63,7 @@
* constructor to keep everything synchronized.
* @hide
*/
-public class StartingWindowController {
+public class StartingWindowController implements RemoteCallable<StartingWindowController> {
private static final String TAG = StartingWindowController.class.getSimpleName();
static final boolean DEBUG_SPLASH_SCREEN = false;
static final boolean DEBUG_TASK_SNAPSHOT = false;
@@ -68,17 +73,17 @@
private BiConsumer<Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
+ private final Context mContext;
private final ShellExecutor mSplashScreenExecutor;
// For Car Launcher
public StartingWindowController(Context context, ShellExecutor splashScreenExecutor) {
- mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
- new TransactionPool());
- mSplashScreenExecutor = splashScreenExecutor;
+ this(context, splashScreenExecutor, new TransactionPool());
}
public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
TransactionPool pool) {
+ mContext = context;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool);
mSplashScreenExecutor = splashScreenExecutor;
}
@@ -90,6 +95,16 @@
return mImpl;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mSplashScreenExecutor;
+ }
+
private static class StartingTypeChecker {
TaskSnapshot mSnapshot;
@@ -150,6 +165,13 @@
}
return false;
}
+ if (!snapshot.getTopActivityComponent().equals(windowInfo.taskInfo.topActivity)) {
+ if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
+ Slog.d(TAG, "isSnapshotCompatible obsoleted snapshot "
+ + windowInfo.taskInfo.topActivity);
+ }
+ return false;
+ }
final int taskRotation = windowInfo.taskInfo.configuration
.windowConfiguration.getRotation();
@@ -188,59 +210,121 @@
/**
* Called when a task need a starting window.
*/
- void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
- final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo);
- final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
- if (mTaskLaunchingCallback != null) {
- mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
- }
- if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
- mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken);
- } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
- final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot;
- mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
- }
- // If prefer don't show, then don't show!
+ public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+ mSplashScreenExecutor.execute(() -> {
+ final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo);
+ final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
+ if (mTaskLaunchingCallback != null) {
+ mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
+ }
+ if (suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
+ mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken);
+ } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
+ final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot;
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
+ }
+ // If prefer don't show, then don't show!
+ });
}
- void copySplashScreenView(int taskId) {
- mStartingSurfaceDrawer.copySplashScreenView(taskId);
+ public void copySplashScreenView(int taskId) {
+ mSplashScreenExecutor.execute(() -> {
+ mStartingSurfaceDrawer.copySplashScreenView(taskId);
+ });
}
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
- void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
+ public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
boolean playRevealAnimation) {
- mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+ mSplashScreenExecutor.execute(() -> {
+ mStartingSurfaceDrawer.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+ });
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
private class StartingSurfaceImpl implements StartingSurface {
+ private IStartingWindowImpl mIStartingWindow;
@Override
- public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.addStartingWindow(windowInfo, appToken));
+ public IStartingWindowImpl createExternalInterface() {
+ if (mIStartingWindow != null) {
+ mIStartingWindow.invalidate();
+ }
+ mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
+ return mIStartingWindow;
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IStartingWindowImpl extends IStartingWindow.Stub {
+ private StartingWindowController mController;
+ private IStartingWindowListener mListener;
+ private final BiConsumer<Integer, Integer> mStartingWindowListener =
+ this::notifyIStartingWindowListener;
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final StartingWindowController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.setStartingWindowListener(null);
+ });
+ }
+ };
+
+ public IStartingWindowImpl(StartingWindowController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
}
@Override
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.removeStartingWindow(taskId, leash, frame,
- playRevealAnimation));
+ public void setStartingWindowListener(IStartingWindowListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener",
+ (controller) -> {
+ if (mListener != null) {
+ // Reset the old death recipient
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.setStartingWindowListener(mStartingWindowListener);
+ });
}
- @Override
- public void copySplashScreenView(int taskId) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.copySplashScreenView(taskId));
- }
+ private void notifyIStartingWindowListener(int taskId, int supportedType) {
+ if (mListener == null) {
+ return;
+ }
- @Override
- public void setStartingWindowListener(BiConsumer<Integer, Integer> listener) {
- mSplashScreenExecutor.execute(() ->
- StartingWindowController.this.setStartingWindowListener(listener));
+ try {
+ mListener.onTaskLaunching(taskId, supportedType);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify task launching", e);
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 629ff0d..b29b18b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -96,6 +96,17 @@
};
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getMode() == TRANSIT_CHANGE) {
+ // No default animation for this, so just update bounds/position.
+ t.setPosition(change.getLeash(),
+ change.getEndAbsBounds().left - change.getEndRelOffset().x,
+ change.getEndAbsBounds().top - change.getEndRelOffset().y);
+ if (change.getTaskInfo() != null) {
+ // Skip non-tasks since those usually have null bounds.
+ t.setWindowCrop(change.getLeash(),
+ change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
+ }
+ }
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
similarity index 63%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
copy to libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index 85bbf74..dffc700 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -16,25 +16,22 @@
package com.android.wm.shell.transition;
-import android.annotation.NonNull;
import android.window.IRemoteTransition;
import android.window.TransitionFilter;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
/**
- * Interface to manage remote transitions.
+ * Interface that is exposed to remote callers to manipulate the transitions feature.
*/
-@ExternalThread
-public interface RemoteTransitions {
- /**
- * Registers a remote transition.
- */
- void registerRemote(@NonNull TransitionFilter filter,
- @NonNull IRemoteTransition remoteTransition);
+interface IShellTransitions {
/**
- * Unregisters a remote transition.
+ * Registers a remote transition handler.
*/
- void unregisterRemote(@NonNull IRemoteTransition remoteTransition);
+ oneway void registerRemote(in TransitionFilter filter,
+ in IRemoteTransition remoteTransition) = 1;
+
+ /**
+ * Unregisters a remote transition handler.
+ */
+ oneway void unregisterRemote(in IRemoteTransition remoteTransition) = 2;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index ac93a17..9667f4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.transition;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
@@ -23,6 +25,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -31,6 +34,8 @@
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.BinderThread;
+
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -42,6 +47,8 @@
* if the request includes a specific remote.
*/
public class RemoteTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "RemoteTransitionHandler";
+
private final ShellExecutor mMainExecutor;
/** Includes remotes explicitly requested by, eg, ActivityOptions */
@@ -51,15 +58,33 @@
private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters =
new ArrayList<>();
+ private final IBinder.DeathRecipient mTransitionDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ mMainExecutor.execute(() -> {
+ mFilters.clear();
+ });
+ }
+ };
+
RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
}
void addFiltered(TransitionFilter filter, IRemoteTransition remote) {
+ try {
+ remote.asBinder().linkToDeath(mTransitionDeathRecipient, 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
mFilters.add(new Pair<>(filter, remote));
}
void removeFiltered(IRemoteTransition remote) {
+ remote.asBinder().unlinkToDeath(mTransitionDeathRecipient, 0 /* flags */);
for (int i = mFilters.size() - 1; i >= 0; --i) {
if (mFilters.get(i).second == remote) {
mFilters.remove(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
similarity index 83%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index 85bbf74..bc42c6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -26,7 +26,15 @@
* Interface to manage remote transitions.
*/
@ExternalThread
-public interface RemoteTransitions {
+public interface ShellTransitions {
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate remote transitions.
+ */
+ default IShellTransitions createExternalInterface() {
+ return null;
+ }
+
/**
* Registers a remote transition.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 677db10..ca1b53d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -23,6 +23,8 @@
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
@@ -51,6 +53,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -60,7 +63,7 @@
import java.util.Arrays;
/** Plays transition animations */
-public class Transitions {
+public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
/** Set to {@code true} to enable shell transitions. */
@@ -73,7 +76,7 @@
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
private final RemoteTransitionHandler mRemoteTransitionHandler;
- private final RemoteTransitionImpl mImpl = new RemoteTransitionImpl();
+ private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -87,10 +90,6 @@
/** Keeps track of currently tracked transitions and all the animations associated with each */
private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>();
- public static RemoteTransitions asRemoteTransitions(Transitions transitions) {
- return transitions.mImpl;
- }
-
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull Context context, @NonNull ShellExecutor mainExecutor,
@NonNull ShellExecutor animExecutor) {
@@ -126,6 +125,20 @@
mRemoteTransitionHandler = null;
}
+ public ShellTransitions asRemoteTransitions() {
+ return mImpl;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
private void dispatchAnimScaleSetting(float scale) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
mHandlers.get(i).setAnimScaleSetting(scale);
@@ -134,8 +147,8 @@
/** Create an empty/non-registering transitions object for system-ui tests. */
@VisibleForTesting
- public static RemoteTransitions createEmptyForTesting() {
- return new RemoteTransitions() {
+ public static ShellTransitions createEmptyForTesting() {
+ return new ShellTransitions() {
@Override
public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter,
@androidx.annotation.NonNull IRemoteTransition remoteTransition) {
@@ -426,24 +439,74 @@
}
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
@ExternalThread
- private class RemoteTransitionImpl implements RemoteTransitions {
+ private class ShellTransitionImpl implements ShellTransitions {
+ private IShellTransitionsImpl mIShellTransitions;
+
+ @Override
+ public IShellTransitions createExternalInterface() {
+ if (mIShellTransitions != null) {
+ mIShellTransitions.invalidate();
+ }
+ mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
+ return mIShellTransitions;
+ }
+
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull IRemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
- Transitions.this.registerRemote(filter, remoteTransition);
+ mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
});
}
@Override
public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
- Transitions.this.unregisterRemote(remoteTransition);
+ mRemoteTransitionHandler.removeFiltered(remoteTransition);
});
}
}
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+ private Transitions mTransitions;
+
+ IShellTransitionsImpl(Transitions transitions) {
+ mTransitions = transitions;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mTransitions = null;
+ }
+
+ @Override
+ public void registerRemote(@NonNull TransitionFilter filter,
+ @NonNull IRemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
+ (transitions) -> {
+ transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
+ });
+ }
+
+ @Override
+ public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
+ (transitions) -> {
+ transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
+ });
+ }
+ }
+
private class SettingsObserver extends ContentObserver {
SettingsObserver() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index 63968f3..98ce274 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -18,6 +18,7 @@
import android.os.SystemClock
import android.platform.test.annotations.Presubmit
+import android.provider.Settings
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -26,6 +27,8 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appPairsDividerIsInvisible
import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import org.junit.After
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,11 +36,10 @@
import org.junit.runners.Parameterized
/**
- * Test AppPairs launch.
- * To run this test: `atest WMShellFlickerTests:AppPairsTest`
- */
-/**
- * Test cold launch app from launcher.
+ * Test cold launch app from launcher. When the device doesn't support non-resizable in multi window
+ * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs should not pair
+ * non-resizable apps.
+ *
* To run this test: `atest WMShellFlickerTests:AppPairsTestCannotPairNonResizeableApps`
*/
@RequiresDevice
@@ -47,6 +49,7 @@
class AppPairsTestCannotPairNonResizeableApps(
testSpec: FlickerTestParameter
) : AppPairsTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -60,6 +63,24 @@
}
}
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow == 1) {
+ // Not support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
@FlakyTest
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
new file mode 100644
index 0000000..1e3595c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.wm.shell.flicker.apppairs
+
+import android.os.SystemClock
+import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appPairsDividerIsVisible
+import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launch app from launcher. When the device supports non-resizable in multi window
+ * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs can pair
+ * non-resizable apps.
+ *
+ * To run this test: `atest WMShellFlickerTests:AppPairsTestSupportPairNonResizeableApps`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AppPairsTestSupportPairNonResizeableApps(
+ testSpec: FlickerTestParameter
+) : AppPairsTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = {
+ super.transition(this, it)
+ transitions {
+ nonResizeableApp?.launchViaIntent(wmHelper)
+ // TODO pair apps through normal UX flow
+ executeShellCommand(
+ composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
+ SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ }
+ }
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow == 0) {
+ // Support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ @FlakyTest
+ @Test
+ override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+
+ @Presubmit
+ @Test
+ fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
+
+ @Presubmit
+ @Test
+ fun bothAppWindowVisible() {
+ val nonResizeableApp = nonResizeableApp
+ require(nonResizeableApp != null) {
+ "Non resizeable app not initialized"
+ }
+ testSpec.assertWmEnd {
+ isVisible(nonResizeableApp.defaultWindowName)
+ isVisible(primaryApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = AppPairsHelper.TEST_REPETITIONS)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
index 128560a..134d00b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.apppairs
import android.app.Instrumentation
+import android.content.Context
import android.platform.test.annotations.Presubmit
import android.system.helpers.ActivityHelper
import android.util.Log
@@ -46,6 +47,7 @@
abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val context: Context = instrumentation.context
protected val isRotated = testSpec.config.startRotation.isRotated()
protected val activityHelper = ActivityHelper.getInstance()
protected val appPairsHelper = AppPairsHelper(instrumentation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
index 17c51fb..bca2576 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -29,8 +29,7 @@
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -60,6 +59,11 @@
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+ splitScreenApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 169271943)
@Test
fun dockedStackPrimaryBoundsIsVisible() =
@@ -73,12 +77,8 @@
@FlakyTest(bugId = 178531736)
@Test
// b/178531736
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME,
- WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
- splitScreenApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -91,12 +91,8 @@
@FlakyTest(bugId = 178531736)
@Test
// b/178531736
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME,
- WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
- splitScreenApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
index a94fd46..9000f22 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -30,8 +30,7 @@
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
@@ -62,6 +61,11 @@
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ secondaryApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 169271943)
@Test
fun dockedStackPrimaryBoundsIsVisible() =
@@ -83,12 +87,8 @@
@Test
// TODO(b/178447631) Remove Splash Screen from white list when flicker lib
// add a wait for splash screen be gone
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
- splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -104,12 +104,8 @@
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
- splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
deleted file mode 100644
index 238059b..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
+++ /dev/null
@@ -1,108 +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.wm.shell.flicker.legacysplitscreen
-
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.WALLPAPER_TITLE
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickstep
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assert
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open non-resizable activity will auto exit split screen mode
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNonResizableNotDock`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FlakyTest(bugId = 173875043)
-class EnterSplitScreenNonResizableNotDock(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- super.transition(this, configuration)
- teardown {
- eachRun {
- nonResizeableApp.exit(wmHelper)
- }
- }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- device.openQuickstep(wmHelper)
- if (device.canSplitScreen(wmHelper)) {
- Assert.fail("Non-resizeable app should not enter split screen")
- }
- }
- }
-
- @Test
- fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
-
- @FlakyTest(bugId = 178447631)
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME,
- SPLASH_SCREEN_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
-
- @Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(WALLPAPER_TITLE,
- LAUNCHER_PACKAGE_NAME,
- SPLASH_SCREEN_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
-
- @Test
- fun appWindowIsVisible() {
- testSpec.assertWmEnd {
- isInvisible(nonResizeableApp.defaultWindowName)
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
new file mode 100644
index 0000000..7d22d4d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
@@ -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 com.android.wm.shell.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.canSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickstep
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen from non-resizable activity. When the device doesn't support
+ * non-resizable in multi window, there should be no button to enter split screen for non-resizable
+ * activity.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNotSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class EnterSplitScreenNotSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ device.openQuickstep(wmHelper)
+ if (device.canSplitScreen(wmHelper)) {
+ Assert.fail("Non-resizeable app should not enter split screen")
+ }
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow == 1) {
+ // Not support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
+
+ @Test
+ fun appWindowIsVisible() {
+ testSpec.assertWmEnd {
+ isInvisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
new file mode 100644
index 0000000..9b4a103
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen from non-resizable activity. When the device supports
+ * non-resizable in multi window, there should be a button to enter split screen for non-resizable
+ * activity.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class EnterSplitScreenSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
+ nonResizeableApp.defaultWindowName,
+ splitScreenApp.defaultWindowName)
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow != 1) {
+ // Support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+
+ @Test
+ fun appWindowIsVisible() {
+ testSpec.assertWmEnd {
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
index acd570a..9717709 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -30,8 +30,7 @@
import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
@@ -70,17 +69,20 @@
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@Presubmit
@Test
fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(DOCKED_STACK_DIVIDER)
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
@Presubmit
@Test
@@ -97,11 +99,8 @@
@FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
index cef1886..3f714bb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -30,8 +30,7 @@
import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
@@ -70,6 +69,11 @@
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 175687842)
@Test
fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
@@ -80,11 +84,8 @@
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -101,11 +102,8 @@
@FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
new file mode 100644
index 0000000..8923845
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non-resizable activity via intent in split screen mode. When the device does not
+ * support non-resizable in multi window, it should trigger exit split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentNotSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LegacySplitScreenFromIntentNotSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+ transitions {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow == 1) {
+ // Not support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun resizableAppLayerBecomesInvisible() =
+ testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun resizableAppWindowBecomesInvisible() =
+ testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible()
+
+ @Test
+ fun onlyNonResizableAppWindowIsVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isInvisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
new file mode 100644
index 0000000..2f5e0bd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non-resizable activity via intent in split screen mode. When the device supports
+ * non-resizable in multi window, it should show the non-resizable app in split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LegacySplitScreenFromIntentSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+ transitions {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME,
+ nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow == 0) {
+ // Support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible()
+
+ @Test
+ fun bothAppsWindowsAreVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isVisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
new file mode 100644
index 0000000..a42774d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.wm.shell.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non-resizable activity via recent overview in split screen mode. When the device does
+ * not support non-resizable in multi window, it should trigger exit split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentNotSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LegacySplitScreenFromRecentNotSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+ transitions {
+ device.reopenAppFromOverview(wmHelper)
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow == 1) {
+ // Not support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun resizableAppLayerBecomesInvisible() =
+ testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun resizableAppWindowBecomesInvisible() =
+ testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible()
+
+ @Test
+ fun onlyNonResizableAppWindowIsVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isInvisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
new file mode 100644
index 0000000..14f6dee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
@@ -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.wm.shell.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.provider.Settings
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.After
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non-resizable activity via recent overview in split screen mode. When the device
+ * supports non-resizable in multi window, it should show the non-resizable app in split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentSupportNonResizable`
+ */
+@Postsubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LegacySplitScreenFromRecentSupportNonResizable(
+ testSpec: FlickerTestParameter
+) : LegacySplitScreenTransition(testSpec) {
+ var prevSupportNonResizableInMultiWindow = 0
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { configuration ->
+ cleanSetup(this, configuration)
+ setup {
+ eachRun {
+ nonResizeableApp.launchViaIntent(wmHelper)
+ splitScreenApp.launchViaIntent(wmHelper)
+ device.launchSplitScreen(wmHelper)
+ }
+ }
+ transitions {
+ device.reopenAppFromOverview(wmHelper)
+ }
+ }
+
+ override val ignoredWindows: List<String>
+ get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME,
+ splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
+ @Before
+ fun setup() {
+ prevSupportNonResizableInMultiWindow = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+ if (prevSupportNonResizableInMultiWindow == 0) {
+ // Support non-resizable in multi window
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1)
+ }
+ }
+
+ @After
+ fun teardown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+ prevSupportNonResizableInMultiWindow)
+ }
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest(bugId = 178447631)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Test
+ fun nonResizableAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun nonResizableAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+
+ @Test
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible()
+
+ @Test
+ fun bothAppsWindowsAreVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isVisible(splitScreenApp.defaultWindowName)
+ isVisible(nonResizeableApp.defaultWindowName)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
index 1e89a25..08d5db0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
@@ -17,11 +17,13 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.view.Surface
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import org.junit.Test
abstract class LegacySplitScreenRotateTransition(
testSpec: FlickerTestParameter
@@ -44,4 +46,16 @@
}
}
}
+
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index 7f69a66..72d6f56 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -34,14 +34,13 @@
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.layerBecomesInvisible
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
import org.junit.FixMethodOrder
@@ -89,6 +88,10 @@
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(launcherPackageName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@Presubmit
@Test
fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
@@ -99,11 +102,6 @@
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
@Presubmit
@@ -122,8 +120,8 @@
@Presubmit
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(launcherPackageName))
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
index 91ea871..e13056c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -17,6 +17,8 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.app.Instrumentation
+import android.content.Context
+import android.platform.test.annotations.Presubmit
import android.support.test.launcherhelper.LauncherStrategyFactory
import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
@@ -29,10 +31,13 @@
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.Test
abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val context: Context = instrumentation.context
protected val isRotated = testSpec.config.startRotation.isRotated()
protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
@@ -40,6 +45,15 @@
protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
.launcherStrategy.supportedLauncherPackage
+ /**
+ * List of windows that are ignored when verifying that visible elements appear on 2
+ * consecutive entries in the trace.
+ *
+ * b/182720234
+ */
+ open val ignoredWindows: List<String> = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = { configuration ->
setup {
@@ -88,11 +102,26 @@
}
}
+ @Presubmit
+ @Test
+ open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoredWindows)
+ }
+ }
+
+ @Presubmit
+ @Test
+ open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoredWindows)
+ }
+ }
+
companion object {
internal const val LIVE_WALLPAPER_PACKAGE_NAME =
"com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
internal const val LETTERBOX_NAME = "Letterbox"
internal const val TOAST_NAME = "Toast"
- internal const val SPLASH_SCREEN_NAME = "Splash Screen"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
deleted file mode 100644
index caafa27..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
+++ /dev/null
@@ -1,117 +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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.appWindowBecomesInVisible
-import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launch non resizable activity in split screen mode will trigger exit split screen mode
- * (Non resizable activity launch via recent overview)
- * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableDismissInLegacySplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- @Presubmit
- @Test
- fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
-
- @FlakyTest(bugId = 178447631)
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME, TOAST_NAME,
- splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName)
- )
-
- @Presubmit
- @Test
- fun layerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
-
- @Presubmit
- @Test
- fun appWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
-
- @Presubmit
- @Test
- fun appWindowBecomesInVisible() =
- testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
-
- @FlakyTest(bugId = 178447631)
- @Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME, TOAST_NAME,
- splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName)
- )
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
deleted file mode 100644
index 543484a..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
+++ /dev/null
@@ -1,119 +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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.appWindowBecomesInVisible
-import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launch non resizable activity in split screen mode will trigger exit split screen mode
- * (Non resizable activity launch via intent)
- * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreen`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableLaunchInLegacySplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = { configuration ->
- cleanSetup(this, configuration)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- wmHelper.waitForAppTransitionIdle()
- }
- }
-
- @Presubmit
- @Test
- fun layerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
-
- @Presubmit
- @Test
- fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
-
- @FlakyTest(bugId = 178447631)
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER,
- LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
-
- @Presubmit
- @Test
- fun appWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
-
- @Presubmit
- @Test
- fun appWindowBecomesInVisible() =
- testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
-
- @FlakyTest(bugId = 178447631)
- @Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(DOCKED_STACK_DIVIDER,
- LAUNCHER_PACKAGE_NAME,
- LETTERBOX_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
- )
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
index d228337..8f15e50 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -31,8 +31,7 @@
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
@@ -61,12 +60,15 @@
}
}
+ override val ignoredWindows: List<String>
+ get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+
@FlakyTest(bugId = 178447631)
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName)
- )
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
@@ -90,10 +92,8 @@
@FlakyTest(bugId = 178447631)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName)
- )
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@FlakyTest(bugId = 151179149)
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index f5174bc..f40a08c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -40,8 +40,6 @@
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.traces.layers.getVisibleBounds
import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
@@ -107,8 +105,11 @@
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+ }
@FlakyTest(bugId = 156223549)
@Test
@@ -144,8 +145,8 @@
testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Test
fun topAppLayerIsAlwaysVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 4847c98..cd20dde 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,10 +16,8 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -64,7 +62,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipWindowBecomesVisible() {
testSpec.assertWm {
@@ -74,22 +72,6 @@
}
}
- @FlakyTest(bugId = 140855415)
- @Test
- override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
-
- @FlakyTest(bugId = 140855415)
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 140855415)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @FlakyTest(bugId = 140855415)
- @Test
- override fun noUncoveredRegions() = super.noUncoveredRegions()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
index afaf33a..c7a1c9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -51,40 +51,40 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerRotatesScales() =
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- @Postsubmit
+ @Presubmit
@Test
override fun noUncoveredRegions() = super.noUncoveredRegions()
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index 3309e10..bf148bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -78,7 +78,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipWindowInsideDisplayBounds() {
testSpec.assertWm {
@@ -86,7 +86,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
@@ -96,15 +96,15 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerInsideDisplayBounds() {
testSpec.assertLayers {
@@ -112,7 +112,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun bothAppLayersVisible() {
testSpec.assertLayersEnd {
@@ -121,11 +121,11 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
index d011419..f554ca3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -55,19 +55,11 @@
}
}
- @Postsubmit
- @Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- @Postsubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
- @Postsubmit
+ @Presubmit
@Test
fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) }
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerInsideDisplay() {
testSpec.assertLayersStart {
@@ -75,7 +67,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipWindowMovesUp() = testSpec.assertWmEnd {
val initialState = this.trace?.first()?.wmState
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 49a1055..8ceef8a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -18,7 +18,6 @@
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -76,19 +75,19 @@
override fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
testSpec.config.endRotation, allStates = false)
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
override fun navBarLayerRotatesAndScales() =
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
testSpec.config.endRotation)
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
override fun statusBarLayerRotatesScales() =
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
testSpec.config.endRotation)
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun appLayerRotates_StartingBounds() {
testSpec.assertLayersStart {
@@ -97,7 +96,7 @@
}
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun appLayerRotates_EndingBounds() {
testSpec.assertLayersEnd {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 67e1768..102af92 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -112,7 +111,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipAlwaysVisible() = testSpec.assertWm {
this.showsAppWindow(pipApp.windowName)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index bf84a6e..da95c77 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -16,8 +16,6 @@
package com.android.wm.shell;
-import android.os.Looper;
-
import com.android.wm.shell.common.ShellExecutor;
import java.util.ArrayList;
@@ -40,11 +38,6 @@
}
@Override
- public void removeAllCallbacks() {
- mRunnables.clear();
- }
-
- @Override
public void removeCallbacks(Runnable r) {
mRunnables.remove(r);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 4cedc48..ef046d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.graphics.Insets;
import android.graphics.Point;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
@@ -119,7 +120,8 @@
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
- new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
+ new InsetsSourceControl(
+ ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0), Insets.NONE)
};
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index c1c4c6d..2f2bbba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -205,7 +205,7 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -217,12 +217,12 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -234,12 +234,12 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -251,7 +251,7 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -263,7 +263,7 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
}
@@ -276,13 +276,13 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -295,13 +295,13 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED), any());
reset(mSplitScreenStarter);
// TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), any(),
eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT), any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index c5221de..bd5fe2b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -16,11 +16,10 @@
package com.android.wm.shell.onehanded;
-import static android.window.DisplayAreaOrganizer.FEATURE_ONE_HANDED;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
@@ -67,6 +66,8 @@
@Mock
OneHandedGestureHandler mMockGestureHandler;
@Mock
+ OneHandedSettingsUtil mMockSettingsUitl;
+ @Mock
OneHandedUiEventLogger mMockUiEventLogger;
@Mock
IOverlayManager mMockOverlayManager;
@@ -79,13 +80,9 @@
@Mock
Handler mMockShellMainHandler;
- final boolean mDefaultEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- getTestContext().getContentResolver());
- final boolean mDefaultSwipeToNotificationEnabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- getTestContext().getContentResolver());
- final boolean mDefaultTapAppToExitEnabled = OneHandedSettingsUtil.getSettingsTapsAppToExit(
- getTestContext().getContentResolver());
+ final boolean mDefaultEnabled = true;
+ final boolean mDefaultSwipeToNotificationEnabled = false;
+ final boolean mDefaultTapAppToExitEnabled = true;
@Before
public void setUp() {
@@ -97,6 +94,14 @@
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>());
when(mMockBackgroundOrganizer.getBackgroundSurface()).thenReturn(mMockLeash);
+ when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any())).thenReturn(
+ mDefaultEnabled);
+ when(mMockSettingsUitl.getSettingsOneHandedModeTimeout(any())).thenReturn(
+ OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
+ when(mMockSettingsUitl.getSettingsTapsAppToExit(any())).thenReturn(
+ mDefaultTapAppToExitEnabled);
+ when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any())).thenReturn(
+ mDefaultSwipeToNotificationEnabled);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
@@ -107,6 +112,7 @@
mMockTouchHandler,
mMockTutorialHandler,
mMockGestureHandler,
+ mMockSettingsUitl,
mSpiedTimeoutHandler,
mMockUiEventLogger,
mMockOverlayManager,
@@ -121,22 +127,13 @@
final OneHandedAnimationController animationController = new OneHandedAnimationController(
mContext);
OneHandedDisplayAreaOrganizer displayAreaOrganizer = new OneHandedDisplayAreaOrganizer(
- mContext, mWindowManager, mMockDisplayController, animationController,
- mMockTutorialHandler, mMockBackgroundOrganizer, mMockShellMainExecutor);
+ mContext, mWindowManager, animationController, mMockTutorialHandler,
+ mMockBackgroundOrganizer, mMockShellMainExecutor);
assertThat(displayAreaOrganizer.isInOneHanded()).isFalse();
}
@Test
- public void testNoRegisterAndUnregisterInSameCall() {
- if (mDefaultEnabled) {
- verify(mMockDisplayAreaOrganizer, never()).unregisterOrganizer();
- } else {
- verify(mMockDisplayAreaOrganizer, never()).registerOrganizer(FEATURE_ONE_HANDED);
- }
- }
-
- @Test
public void testStartOneHandedShouldTriggerScheduleOffset() {
when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
mSpiedOneHandedController.setOneHandedEnabled(true);
@@ -190,35 +187,39 @@
public void testUpdateEnabled() {
mSpiedOneHandedController.setOneHandedEnabled(true);
- verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled);
- verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(
- mDefaultEnabled || mDefaultSwipeToNotificationEnabled);
+ verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
+ verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
}
@Test
public void testUpdateSwipeToNotification() {
mSpiedOneHandedController.setSwipeToNotificationEnabled(mDefaultSwipeToNotificationEnabled);
- verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(mDefaultEnabled);
- verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(
- mDefaultEnabled || mDefaultSwipeToNotificationEnabled);
+ verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
+ verify(mMockGestureHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
}
@Test
- public void testSettingsObserverUpdateTapAppToExit() {
- mSpiedOneHandedController.onTaskChangeExitSettingChanged();
- if (mDefaultTapAppToExitEnabled) {
- verify(mMockTaskStackListener, atLeastOnce()).addListener(any());
- } else {
- verify(mMockTaskStackListener, atLeastOnce()).removeListener(any());
- }
+ public void testTapAppToExitEnabledAddListener() {
+ mSpiedOneHandedController.setTaskChangeToExit(mDefaultTapAppToExitEnabled);
+
+ // If device settings default ON, then addListener() will be trigger 1 time at init
+ verify(mMockTaskStackListener, atLeastOnce()).addListener(any());
+ }
+
+ @Test
+ public void testTapAppToExitDisabledRemoveListener() {
+ mSpiedOneHandedController.setTaskChangeToExit(!mDefaultTapAppToExitEnabled);
+
+ // If device settings default ON, then removeListener() will be trigger 1 time at init
+ verify(mMockTaskStackListener, atLeastOnce()).removeListener(any());
}
@Test
public void testSettingsObserverUpdateEnabled() {
mSpiedOneHandedController.onEnabledSettingChanged();
- verify(mSpiedOneHandedController).setOneHandedEnabled(mDefaultEnabled);
+ verify(mSpiedOneHandedController).setOneHandedEnabled(anyBoolean());
}
@Test
@@ -232,14 +233,7 @@
public void testSettingsObserverUpdateSwipeToNotification() {
mSpiedOneHandedController.onSwipeToNotificationEnabledSettingChanged();
- // Swipe to notification function is opposite with one handed mode function
- if (mDefaultSwipeToNotificationEnabled) {
- verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(
- mDefaultSwipeToNotificationEnabled);
- } else {
- verify(mSpiedOneHandedController, never()).setSwipeToNotificationEnabled(
- mDefaultSwipeToNotificationEnabled);
- }
+ verify(mSpiedOneHandedController).setSwipeToNotificationEnabled(anyBoolean());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 1fa1e2f..f897b09 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -122,7 +122,6 @@
mSpiedDisplayAreaOrganizer = spy(new OneHandedDisplayAreaOrganizer(mContext,
mWindowManager,
- mMockDisplayController,
mMockAnimationController,
mTutorialHandler,
mMockBackgroundOrganizer,
@@ -170,6 +169,15 @@
}
@Test
+ public void testRotation_getNewDisplayBounds() {
+ when(mMockLeash.isValid()).thenReturn(false);
+ // Rotate 0 -> 90
+ mSpiedDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90,
+ mMockWindowContainerTransaction);
+ verify(mSpiedDisplayAreaOrganizer).getDisplayBounds();
+ }
+
+ @Test
public void testRotation_portrait_0_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 90
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
index 61643d8..1e6c41a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedSettingsUtilTest.java
@@ -16,17 +16,11 @@
package com.android.wm.shell.onehanded;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER;
-import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS;
-
-import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
import android.content.ContentResolver;
import android.database.ContentObserver;
-import android.net.Uri;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -34,76 +28,30 @@
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)
public class OneHandedSettingsUtilTest extends OneHandedTestCase {
- ContentResolver mContentResolver;
- ContentObserver mContentObserver;
- boolean mOnChanged;
+ OneHandedSettingsUtil mSettingsUtil;
+
+ @Mock
+ ContentResolver mMockContentResolver;
+ @Mock
+ ContentObserver mMockContentObserver;
@Before
public void setUp() {
- mContentResolver = mContext.getContentResolver();
- mContentObserver = new ContentObserver(mContext.getMainThreadHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- mOnChanged = true;
- }
- };
- }
+ MockitoAnnotations.initMocks(this);
- @Test
- public void testRegisterSecureKeyObserver() {
- final Uri result = OneHandedSettingsUtil.registerSettingsKeyObserver(
- Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver);
-
- assertThat(result).isNotNull();
-
- OneHandedSettingsUtil.registerSettingsKeyObserver(
- Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver);
+ mSettingsUtil = new OneHandedSettingsUtil();
}
@Test
public void testUnregisterSecureKeyObserver() {
- OneHandedSettingsUtil.registerSettingsKeyObserver(
- Settings.Secure.TAPS_APP_TO_EXIT, mContentResolver, mContentObserver);
- OneHandedSettingsUtil.unregisterSettingsKeyObserver(mContentResolver, mContentObserver);
+ mSettingsUtil.unregisterSettingsKeyObserver(mMockContentResolver, mMockContentObserver);
- assertThat(mOnChanged).isFalse();
-
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.TAPS_APP_TO_EXIT, 0);
-
- assertThat(mOnChanged).isFalse();
- }
-
- @Test
- public void testGetSettingsIsOneHandedModeEnabled() {
- assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- mContentResolver)).isAnyOf(true, false);
- }
-
- @Test
- public void testGetSettingsTapsAppToExit() {
- assertThat(OneHandedSettingsUtil.getSettingsTapsAppToExit(
- mContentResolver)).isAnyOf(true, false);
- }
-
- @Test
- public void testGetSettingsOneHandedModeTimeout() {
- assertThat(OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
- mContentResolver)).isAnyOf(
- ONE_HANDED_TIMEOUT_NEVER,
- ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS,
- ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS,
- ONE_HANDED_TIMEOUT_LONG_IN_SECONDS);
- }
-
- @Test
- public void testGetSettingsSwipeToNotificationEnabled() {
- assertThat(OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- mContentResolver)).isAnyOf(true, false);
+ verify(mMockContentResolver).unregisterContentObserver(any());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index 69c537c..f586dda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -64,6 +64,8 @@
Handler mMockShellMainHandler;
@Mock
OneHandedUiEventLogger mMockUiEventLogger;
+ @Mock
+ OneHandedSettingsUtil mMockSettingsUtil;
@Before
public void setUp() {
@@ -80,6 +82,7 @@
mMockTouchHandler,
mMockTutorialHandler,
mMockGestureHandler,
+ mMockSettingsUtil,
mTimeoutHandler,
mMockUiEventLogger,
mMockOverlayManager,
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index c0ef7be..7e45f95 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -226,8 +226,6 @@
}
void AssetManager2::DumpToLog() const {
- base::ScopedLogSeverity _log(base::INFO);
-
LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this);
std::string list;
@@ -1721,7 +1719,6 @@
}
void Theme::Dump() const {
- base::ScopedLogSeverity _log(base::INFO);
LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
for (int p = 0; p < packages_.size(); p++) {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 971a53a..e58f31f 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -126,7 +126,7 @@
SkAndroidFrameworkTraceUtil::setEnableTracing(
base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false));
- runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false);
+ runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index dcb79ba..42aa87b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -160,7 +160,7 @@
/**
* Property for whether this is running in the emulator.
*/
-#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
+#define PROPERTY_IS_EMULATOR "ro.boot.qemu"
///////////////////////////////////////////////////////////////////////////////
// Misc
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index e93824d..0112686 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -336,6 +336,7 @@
GET_DEV_PROC(ResetCommandBuffer);
GET_DEV_PROC(ResetFences);
GET_DEV_PROC(WaitForFences);
+ GET_DEV_PROC(FrameBoundaryANDROID);
}
void VulkanManager::initialize() {
@@ -516,6 +517,25 @@
if (semaphore != VK_NULL_HANDLE) {
if (submitted == GrSemaphoresSubmitted::kYes) {
mSwapSemaphore = semaphore;
+ if (mFrameBoundaryANDROID) {
+ // retrieve VkImage used as render target
+ VkImage image = VK_NULL_HANDLE;
+ GrBackendRenderTarget backendRenderTarget =
+ surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
+ if (backendRenderTarget.isValid()) {
+ GrVkImageInfo info;
+ if (backendRenderTarget.getVkImageInfo(&info)) {
+ image = info.fImage;
+ } else {
+ ALOGE("Frame boundary: backend is not vulkan");
+ }
+ } else {
+ ALOGE("Frame boundary: invalid backend render target");
+ }
+ // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+ // it won't wait on it.
+ mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
+ }
} else {
destroy_semaphore(mDestroySemaphoreContext);
mDestroySemaphoreContext = nullptr;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 0912369..7b5fe19 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -31,6 +31,21 @@
#include <vk/GrVkExtensions.h>
#include <vulkan/vulkan.h>
+// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI
+// (https://github.com/google/agi) to enable profiling of apps rendering via
+// HWUI. This extension is not defined in Khronos, hence the need to declare it
+// manually here. There's a superseding extension (VK_EXT_frame_boundary) being
+// discussed in Khronos, but in the meantime we use the bespoke
+// VK_ANDROID_frame_boundary. This is a device extension that is implemented by
+// AGI's Vulkan capture layer, such that it is only supported by devices when
+// AGI is doing a capture of the app.
+//
+// TODO(b/182165045): use the Khronos blessed VK_EXT_frame_boudary once it has
+// landed in the spec.
+typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore semaphore,
+ VkImage image);
+#define VK_ANDROID_FRAME_BOUNDARY_EXTENSION_NAME "VK_ANDROID_frame_boundary"
+
#include "Frame.h"
#include "IRenderPipeline.h"
#include "VulkanSurface.h"
@@ -160,6 +175,7 @@
VkPtr<PFN_vkDestroyFence> mDestroyFence;
VkPtr<PFN_vkWaitForFences> mWaitForFences;
VkPtr<PFN_vkResetFences> mResetFences;
+ VkPtr<PFN_vkFrameBoundaryANDROID> mFrameBoundaryANDROID;
VkInstance mInstance = VK_NULL_HANDLE;
VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
diff --git a/location/java/android/location/CorrelationVector.java b/location/java/android/location/CorrelationVector.java
index 4b6e688..718977f 100644
--- a/location/java/android/location/CorrelationVector.java
+++ b/location/java/android/location/CorrelationVector.java
@@ -77,6 +77,9 @@
* be encoded as signed 16 bit integer where 1 is represented by 32767 and -1 is represented
* by -32768.
*
+ * <p>The values are quantized using a 16bit integer to save on the data size since the array
+ * contains real data and it might grow.
+ *
*/
@NonNull
public int[] getMagnitude() {
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 84a363d..dbf2621 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -363,10 +363,10 @@
* <p>The bytes (or words) specified using big endian format (MSB first).
*
* <ul>
- * <li>For GPS L1 C/A, Beidou D1 & Beidou D2, each subframe contains 10 30-bit words. Each
- * word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip B31 and B32), with
- * MSB first, for a total of 40 bytes, covering a time period of 6, 6, and 0.6 seconds,
- * respectively.</li>
+ * <li>For GPS L1 C/A, IRNSS L5 C/A, Beidou D1 & Beidou D2, each subframe contains 10
+ * 30-bit words. Each word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip
+ * B31 and B32), with MSB first, for a total of 40 bytes, covering a time period of 6, 6, and
+ * 0.6 seconds, respectively.</li>
* <li>For Glonass L1 C/A, each string contains 85 data bits, including the checksum. These
* bits should be fit into 11 bytes, with MSB first (skip B86-B88), covering a time period of 2
* seconds.</li>
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e8e2631..f957a73 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1988,7 +1988,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public boolean setPreferredDeviceForCapturePreset(int capturePreset,
+ public boolean setPreferredDeviceForCapturePreset(@MediaRecorder.SystemSource int capturePreset,
@NonNull AudioDeviceAttributes device) {
return setPreferredDevicesForCapturePreset(capturePreset, Arrays.asList(device));
}
@@ -2002,7 +2002,8 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public boolean clearPreferredDevicesForCapturePreset(int capturePreset) {
+ public boolean clearPreferredDevicesForCapturePreset(
+ @MediaRecorder.SystemSource int capturePreset) {
if (!MediaRecorder.isValidAudioSource(capturePreset)) {
return false;
}
@@ -2024,7 +2025,8 @@
@NonNull
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) {
+ public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(
+ @MediaRecorder.SystemSource int capturePreset) {
if (!MediaRecorder.isValidAudioSource(capturePreset)) {
return new ArrayList<AudioDeviceAttributes>();
}
@@ -2036,7 +2038,8 @@
}
private boolean setPreferredDevicesForCapturePreset(
- int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
+ @MediaRecorder.SystemSource int capturePreset,
+ @NonNull List<AudioDeviceAttributes> devices) {
Objects.requireNonNull(devices);
if (!MediaRecorder.isValidAudioSource(capturePreset)) {
return false;
@@ -2081,7 +2084,8 @@
* @param devices a list of newly set preferred audio devices
*/
void onPreferredDevicesForCapturePresetChanged(
- int capturePreset, @NonNull List<AudioDeviceAttributes> devices);
+ @MediaRecorder.SystemSource int capturePreset,
+ @NonNull List<AudioDeviceAttributes> devices);
}
/**
@@ -5287,7 +5291,10 @@
* otherwise (typically one device, except for duplicated paths).
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.QUERY_AUDIO_STATE
+ })
public @NonNull List<AudioDeviceAttributes> getDevicesForAttributes(
@NonNull AudioAttributes attributes) {
Objects.requireNonNull(attributes);
@@ -5426,7 +5433,10 @@
* @return the volume behavior for the device
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.QUERY_AUDIO_STATE
+ })
public @DeviceVolumeBehavior
int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
// verify arguments (validity of device type is enforced in server)
@@ -5440,6 +5450,28 @@
}
}
+ /**
+ * @hide
+ * Returns {@code true} if the volume device behavior is {@link #DEVICE_VOLUME_BEHAVIOR_FULL}.
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.QUERY_AUDIO_STATE
+ })
+ public boolean isFullVolumeDevice() {
+ final AudioAttributes attributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+ final List<AudioDeviceAttributes> devices = getDevicesForAttributes(attributes);
+ for (AudioDeviceAttributes device : devices) {
+ if (getDeviceVolumeBehavior(device) == DEVICE_VOLUME_BEHAVIOR_FULL) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Indicate wired accessory connection state change.
* @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 88731d2..bccefdf 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2086,6 +2086,65 @@
}
/**
+ * Sets the streaming start threshold for an <code>AudioTrack</code>.
+ * <p> The streaming start threshold is the buffer level that the written audio
+ * data must reach for audio streaming to start after {@link #play()} is called.
+ * <p> For compressed streams, the size of a frame is considered to be exactly one byte.
+ *
+ * @param startThresholdInFrames the desired start threshold.
+ * @return the actual start threshold in frames value. This is
+ * an integer between 1 to the buffer capacity
+ * (see {@link #getBufferCapacityInFrames()}),
+ * and might change if the output sink changes after track creation.
+ * @throws IllegalStateException if the track is not initialized or the
+ * track transfer mode is not {@link #MODE_STREAM}.
+ * @throws IllegalArgumentException if startThresholdInFrames is not positive.
+ * @see #getStartThresholdInFrames()
+ */
+ public @IntRange(from = 1) int setStartThresholdInFrames(
+ @IntRange (from = 1) int startThresholdInFrames) {
+ if (mState != STATE_INITIALIZED) {
+ throw new IllegalStateException("AudioTrack is not initialized");
+ }
+ if (mDataLoadMode != MODE_STREAM) {
+ throw new IllegalStateException("AudioTrack must be a streaming track");
+ }
+ if (startThresholdInFrames < 1) {
+ throw new IllegalArgumentException("startThresholdInFrames "
+ + startThresholdInFrames + " must be positive");
+ }
+ return native_setStartThresholdInFrames(startThresholdInFrames);
+ }
+
+ /**
+ * Returns the streaming start threshold of the <code>AudioTrack</code>.
+ * <p> The streaming start threshold is the buffer level that the written audio
+ * data must reach for audio streaming to start after {@link #play()} is called.
+ * When an <code>AudioTrack</code> is created, the streaming start threshold
+ * is the buffer capacity in frames. If the buffer size in frames is reduced
+ * by {@link #setBufferSizeInFrames(int)} to a value smaller than the start threshold
+ * then that value will be used instead for the streaming start threshold.
+ * <p> For compressed streams, the size of a frame is considered to be exactly one byte.
+ *
+ * @return the current start threshold in frames value. This is
+ * an integer between 1 to the buffer capacity
+ * (see {@link #getBufferCapacityInFrames()}),
+ * and might change if the output sink changes after track creation.
+ * @throws IllegalStateException if the track is not initialized or the
+ * track is not {@link #MODE_STREAM}.
+ * @see #setStartThresholdInFrames(int)
+ */
+ public @IntRange (from = 1) int getStartThresholdInFrames() {
+ if (mState != STATE_INITIALIZED) {
+ throw new IllegalStateException("AudioTrack is not initialized");
+ }
+ if (mDataLoadMode != MODE_STREAM) {
+ throw new IllegalStateException("AudioTrack must be a streaming track");
+ }
+ return native_getStartThresholdInFrames();
+ }
+
+ /**
* Returns the frame count of the native <code>AudioTrack</code> buffer.
* @return current size in frames of the <code>AudioTrack</code> buffer.
* @throws IllegalStateException
@@ -4239,6 +4298,8 @@
private native int native_set_dual_mono_mode(int dualMonoMode);
private native int native_get_dual_mono_mode(int[] dualMonoMode);
private native void native_setLogSessionId(@Nullable String logSessionId);
+ private native int native_setStartThresholdInFrames(int startThresholdInFrames);
+ private native int native_getStartThresholdInFrames();
/**
* Sets the audio service Player Interface Id.
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index b073638..1b74367 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -385,8 +385,7 @@
* (acquired via {@link #dequeueInputImage}). In the former case, the Image
* data will be moved to this ImageWriter. Note that the Image properties
* (size, format, strides, etc.) must be the same as the properties of the
- * images dequeued from this ImageWriter, or this method will throw an
- * {@link IllegalArgumentException}. In the latter case, the application has
+ * images dequeued from this ImageWriter. In the latter case, the application has
* filled the input image with data. This method then passes the filled
* buffer to the downstream consumer. In both cases, it's up to the caller
* to ensure that the Image timestamp (in nanoseconds) is correctly set, as
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 548b415..ae64c02 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
-import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
@@ -2937,9 +2936,7 @@
* @return a {@link PlaybackComponent} associated with the session,
* or {@code null} if the session is closed or does not exist.
* @see PlaybackComponent
- * @hide
*/
- @TestApi
@Nullable
public PlaybackComponent getPlaybackComponent(@NonNull byte[] sessionId) {
if (sessionId == null) {
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index f8a642a..4e8a273 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -161,6 +161,10 @@
public static final String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc";
public static final String MIMETYPE_AUDIO_AC4 = "audio/ac4";
public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
+ /** MIME type for MPEG-H Audio single stream */
+ public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1";
+ /** MIME type for MPEG-H Audio single stream, encapsulated in MHAS */
+ public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1";
/**
* MIME type for HEIF still image data encoded in HEVC.
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 87e1e5b..dd08d8a 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -395,6 +395,26 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Source {}
+ /** @hide */
+ @IntDef({
+ AudioSource.DEFAULT,
+ AudioSource.MIC,
+ AudioSource.VOICE_UPLINK,
+ AudioSource.VOICE_DOWNLINK,
+ AudioSource.VOICE_CALL,
+ AudioSource.CAMCORDER,
+ AudioSource.VOICE_RECOGNITION,
+ AudioSource.VOICE_COMMUNICATION,
+ AudioSource.REMOTE_SUBMIX,
+ AudioSource.UNPROCESSED,
+ AudioSource.VOICE_PERFORMANCE,
+ AudioSource.ECHO_REFERENCE,
+ AudioSource.RADIO_TUNER,
+ AudioSource.HOTWORD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SystemSource {}
+
// TODO make AudioSource static (API change) and move this method inside the AudioSource class
/**
* @hide
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b4db305..02fa040 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -21,6 +21,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -89,6 +90,7 @@
private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests =
new CopyOnWriteArrayList<>();
+ // TODO: Specify the fields that are only used (or not used) by system media router.
private final String mClientPackageName;
private final ManagerCallback mManagerCallback;
@@ -132,18 +134,34 @@
}
/**
- * Gets an instance of the media router which controls the app's media routing.
+ * Gets an instance of the system media router which controls the app's media routing.
* Returns {@code null} if the given package name is invalid.
+ * There are several things to note when using the media routers created with this method.
* <p>
- * Note: For media routers created with this method, the discovery preference passed to
- * {@link #registerRouteCallback} will have no effect. The callback will be called accordingly
- * with the client app's discovery preference. Therefore, it is recommended to pass
+ * First of all, the discovery preference passed to {@link #registerRouteCallback}
+ * will have no effect. The callback will be called accordingly with the client app's
+ * discovery preference. Therefore, it is recommended to pass
* {@link RouteDiscoveryPreference#EMPTY} there.
+ * <p>
+ * Also, do not keep/compare the instances of the {@link RoutingController}, since they are
+ * always newly created with the latest session information whenever below methods are called:
+ * <ul>
+ * <li> {@link #getControllers()} </li>
+ * <li> {@link #getController(String)}} </li>
+ * <li> {@link TransferCallback#onTransfer(RoutingController, RoutingController)} </li>
+ * <li> {@link TransferCallback#onStop(RoutingController)} </li>
+ * <li> {@link ControllerCallback#onControllerUpdated(RoutingController)} </li>
+ * </ul>
+ * Therefore, in order to track the current routing status, keep the controller's ID instead,
+ * and use {@link #getController(String)} and {@link #getSystemController()} for
+ * getting controllers.
+ * <p>
+ * Finally, it will have no effect to call {@link #setOnGetControllerHintsListener}.
*
* @param clientPackageName the package name of the app to control
* @hide
*/
- //@SystemApi
+ @SystemApi
@Nullable
public static MediaRouter2 getInstance(@NonNull Context context,
@NonNull String clientPackageName) {
@@ -168,12 +186,40 @@
instance = new MediaRouter2(context, clientPackageName);
sSystemMediaRouter2Map.put(clientPackageName, instance);
// TODO: Remove router instance once it is not needed.
- instance.registerManagerCallback();
+ instance.registerManagerCallbackForSystemRouter();
}
return instance;
}
}
+ /**
+ * Starts scanning remote routes.
+ * Note that calling start/stopScan is applied to all system routers in the same process.
+ *
+ * @see #stopScan()
+ * @hide
+ */
+ @SystemApi
+ public void startScan() {
+ if (isSystemRouter()) {
+ sManager.startScan();
+ }
+ }
+
+ /**
+ * Stops scanning remote routes to reduce resource consumption.
+ * Note that calling start/stopScan is applied to all system routers in the same process.
+ *
+ * @see #startScan()
+ * @hide
+ */
+ @SystemApi
+ public void stopScan() {
+ if (isSystemRouter()) {
+ sManager.stopScan();
+ }
+ }
+
private MediaRouter2(Context appContext) {
mContext = appContext;
mMediaRouterService = IMediaRouterService.Stub.asInterface(
@@ -209,13 +255,19 @@
}
private MediaRouter2(Context context, String clientPackageName) {
+ mContext = context;
mClientPackageName = clientPackageName;
mManagerCallback = new ManagerCallback();
- mContext = context;
- mMediaRouterService = null;
- mPackageName = null;
mHandler = new Handler(Looper.getMainLooper());
- mSystemController = null;
+ mSystemController = new SystemRoutingController(
+ ensureClientPackageNameForSystemSession(sManager.getSystemRoutingSession()));
+ mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+ sManager.getPreferredFeatures(clientPackageName), true).build();
+ updateAllRoutesFromManager();
+ mMediaRouterService = null; // TODO: Make this non-null and check permission.
+
+ // Only used by non-system MediaRouter2.
+ mPackageName = null;
}
/**
@@ -240,7 +292,7 @@
* @see #getInstance(Context, String)
* @hide
*/
- //@SystemApi
+ @SystemApi
@Nullable
public String getClientPackageName() {
return mClientPackageName;
@@ -358,7 +410,8 @@
*
* @hide
*/
- //@SystemApi
+ @SystemApi
+ @NonNull
public List<MediaRoute2Info> getAllRoutes() {
if (isSystemRouter()) {
return sManager.getAllRoutes();
@@ -377,10 +430,6 @@
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
- if (isSystemRouter()) {
- return sManager.getAvailableRoutes(mClientPackageName);
- }
-
synchronized (mLock) {
if (mShouldUpdateRoutes) {
mShouldUpdateRoutes = false;
@@ -474,6 +523,9 @@
* {@code null} for unset.
*/
public void setOnGetControllerHintsListener(@Nullable OnGetControllerHintsListener listener) {
+ if (isSystemRouter()) {
+ return;
+ }
mOnGetControllerHintsListener = listener;
}
@@ -519,7 +571,7 @@
* @param route the route you want to transfer the media to.
* @hide
*/
- //@SystemApi
+ @SystemApi
public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
if (isSystemRouter()) {
sManager.transfer(controller.getRoutingSessionInfo(), route);
@@ -606,6 +658,23 @@
}
/**
+ * Gets a {@link RoutingController} whose ID is equal to the given ID.
+ * Returns {@code null} if there is no matching controller.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public RoutingController getController(@NonNull String id) {
+ Objects.requireNonNull(id, "id must not be null");
+ for (RoutingController controller : getControllers()) {
+ if (TextUtils.equals(id, controller.getId())) {
+ return controller;
+ }
+ }
+ return null;
+ }
+
+ /**
* Gets the list of currently active {@link RoutingController routing controllers} on which
* media can be played.
* <p>
@@ -614,15 +683,26 @@
*/
@NonNull
public List<RoutingController> getControllers() {
- // TODO: Do not create the controller instances every time,
- // Instead, update the list using the sessions' ID and session related callbacks.
+ List<RoutingController> result = new ArrayList<>();
+
if (isSystemRouter()) {
- return sManager.getRoutingSessions(mClientPackageName).stream()
- .map(info -> new RoutingController(info))
- .collect(Collectors.toList());
+ // Unlike non-system MediaRouter2, controller instances cannot be kept,
+ // since the transfer events initiated from other apps will not come through manager.
+ List<RoutingSessionInfo> sessions = sManager.getRoutingSessions(mClientPackageName);
+ for (RoutingSessionInfo session : sessions) {
+ RoutingController controller;
+ if (session.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(
+ ensureClientPackageNameForSystemSession(session));
+ controller = mSystemController;
+ } else {
+ controller = new RoutingController(session);
+ }
+ result.add(controller);
+ }
+ return result;
}
- List<RoutingController> result = new ArrayList<>();
result.add(0, mSystemController);
synchronized (mLock) {
result.addAll(mNonSystemRoutingControllers.values());
@@ -639,9 +719,15 @@
* @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
* @hide
*/
+ @SystemApi
public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
Objects.requireNonNull(route, "route must not be null");
+ if (isSystemRouter()) {
+ sManager.setRouteVolume(route, volume);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -928,12 +1014,30 @@
/**
* Registers {@link MediaRouter2Manager.Callback} for getting events.
+ * Should only used for system media routers.
*/
- private void registerManagerCallback() {
+ private void registerManagerCallbackForSystemRouter() {
// Using direct executor here, since MediaRouter2Manager also posts to the main handler.
sManager.registerCallback(Runnable::run, mManagerCallback);
}
+ /**
+ * Returns a {@link RoutingSessionInfo} which has the client package name.
+ * The client package name is set only when the given sessionInfo doesn't have it.
+ * Should only used for system media routers.
+ */
+ private RoutingSessionInfo ensureClientPackageNameForSystemSession(
+ @NonNull RoutingSessionInfo sessionInfo) {
+ if (!sessionInfo.isSystemSession()
+ || !TextUtils.isEmpty(sessionInfo.getClientPackageName())) {
+ return sessionInfo;
+ }
+
+ return new RoutingSessionInfo.Builder(sessionInfo)
+ .setClientPackageName(mClientPackageName)
+ .build();
+ }
+
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
RouteDiscoveryPreference discoveryRequest) {
return routes.stream()
@@ -941,6 +1045,16 @@
.collect(Collectors.toList());
}
+ private void updateAllRoutesFromManager() {
+ synchronized (mLock) {
+ mRoutes.clear();
+ for (MediaRoute2Info route : sManager.getAllRoutes()) {
+ mRoutes.put(route.getId(), route);
+ }
+ mShouldUpdateRoutes = true;
+ }
+ }
+
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
@@ -971,6 +1085,13 @@
}
}
+ private void notifyPreferredFeaturesChanged(List<String> features) {
+ for (RouteCallbackRecord record: mRouteCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onPreferredFeaturesChanged(features));
+ }
+ }
+
private void notifyTransfer(RoutingController oldController, RoutingController newController) {
for (TransferCallbackRecord record: mTransferCallbackRecords) {
record.mExecutor.execute(
@@ -1024,6 +1145,17 @@
* @param routes the list of routes that have been changed. It's never empty.
*/
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+
+ /**
+ * Called when the client app's preferred features are changed.
+ * When this is called, it is recommended to {@link #getRoutes()} to get the routes
+ * that are currently available to the app.
+ *
+ * @param preferredFeatures the new preferred features set by the application
+ * @hide
+ */
+ @SystemApi
+ public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
}
/**
@@ -1131,6 +1263,11 @@
mState = CONTROLLER_STATE_ACTIVE;
}
+ RoutingController(@NonNull RoutingSessionInfo sessionInfo, int state) {
+ mSessionInfo = sessionInfo;
+ mState = state;
+ }
+
/**
* @return the ID of the controller. It is globally unique.
*/
@@ -1291,6 +1428,11 @@
return;
}
+ if (isSystemRouter()) {
+ sManager.selectRoute(getRoutingSessionInfo(), route);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -1338,6 +1480,11 @@
return;
}
+ if (isSystemRouter()) {
+ sManager.deselectRoute(getRoutingSessionInfo(), route);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -1407,6 +1554,12 @@
Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
return;
}
+
+ if (isSystemRouter()) {
+ sManager.setSessionVolume(getRoutingSessionInfo(), volume);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -1471,6 +1624,11 @@
mState = CONTROLLER_STATE_RELEASED;
}
+ if (isSystemRouter()) {
+ sManager.releaseSession(getRoutingSessionInfo());
+ return;
+ }
+
synchronized (mLock) {
mNonSystemRoutingControllers.remove(getId(), this);
@@ -1539,6 +1697,12 @@
}
private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
+ if (isSystemRouter()) {
+ return getRoutes().stream()
+ .filter(r -> routeIds.contains(r.getId()))
+ .collect(Collectors.toList());
+ }
+
synchronized (mLock) {
return routeIds.stream().map(mRoutes::get)
.filter(Objects::nonNull)
@@ -1722,12 +1886,17 @@
}
}
+ // Note: All methods are run on main thread.
class ManagerCallback implements MediaRouter2Manager.Callback {
@Override
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> filteredRoutes =
- sManager.filterRoutesForPackage(routes, mClientPackageName);
+ updateAllRoutesFromManager();
+
+ List<MediaRoute2Info> filteredRoutes;
+ synchronized (mLock) {
+ filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+ }
if (filteredRoutes.isEmpty()) {
return;
}
@@ -1739,8 +1908,12 @@
@Override
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> filteredRoutes =
- sManager.filterRoutesForPackage(routes, mClientPackageName);
+ updateAllRoutesFromManager();
+
+ List<MediaRoute2Info> filteredRoutes;
+ synchronized (mLock) {
+ filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+ }
if (filteredRoutes.isEmpty()) {
return;
}
@@ -1752,8 +1925,12 @@
@Override
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> filteredRoutes =
- sManager.filterRoutesForPackage(routes, mClientPackageName);
+ updateAllRoutesFromManager();
+
+ List<MediaRoute2Info> filteredRoutes;
+ synchronized (mLock) {
+ filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+ }
if (filteredRoutes.isEmpty()) {
return;
}
@@ -1764,31 +1941,101 @@
}
@Override
- public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
- // TODO: Call ControllerCallback.onControllerUpdated
- }
-
- @Override
public void onTransferred(@NonNull RoutingSessionInfo oldSession,
- @Nullable RoutingSessionInfo newSession) {
- // TODO: Call TransferCallback.onTransfer
+ @NonNull RoutingSessionInfo newSession) {
+ if (!oldSession.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, oldSession.getClientPackageName())) {
+ return;
+ }
+
+ if (!newSession.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, newSession.getClientPackageName())) {
+ return;
+ }
+
+ // For successful in-session transfer, onControllerUpdated() handles it.
+ if (TextUtils.equals(oldSession.getId(), newSession.getId())) {
+ return;
+ }
+
+
+ RoutingController oldController;
+ if (oldSession.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(
+ ensureClientPackageNameForSystemSession(oldSession));
+ oldController = mSystemController;
+ } else {
+ oldController = new RoutingController(oldSession);
+ }
+
+ RoutingController newController;
+ if (newSession.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(
+ ensureClientPackageNameForSystemSession(newSession));
+ newController = mSystemController;
+ } else {
+ newController = new RoutingController(newSession);
+ }
+
+ notifyTransfer(oldController, newController);
}
@Override
public void onTransferFailed(@NonNull RoutingSessionInfo session,
@NonNull MediaRoute2Info route) {
- // TODO: Call TransferCallback.onTransferFailure
+ if (!session.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+ return;
+ }
+ notifyTransferFailure(route);
+ }
+
+ @Override
+ public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
+ if (!session.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+ return;
+ }
+
+ RoutingController controller;
+ if (session.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(
+ ensureClientPackageNameForSystemSession(session));
+ controller = mSystemController;
+ } else {
+ controller = new RoutingController(session);
+ }
+ notifyControllerUpdated(controller);
}
@Override
public void onSessionReleased(@NonNull RoutingSessionInfo session) {
- // TODO: Call TransferCallback.onStop()
+ if (session.isSystemSession()) {
+ Log.e(TAG, "onSessionReleased: Called on system session. Ignoring.");
+ return;
+ }
+
+ if (!TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+ return;
+ }
+
+ notifyStop(new RoutingController(session, RoutingController.CONTROLLER_STATE_RELEASED));
}
@Override
public void onPreferredFeaturesChanged(@NonNull String packageName,
@NonNull List<String> preferredFeatures) {
- // Does nothing.
+ if (!TextUtils.equals(mClientPackageName, packageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+ preferredFeatures, true).build();
+ }
+
+ updateAllRoutesFromManager();
+ notifyPreferredFeaturesChanged(preferredFeatures);
}
@Override
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index ca619d4..6fefbe1 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -148,7 +148,7 @@
/**
* Starts scanning remote routes.
- * @see #stopScan(String)
+ * @see #stopScan()
*/
public void startScan() {
Client client = getOrCreateClient();
@@ -163,7 +163,7 @@
/**
* Stops scanning remote routes to reduce resource consumption.
- * @see #startScan(String)
+ * @see #startScan()
*/
public void stopScan() {
Client client = getOrCreateClient();
@@ -237,6 +237,20 @@
}
/**
+ * Returns the preferred features of the specified package name.
+ */
+ @NonNull
+ public List<String> getPreferredFeatures(@NonNull String packageName) {
+ Objects.requireNonNull(packageName, "packageName must not be null");
+
+ List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+ if (preferredFeatures == null) {
+ preferredFeatures = Collections.emptyList();
+ }
+ return preferredFeatures;
+ }
+
+ /**
* Returns a list of routes which are related to the given package name in the given route list.
*/
@NonNull
@@ -788,8 +802,8 @@
* Requests releasing a session.
* <p>
* If a session is released, any operation on the session will be ignored.
- * {@link Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)} with {@code null}
- * session will be called when the session is released.
+ * {@link Callback#onSessionReleased(RoutingSessionInfo)} will be called
+ * when the session is released.
* </p>
*
* @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
@@ -945,10 +959,10 @@
* Called when media is transferred.
*
* @param oldSession the previous session
- * @param newSession the new session or {@code null} if the session is released.
+ * @param newSession the new session
*/
default void onTransferred(@NonNull RoutingSessionInfo oldSession,
- @Nullable RoutingSessionInfo newSession) { }
+ @NonNull RoutingSessionInfo newSession) { }
/**
* Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
diff --git a/media/java/android/media/musicrecognition/IMusicRecognitionAttributionTagCallback.aidl b/media/java/android/media/musicrecognition/IMusicRecognitionAttributionTagCallback.aidl
new file mode 100644
index 0000000..ceef73c
--- /dev/null
+++ b/media/java/android/media/musicrecognition/IMusicRecognitionAttributionTagCallback.aidl
@@ -0,0 +1,10 @@
+package android.media.musicrecognition;
+
+/**
+ * Interface from {@link MusicRecognitionService} to system to pass attribution tag.
+ *
+ * @hide
+ */
+oneway interface IMusicRecognitionAttributionTagCallback {
+ void onAttributionTag(in String attributionTag);
+}
\ No newline at end of file
diff --git a/media/java/android/media/musicrecognition/IMusicRecognitionService.aidl b/media/java/android/media/musicrecognition/IMusicRecognitionService.aidl
index 26543ed..c970161 100644
--- a/media/java/android/media/musicrecognition/IMusicRecognitionService.aidl
+++ b/media/java/android/media/musicrecognition/IMusicRecognitionService.aidl
@@ -4,6 +4,7 @@
import android.os.ParcelFileDescriptor;
import android.os.IBinder;
import android.media.musicrecognition.IMusicRecognitionServiceCallback;
+import android.media.musicrecognition.IMusicRecognitionAttributionTagCallback;
/**
* Interface from the system to a {@link MusicRecognitionService}.
@@ -15,4 +16,6 @@
in ParcelFileDescriptor fd,
in AudioFormat audioFormat,
in IMusicRecognitionServiceCallback callback);
+
+ void getAttributionTag(in IMusicRecognitionAttributionTagCallback callback);
}
\ No newline at end of file
diff --git a/media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl b/media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl
index 15215c4..10a6554 100644
--- a/media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl
+++ b/media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl
@@ -4,7 +4,7 @@
import android.media.MediaMetadata;
/**
- * Interface from a {@MusicRecognitionService} the system.
+ * Interface from a {@MusicRecognitionService} to the system.
*
* @hide
*/
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionService.java b/media/java/android/media/musicrecognition/MusicRecognitionService.java
index 04b4c39b..385aff0 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionService.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionService.java
@@ -90,7 +90,7 @@
try {
callback.onRecognitionSucceeded(result, extras);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
@@ -99,11 +99,18 @@
try {
callback.onRecognitionFailed(failureCode);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
}));
}
+
+ @Override
+ public void getAttributionTag(
+ IMusicRecognitionAttributionTagCallback callback) throws RemoteException {
+ String tag = MusicRecognitionService.this.getAttributionTag();
+ callback.onAttributionTag(tag);
+ }
};
@Override
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 1870a93..7f5dd5d 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2260,6 +2260,8 @@
c2_status_t c2err = sGrallocAlloc->priorGraphicAllocation(handle, &alloc);
if (c2err != C2_OK) {
ALOGW("Failed to wrap AHardwareBuffer into C2GraphicAllocation");
+ native_handle_close(handle);
+ native_handle_delete(handle);
throwExceptionAsNecessary(env, BAD_VALUE);
return;
}
diff --git a/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp
index f8b4bdb..50e0d33 100644
--- a/media/jni/soundpool/Sound.cpp
+++ b/media/jni/soundpool/Sound.cpp
@@ -99,8 +99,8 @@
__func__);
break;
}
- int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
- ALOGV("%s: read %d", __func__, sampleSize);
+ ssize_t sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
+ ALOGV("%s: read %zd", __func__, sampleSize);
if (sampleSize < 0) {
sampleSize = 0;
sawInputEOS = true;
@@ -124,8 +124,8 @@
}
AMediaCodecBufferInfo info;
- const int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
- ALOGV("%s: dequeueoutput returned: %d", __func__, status);
+ const ssize_t status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
+ ALOGV("%s: dequeueoutput returned: %zd", __func__, status);
if (status >= 0) {
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
ALOGV("%s: output EOS", __func__);
@@ -167,10 +167,10 @@
} else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
ALOGV("%s: no output buffer right now", __func__);
} else if (status <= AMEDIA_ERROR_BASE) {
- ALOGE("%s: decode error: %d", __func__, status);
+ ALOGE("%s: decode error: %zd", __func__, status);
break;
} else {
- ALOGV("%s: unexpected info code: %d", __func__, status);
+ ALOGV("%s: unexpected info code: %zd", __func__, status);
}
}
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index abb0f12..95fe000 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -320,7 +320,8 @@
// audio track while the new one is being started and avoids processing them with
// wrong audio audio buffer size (mAudioBufferSize)
auto toggle = mToggle ^ 1;
- void* userData = (void*)((uintptr_t)this | toggle);
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ void* userData = reinterpret_cast<void*>((uintptr_t)this | toggle);
audio_channel_mask_t soundChannelMask = sound->getChannelMask();
// When sound contains a valid channel mask, use it as is.
// Otherwise, use stream count to calculate channel mask.
@@ -391,6 +392,7 @@
void Stream::staticCallback(int event, void* user, void* info)
{
const auto userAsInt = (uintptr_t)user;
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
auto stream = reinterpret_cast<Stream*>(userAsInt & ~1);
stream->callback(event, info, int(userAsInt & 1), 0 /* tries */);
}
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 502ee00..309d71c 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -43,6 +43,14 @@
// Amount of time for a StreamManager thread to wait before closing.
static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
+// Debug flag:
+// kForceLockStreamManagerStop is set to true to force lock the StreamManager
+// worker thread during stop. This limits concurrency of Stream processing.
+// Normally we lock the StreamManager worker thread during stop ONLY
+// for SoundPools configured with a single Stream.
+//
+static constexpr bool kForceLockStreamManagerStop = false;
+
////////////
StreamMap::StreamMap(int32_t streams) {
@@ -103,6 +111,7 @@
: StreamMap(streams)
, mAttributes(*attributes)
, mOpPackageName(std::move(opPackageName))
+ , mLockStreamManagerStop(streams == 1 || kForceLockStreamManagerStop)
{
ALOGV("%s(%d, %zu, ...)", __func__, streams, threads);
forEach([this](Stream *stream) {
@@ -113,7 +122,8 @@
});
mThreadPool = std::make_unique<ThreadPool>(
- std::min(threads, (size_t)std::thread::hardware_concurrency()),
+ std::min((size_t)streams, // do not make more threads than streams to play
+ std::min(threads, (size_t)std::thread::hardware_concurrency())),
"SoundPool_");
}
@@ -330,7 +340,7 @@
// streams on mProcessingStreams are undergoing processing by the StreamManager thread
// and do not participate in normal stream migration.
- return found;
+ return (ssize_t)found;
}
void StreamManager::addToRestartQueue_l(Stream *stream) {
@@ -348,14 +358,14 @@
void StreamManager::run(int32_t id)
{
ALOGV("%s(%d) entering", __func__, id);
- int64_t waitTimeNs = kWaitTimeBeforeCloseNs;
+ int64_t waitTimeNs = 0; // on thread start, mRestartStreams can be non-empty.
std::unique_lock lock(mStreamManagerLock);
while (!mQuit) {
- if (mRestartStreams.empty()) { // on thread start, mRestartStreams can be non-empty.
+ if (waitTimeNs > 0) {
mStreamManagerCondition.wait_for(
lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs));
}
- ALOGV("%s(%d) awake", __func__, id);
+ ALOGV("%s(%d) awake lock waitTimeNs:%lld", __func__, id, (long long)waitTimeNs);
sanityCheckQueue_l();
@@ -375,12 +385,12 @@
}
mRestartStreams.erase(it);
mProcessingStreams.emplace(stream);
- lock.unlock();
+ if (!mLockStreamManagerStop) lock.unlock();
stream->stop();
ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
if (Stream* nextStream = stream->playPairStream()) {
ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
- lock.lock();
+ if (!mLockStreamManagerStop) lock.lock();
if (nextStream->getStopTimeNs() > 0) {
// the next stream was stopped before we can move it to the active queue.
ALOGV("%s(%d) stopping started streamID:%d",
@@ -390,7 +400,7 @@
addToActiveQueue_l(nextStream);
}
} else {
- lock.lock();
+ if (!mLockStreamManagerStop) lock.lock();
mAvailableStreams.insert(stream);
}
mProcessingStreams.erase(stream);
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
index 81ac69e..85b468c 100644
--- a/media/jni/soundpool/StreamManager.h
+++ b/media/jni/soundpool/StreamManager.h
@@ -437,6 +437,14 @@
void sanityCheckQueue_l() const REQUIRES(mStreamManagerLock);
const audio_attributes_t mAttributes;
+ const std::string mOpPackageName;
+
+ // For legacy compatibility, we lock the stream manager on stop when
+ // there is only one stream. This allows a play to be called immediately
+ // after stopping, otherwise it is possible that the play might be discarded
+ // (returns 0) because that stream may be in the worker thread call to stop.
+ const bool mLockStreamManagerStop;
+
std::unique_ptr<ThreadPool> mThreadPool; // locked internally
// mStreamManagerLock is used to lock access for transitions between the
@@ -477,8 +485,6 @@
// The paired stream may be active or restarting.
// No particular order.
std::unordered_set<Stream*> mProcessingStreams GUARDED_BY(mStreamManagerLock);
-
- const std::string mOpPackageName;
};
} // namespace android::soundpool
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 357cc63..a66d99f 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -34,7 +34,8 @@
jclass mSoundPoolClass;
} fields;
static inline SoundPool* MusterSoundPool(JNIEnv *env, jobject thiz) {
- return (SoundPool*)env->GetLongField(thiz, fields.mNativeContext);
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ return reinterpret_cast<SoundPool*>(env->GetLongField(thiz, fields.mNativeContext));
}
static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
struct audio_attributes_fields_t {
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 7793180..0476216 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -314,6 +314,11 @@
}
Result DvrClient::close() {
+ if (mDvrMQEventFlag != NULL) {
+ EventFlag::deleteEventFlag(&mDvrMQEventFlag);
+ }
+ mDvrMQ = NULL;
+
if (mTunerDvr != NULL) {
Status s = mTunerDvr->close();
mTunerDvr = NULL;
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index f31d465..8846e4d6 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -259,6 +259,11 @@
}
Result FilterClient::close() {
+ if (mFilterMQEventFlag != NULL) {
+ EventFlag::deleteEventFlag(&mFilterMQEventFlag);
+ }
+ mFilterMQ = NULL;
+
if (mTunerFilter != NULL) {
Status s = mTunerFilter->close();
closeAvSharedMemory();
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 6fab9e4..10d68ba 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -50,7 +50,7 @@
import android.widget.TextView;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.TrafficStatsConstants;
+import com.android.net.module.util.NetworkStackConstants;
import java.io.IOException;
import java.lang.reflect.Field;
@@ -86,7 +86,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCm = ConnectivityManager.from(this);
+ mCm = getSystemService(ConnectivityManager.class);
mUrl = getUrlForCaptivePortal();
if (mUrl == null) {
done(false);
@@ -161,7 +161,6 @@
if (network != null) {
network = network.getPrivateDnsBypassingCopy();
mCm.bindProcessToNetwork(network);
- mCm.setProcessDefaultNetworkForHostResolution(network);
}
mNetwork = network;
}
@@ -239,7 +238,7 @@
HttpURLConnection urlConnection = null;
int httpResponseCode = 500;
int oldTag = TrafficStats.getAndSetThreadStatsTag(
- TrafficStatsConstants.TAG_SYSTEM_PROBE);
+ NetworkStackConstants.TAG_SYSTEM_PROBE);
try {
urlConnection = (HttpURLConnection) mNetwork.openConnection(
new URL(mCm.getCaptivePortalServerUrl()));
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
index 78a02d7..43ca739 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -49,7 +49,7 @@
case PROVISION_OBSERVER_REEVALUATION_JOB_ID:
if (isProvisioned(this)) {
Log.d(TAG, "device provisioned, force network re-evaluation");
- final ConnectivityManager connMgr = ConnectivityManager.from(this);
+ final ConnectivityManager connMgr = getSystemService(ConnectivityManager.class);
Network[] info = connMgr.getAllNetworks();
for (Network nw : info) {
final NetworkCapabilities nc = connMgr.getNetworkCapabilities(nw);
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index ed120b5..36e59ba 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Kies \'n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om deur <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> bestuur te word"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"toestel"</string>
<string name="profile_name_watch" msgid="576290739483672360">"horlosie"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Stel <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> om jou <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> te bestuur"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Hierdie program is nodig om jou <xliff:g id="PROFILE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Laat toe"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 76f68e7..0fefa8a 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"በ<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> የእርስዎን <xliff:g id="DEVICE_NAME">%2$s</xliff:g> - <strong></strong> ለማስተዳደር"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"የእርስዎን <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ለማስተዳደር ይህ መተግበሪያ ያስፈልጋል <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"ፍቀድ"</string>
+ <string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 92783a5..ca3b9f3 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديره تطبيق <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"جهاز"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ساعة"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"ضبط <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> لإدارة <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"هذا التطبيق مطلوب لإدارة <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"السماح"</string>
+ <string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index b07cad6..8e4a202 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> tərəfindən idarə ediləcək <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
<string name="profile_name_watch" msgid="576290739483672360">"izləyin"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> cihazınızı idarə etmək üçün <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ayarlayın"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Bu tətbiq <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilinizi idarə etmək üçün lazımdır. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"İcazə verin"</string>
+ <string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index edeaa77..ef19c48 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
<string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Podesite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> tako da upravlja uređajem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ova aplikacija je potrebna za upravljanje profilom <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 9410d68..4366a08 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Выберыце прыладу (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"прылада"</string>
<string name="profile_name_watch" msgid="576290739483672360">"гадзіннік"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Дазвольце праграме <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> кіраваць прыладай <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Гэта праграма неабходная для кіравання профілем \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\". <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Дазволіць"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 4457dbd..77f3413 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Изберете устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), което да се управлява от <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
<string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Задайте <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> да управлява устройството ви <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Това приложение е необходимо за управление на <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Разрешаване"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 4aa7e74..5fa4781 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন যেটি <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ম্যানেজ করবে"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string>
<string name="profile_name_watch" msgid="576290739483672360">"দেখুন"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"আপনার <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ম্যানেজ করার জন্য <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> সেট করুন"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"আপনার <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ম্যানেজ করতে এই অ্যাপটি প্রয়োজন। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিন"</string>
+ <string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 8ffa327..18153b5 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Odaberite uređaj <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
<string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Postavite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> da upravlja vašim uređajem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ova aplikacija je potrebna za upravljanje profilom: <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index c9c186e..bbd1125 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> perquè el gestioni <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string>
<string name="profile_name_watch" msgid="576290739483672360">"rellotge"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Defineix que <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> gestioni el dispositiu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Aquesta aplicació es necessita per gestionar <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permet"</string>
+ <string name="consent_no" msgid="2640796915611404382">"No permetis"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index b2ac32d..c5fed33 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete spravovat pomocí aplikace <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"zařízení"</string>
<string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Nastavit aplikaci <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pro správu vašeho zařízení: <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Tato aplikace je nutná pro správu profilu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Povolit"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index bc67948..21280d5 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Vælg den enhed (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), som skal administreres af <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"enhed"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ur"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Indstil <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> til at administrere <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Du skal bruge denne app for at administrere dit <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Tillad"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index fed516f..162d6fc 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για διαχείριση από την εφαρμογή <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ρολόι"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Ορίστε την εφαρμογή <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> για διαχείριση της συσκευής <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Αυτή η εφαρμογή είναι απαραίτητη για τη διαχείριση του προφίλ σας <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Να επιτρέπεται"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index 4fd057f..de89b39 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index 4fd057f..de89b39 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index 4fd057f..de89b39 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index 4fd057f..de89b39 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Set <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index 0e3902c..10c2012 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -22,10 +22,7 @@
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
<!-- no translation found for confirmation_title (814973816731238955) -->
<skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Don’t allow"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 6b903c6..8dae51c 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> lo administre"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
<string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Configura <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para administrar el dispositivo <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Esta app es necesaria para administrar tu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
+ <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index 0a2906a..a307b61 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para gestionarlo con <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
<string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Configura <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para que gestione tu dispositivo <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Se necesita esta aplicación para gestionar tu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
+ <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 4958a17..2fb2bc4 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Valige seade <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mida haldab rakendus <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"seade"</string>
<string name="profile_name_watch" msgid="576290739483672360">"käekell"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Määrake rakendus <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> haldama teie seadet <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Seda rakendust on vaja teie profiili <xliff:g id="PROFILE_NAME">%1$s</xliff:g> haldamiseks. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Luba"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index 2a61fd5..646f844 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Aukeratu <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string>
<string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Konfiguratu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> kudea dezan"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> kudeatzeko beharrezkoa da aplikazioa. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index 8d10230..69b9647 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای مدیریت کردن با <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"تنظیم <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> برای مدیریت <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"این برنامه برای مدیریت <xliff:g id="PROFILE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"مجاز"</string>
+ <string name="consent_no" msgid="2640796915611404382">"مجاز نیست"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index cfa03af..b174b8a 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, jota <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> hallinnoi"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"laite"</string>
<string name="profile_name_watch" msgid="576290739483672360">"kello"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Valitse <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> laitteen (<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>) ylläpitäjäksi"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Profiilin (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Salli"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Älä salli"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index d3cfc29..696598d 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Choisissez un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré par <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
<string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Utiliser <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pour gérer votre <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Cette application est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index d82e390..787794b 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Sélectionner le/la <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
<string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Définir <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pour gérer votre <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Cette appli est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index 2a85398..a3efc4c 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Escolle un perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) para que o xestione a aplicación <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
<string name="profile_name_watch" msgid="576290739483672360">"reloxo"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Configura <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para que xestione o teu dispositivo <strong>(<xliff:g id="DEVICE_NAME">%2$s</xliff:g>)</strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Esta aplicación é necesaria para xestionar o teu perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>). <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Non permitir"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index 6f52403..1b0fe1a 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"તમારું <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> મેનેજ કરવા માટે, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> સેટ કરો"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"તમારી <xliff:g id="PROFILE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે આ ઍપ જરૂરી છે. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"મંજૂરી આપો"</string>
+ <string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index 9af9992..dee30f2 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें, ताकि उसे <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> की मदद से प्रबंधित किया जा सके"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string>
<string name="profile_name_watch" msgid="576290739483672360">"स्मार्टवॉच"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> को मैनेज करने के लिए, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> सेट अप करें"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"यह ऐप्लिकेशन, <xliff:g id="PROFILE_NAME">%1$s</xliff:g> मैनेज करने के लिए ज़रूरी है. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"अनुमति दें"</string>
+ <string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index fe30ec4..bc36521 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
<string name="profile_name_watch" msgid="576290739483672360">"satom"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Postavite aplikaciju <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> da upravlja vašim uređajem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ta je aplikacija potrebna za upravljanje vašim profilom <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Dopusti"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index 370d4df..ef50544 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"A(z) <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> alkalmazással kezelni kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiválasztása"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"eszköz"</string>
<string name="profile_name_watch" msgid="576290739483672360">"óra"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"A(z) <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> alkalmazás beállítva a(z) <xliff:g id="DEVICE_NAME">%2$s</xliff:g> (<strong></strong>) kezelésére"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Szükség van erre az alkalmazásra a következő kezeléséhez: <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Engedélyezés"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index fee55d0..103361a 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը, որը պետք է կառավարվի <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> հավելվածի կողմից"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"սարք"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ժամացույց"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Ընտրեք <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> հավելվածը որպես <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> սարքի կառավարիչ"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="PROFILE_NAME">%1$s</xliff:g> պրոֆիլը կառավարելու համար։ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Թույլատրել"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index 498bf2c..225b276 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk dikelola oleh <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"perangkat"</string>
<string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Tetapkan <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> untuk mengelola <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Aplikasi ini diperlukan untuk mengelola <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Izinkan"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index bd12658..7855a2a 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Velja <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sem <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> á að stjórna"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"tæki"</string>
<string name="profile_name_watch" msgid="576290739483672360">"úr"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Veita <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> stjórn á <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Þetta forrit er nauðsynlegt til að hafa umsjón með <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Leyfa"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index 40d4320..9e503e1 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> che sia gestito da <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
<string name="profile_name_watch" msgid="576290739483672360">"orologio"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Configura l\'app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> affinché gestisca <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Questa app è necessaria per gestire il tuo <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Consenti"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index 807cdd4..d293fb0 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"בחירה של <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string>
<string name="profile_name_watch" msgid="576290739483672360">"שעון"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"הגדרה של <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> לניהול <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"יש אישור"</string>
+ <string name="consent_no" msgid="2640796915611404382">"אין אישור"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index 92022be..ac8e2d2 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> の管理対象となる<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> を管理する <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> の設定"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"このアプリは<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"許可"</string>
+ <string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index 64a79b4..8b7680e 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, რომელიც უნდა მართოს <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>-მა"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string>
<string name="profile_name_watch" msgid="576290739483672360">"საათი"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"დააყენეთ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, რათა მართოთ თქვენი <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"ეს აპი საჭიროა თქვენი <xliff:g id="PROFILE_NAME">%1$s</xliff:g>-ს სამართავად. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"დაშვება"</string>
+ <string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index edd5c0e..1ad854e 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> арқылы басқарылатын <xliff:g id="PROFILE_NAME">%1$s</xliff:g> құрылғысын таңдаңыз"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string>
<string name="profile_name_watch" msgid="576290739483672360">"сағат"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> қолданбасына <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> құрылғысын басқаруға рұқсат беріңіз"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Бұл қолданба <xliff:g id="PROFILE_NAME">%1$s</xliff:g> профиліңізді басқару үшін қажет. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Рұқсат беру"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 36c02de..7231c2d 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីឱ្យស្ថិតក្រោមការគ្រប់គ្រងរបស់ <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string>
<string name="profile_name_watch" msgid="576290739483672360">"នាឡិកា"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"កំណត់ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ដើម្បីគ្រប់គ្រង <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> របស់អ្នក"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="PROFILE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"អនុញ្ញាត"</string>
+ <string name="consent_no" msgid="2640796915611404382">"កុំអនុញ្ញាត"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index 56c1557..6f75328 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"ನಿಮ್ಮ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ಅನ್ನು ನಿರ್ವಹಿಸಲು <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"ನಿಮ್ಮ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. ಅನ್ನು ನಿರ್ವಹಿಸಲು ಈ ಆ್ಯಪ್ನ ಅಗತ್ಯವಿದೆ. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"ಅನುಮತಿಸಿ"</string>
+ <string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index 79c36dd..5b171ea 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>에서 관리할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g>을(를) 선택"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"기기"</string>
<string name="profile_name_watch" msgid="576290739483672360">"시계"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> 앱이 <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> 기기를 관리하도록 설정"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"이 앱은 <xliff:g id="PROFILE_NAME">%1$s</xliff:g> 프로필을 관리하는 데 필요합니다. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"허용"</string>
+ <string name="consent_no" msgid="2640796915611404382">"허용 안함"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index 8a90b3d..f7c896b 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> тарабынан башкарылсын"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string>
<string name="profile_name_watch" msgid="576290739483672360">"саат"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> түзмөгүңүздү башкаруу үчүн <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> колдонмосун жөндөңүз"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Бул колдонмо <xliff:g id="PROFILE_NAME">%1$s</xliff:g> профилиңизди башкаруу үчүн керек. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Уруксат берүү"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index a6564b3..8ad881f 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ເພື່ອໃຫ້ຖືກຈັດການໂດຍ <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ໂມງ"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"ຕັ້ງຄ່າ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ເພື່ອຈັດການ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ຂອງທ່ານ"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"ຕ້ອງໃຊ້ແອັບນີ້ເພື່ອຈັດການ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"ອະນຸຍາດ"</string>
+ <string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index 382f1cf..c40d4a7 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, kurį valdys <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> (pasirinkite)"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string>
<string name="profile_name_watch" msgid="576290739483672360">"laikrodis"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> tvarkymo naudojant <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> nustatymas"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ši programa reikalinga norint tvarkyti jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Leisti"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index 8d70bf7..c842ee1 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) izvēle, ko pārvaldīt lietotnē <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ierīce"</string>
<string name="profile_name_watch" msgid="576290739483672360">"pulkstenis"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Lietotnes <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> iestatīšana ierīces <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> pārvaldībai"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Šī lietotne ir nepieciešama jūsu profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) pārvaldībai. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Atļaut"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index 5322e98..fdc366e 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> со којшто ќе управува <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"уред"</string>
<string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Поставете <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> за да управувате со вашиот <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Апликацијава е потребна за управување со вашиот <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index a9262c7..ae445a3 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string>
<string name="profile_name_watch" msgid="576290739483672360">"വാച്ച്"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"നിങ്ങളുടെ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> മാനേജ് ചെയ്യുന്നതിന് <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> സജ്ജീകരിക്കുക"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"നിങ്ങളുടെ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ഈ ആപ്പ് ആവശ്യമാണ്. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"അനുവദിക്കുക"</string>
+ <string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 3032864..01850e7 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>-н удирдах<xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г сонгоно уу"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string>
<string name="profile_name_watch" msgid="576290739483672360">"цаг"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Та <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>-г удирдахын тулд <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>-г тохируулна уу"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Энэ апп таны <xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Зөвшөөрөх"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index 01dae7d..7eea9bf 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string>
<string name="profile_name_watch" msgid="576290739483672360">"पाहा"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"तुमचे <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> व्यवस्थापित करण्यासाठी <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> सेट करा"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"तुमची <xliff:g id="PROFILE_NAME">%1$s</xliff:g> प्रोफाइल व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"अनुमती द्या"</string>
+ <string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index 4e0f58b..e43a27f 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string>
<string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Tetapkan <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> untuk mengurus <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> anda"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Apl ini diperlukan untuk menguruskan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> anda. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Benarkan"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 050c8ce..1bd3a1d 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"စက်"</string>
<string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"သင်၏ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ကို စီမံခန့်ခွဲရန် <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ကို သတ်မှတ်ပါ"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"သင်၏ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်ကိုလိုအပ်သည်။ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"ခွင့်ပြုရန်"</string>
+ <string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index a8e2203..e4d247b 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal administreres av <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
<string name="profile_name_watch" msgid="576290739483672360">"klokke"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Velg at <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> skal administrere <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Denne appen kreves for å administrere <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Tillat"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index e7e0390..9dc23e7 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"apparaat"</string>
<string name="profile_name_watch" msgid="576290739483672360">"horloge"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> instellen om je <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> te beheren"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Deze app is vereist om je <xliff:g id="PROFILE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index 1f516fa..4cfe057 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"ଆପଣଙ୍କ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>କୁ ପରିଚାଳନା କରିବା ପାଇଁ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>କୁ ସେଟ୍ କରନ୍ତୁ"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"ଆପଣଙ୍କ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଏହି ଆପ୍ ଆବଶ୍ୟକ। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+ <string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 7adf064..3dbd2f7 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Wybierz profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, którym ma zarządzać aplikacja <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
<string name="profile_name_watch" msgid="576290739483672360">"zegarek"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Skonfiguruj aplikację <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, aby zarządzała urządzeniem <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ta aplikacja jest niezbędna do zarządzania profilem <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index b5ddc6d..91a9fa4 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Configure o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para gerenciar seu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Esse app é necessário para gerenciar seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index c06ac7d..5ad9389 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerido pela app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Defina a app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para gerir o seu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Esta app é necessária para gerir o seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index b5ddc6d..91a9fa4 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Configure o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para gerenciar seu <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Esse app é necessário para gerenciar seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index 437e8dc..15f5393 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Alegeți un profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pe care să îl gestioneze <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ceas"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Setați <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> pentru a vă gestiona dispozitivul <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Această aplicație este necesară pentru a gestiona <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Permiteți"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Nu permiteți"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index d087959..2f79416 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Выберите устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), которым будет управлять приложение <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
<string name="profile_name_watch" msgid="576290739483672360">"часы"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Разрешите приложению <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> управлять устройством <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Это приложение необходимо для управления вашим профилем (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>). <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Разрешить"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Запретить"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index 365b6bf..d108a25 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> මගින් කළමනාකරණය කරනු ලැබීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ඔරලෝසුව"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ඔබගේ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> කළමනාකරණය කිරීමට සකසන්න"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"මෙම යෙදුමට ඔබගේ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> කළමනාකරණය කිරීමට අවශ්යයි. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"ඉඩ දෙන්න"</string>
+ <string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index d5c099a..d53728b 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý bude spravovať aplikácia <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string>
<string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Nastavte aplikáciu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, aby spravovala zariadenie <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Táto aplikácia sa vyžaduje na správu profilu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Povoliť"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index d855db1..2849210 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Izbira naprave <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ki jo bo upravljala aplikacija <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"naprava"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ura"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Nastavitev aplikacije <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> za upravljanje naprave <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ta aplikacija je potrebna za upravljanje profila »<xliff:g id="PROFILE_NAME">%1$s</xliff:g>«. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Dovoli"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index 7335446..a57835a 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Zgjidh një profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> që do të menaxhohet nga <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Cakto <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> që të menaxhojë pajisjen tënde <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ky aplikacion nevojitet për të menaxhuar profilin tënd <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Lejo"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index f3f65e1..0b01cbc 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Одаберите профил <xliff:g id="PROFILE_NAME">%1$s</xliff:g> којим ће управљати апликација <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"уређај"</string>
<string name="profile_name_watch" msgid="576290739483672360">"сат"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Подесите апликацију <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> тако да управља уређајем <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Ова апликација је потребна за управљање профилом <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index 5741668..f6dcec9 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för hantering av <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
<string name="profile_name_watch" msgid="576290739483672360">"klocka"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Konfigurera <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> för att hantera din <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Appen behövs för att hantera <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Tillåt"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 357eb4a..0999c5b 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
<string name="profile_name_watch" msgid="576290739483672360">"saa"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Weka <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ili udhibiti <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> yako"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Programu hii inahitajika ili udhibiti wasifu wako wa <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index 651d9e2..884d57f 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> ஆப்ஸ் நிர்வகிக்கக்கூடிய <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ஐத் தேர்ந்தெடுங்கள்"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string>
<string name="profile_name_watch" msgid="576290739483672360">"வாட்ச்"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"உங்கள் <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> சாதனத்தை நிர்வகிக்க <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ஆப்ஸை அமையுங்கள்"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"உங்கள் <xliff:g id="PROFILE_NAME">%1$s</xliff:g> சுயவிவரத்தை நிர்வகிக்க இந்த ஆப்ஸ் தேவைப்படுகிறது. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"அனுமதி"</string>
+ <string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index 557515a..58ebda6 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะให้มีการจัดการโดย <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string>
<string name="profile_name_watch" msgid="576290739483672360">"นาฬิกา"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"ตั้งค่าให้ <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> จัดการ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ของคุณ"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ของคุณ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"อนุญาต"</string>
+ <string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 6cbf2e8..1a83284 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para pamahalaan ng <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relo"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Itakda ang <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> para pamahalaan ang iyong <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Kinakailangan ang app na ito para pamahalaan ang iyong <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Payagan"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index bc1ab31..b89e874 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> tarafından yönetilecek bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
<string name="profile_name_watch" msgid="576290739483672360">"saat"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> cihazınızı yönetmek için <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> uygulamasını ayarlayın"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Bu uygulama, <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilinizin yönetilmesi için gereklidir. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"İzin ver"</string>
+ <string name="consent_no" msgid="2640796915611404382">"İzin verme"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 33477c8..2ae852c 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"пристрій"</string>
<string name="profile_name_watch" msgid="576290739483672360">"годинник"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Налаштуйте додаток <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>, щоб керувати пристроєм <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Цей додаток потрібен, щоб керувати профілем \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\". <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Дозволити"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index b16db73..5681118 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string>
<string name="profile_name_watch" msgid="576290739483672360">"soat"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"<strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> qurilmasini boshqarish uchun <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ilovasini sozlang"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Bu ilova <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Ruxsat"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index 75d2de37..3bbdb57 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sẽ do <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> quản lý"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string>
<string name="profile_name_watch" msgid="576290739483672360">"đồng hồ"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Đặt <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> để quản lý <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> của bạn"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"Cần có ứng dụng này để quản lý <xliff:g id="PROFILE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Cho phép"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index 576f3f4..be29925b 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"選擇由 <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> 管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
<string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"設定 <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> 來管理您的 <xliff:g id="DEVICE_NAME">%2$s</xliff:g> - <strong></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"必須使用此應用程式,才能管理<xliff:g id="PROFILE_NAME">%1$s</xliff:g>。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
+ <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 8a50658..8044869 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」<strong></strong>管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
<string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」<strong></strong>管理你的「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」<strong></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"你必須使用這個應用程式,才能管理<xliff:g id="PROFILE_NAME">%1$s</xliff:g>。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
+ <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index fc3f19d..3ed177c 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -20,12 +20,8 @@
<string name="chooser_title" msgid="2262294130493605839">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ezophathwa yi-<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string>
<string name="profile_name_watch" msgid="576290739483672360">"buka"</string>
- <!-- no translation found for confirmation_title (814973816731238955) -->
- <skip />
- <!-- no translation found for profile_summary (2059360676631420073) -->
- <skip />
- <!-- no translation found for consent_yes (8344487259618762872) -->
- <skip />
- <!-- no translation found for consent_no (2640796915611404382) -->
- <skip />
+ <string name="confirmation_title" msgid="814973816731238955">"Setha i-<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ukuze iphathe i-<xliff:g id="DEVICE_NAME">%2$s</xliff:g> yakho - <strong></strong>"</string>
+ <string name="profile_summary" msgid="2059360676631420073">"I-app iyadingeka ukuphatha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> yakho. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+ <string name="consent_yes" msgid="8344487259618762872">"Vumela"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 4dd012a..44748e9 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -29,7 +29,7 @@
<string name="profile_name_watch">watch</string>
<!-- Title of the device association confirmation dialog. -->
- <string name="confirmation_title">Set <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
+ <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to manage your <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
<!-- Text of the device profile permissions explanation in the association dialog. -->
<string name="profile_summary">This app is needed to manage your <xliff:g id="profile_name" example="watch">%1$s</xliff:g>. <xliff:g id="privileges_discplaimer" example="Android Wear will get access to your Notifications, Calendar and Contacts.">%2$s</xliff:g></string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index f1a98ad..91a6749 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -128,9 +128,11 @@
if (useDeviceProfile) {
profileSummary.setVisibility(View.VISIBLE);
+ String deviceRef = getRequest().isSingleDevice()
+ ? getService().mDevicesFound.get(0).getDisplayName()
+ : profileName;
profileSummary.setText(getString(R.string.profile_summary,
- getCallingAppName(),
- profileName,
+ deviceRef,
profilePrivacyDisclaimer));
} else {
profileSummary.setVisibility(View.GONE);
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 86b85e83..5b33328 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -23,6 +23,26 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+java_library {
+ name: "framework-connectivity-protos",
+ sdk_version: "module_current",
+ proto: {
+ type: "nano",
+ },
+ srcs: [
+ // TODO: consider moving relevant .proto files directly to the module directory
+ ":framework-javastream-protos",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
+ jarjar_rules: "jarjar-rules-proto.txt",
+ visibility: [
+ "//visibility:private",
+ ],
+}
+
filegroup {
name: "framework-connectivity-internal-sources",
srcs: [
@@ -63,8 +83,7 @@
name: "framework-connectivity",
api_only: true,
defaults: ["framework-module-defaults"],
- // TODO: build against module API
- platform_apis: true,
+ installable: true,
srcs: [
":framework-connectivity-sources",
],
@@ -81,5 +100,41 @@
libs: [
"unsupportedappusage",
],
- permitted_packages: ["android.net", "com.android.connectivity.aidl"],
+ permitted_packages: [
+ "android.net",
+ "com.android.connectivity.aidl",
+ ],
+}
+
+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",
+ "ServiceConnectivityResources",
+ ],
+ 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",
+ "com.android.connectivity.aidl",
+ ],
}
diff --git a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/packages/Connectivity/framework/aidl-export/android/net/NetworkScore.aidl
similarity index 79%
copy from packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
copy to packages/Connectivity/framework/aidl-export/android/net/NetworkScore.aidl
index 7979afc..af12dcf 100644
--- a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
+++ b/packages/Connectivity/framework/aidl-export/android/net/NetworkScore.aidl
@@ -1,6 +1,5 @@
/**
- *
- * Copyright (C) 2021 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.
@@ -17,7 +16,5 @@
package android.net;
-/** @hide */
-oneway interface IOnSetOemNetworkPreferenceListener {
- void onComplete();
-}
+parcelable NetworkScore;
+
diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt
index 243e4ca..f22d4b7 100644
--- a/packages/Connectivity/framework/api/current.txt
+++ b/packages/Connectivity/framework/api/current.txt
@@ -87,6 +87,7 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered();
method public boolean isDefaultNetworkActive();
method @Deprecated public static boolean isNetworkTypeValid(int);
+ method public void registerBestMatchingNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 4b33366..7a91f64 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -6,11 +6,25 @@
}
public class ConnectivityManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset();
method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshot();
+ 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 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 @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(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
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 public void systemReady();
+ 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";
+ field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
+ field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
}
public final class NetworkAgentConfig implements android.os.Parcelable {
@@ -25,6 +39,11 @@
field public static final int TRANSPORT_TEST = 7; // 0x7
}
+ public class ParseException extends java.lang.RuntimeException {
+ ctor public ParseException(@NonNull String);
+ ctor public ParseException(@NonNull String, @NonNull Throwable);
+ }
+
public final class TcpRepairWindow {
ctor public TcpRepairWindow(int, int, int, int, int, int);
field public final int maxWindow;
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index a98f14e..8845225 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -18,7 +18,7 @@
method public long getRefreshTimeMillis();
method @Nullable public android.net.Uri getUserPortalUrl();
method public int getUserPortalUrlSource();
- method @Nullable public String getVenueFriendlyName();
+ method @Nullable public CharSequence getVenueFriendlyName();
method @Nullable public android.net.Uri getVenueInfoUrl();
method public int getVenueInfoUrlSource();
method public boolean isCaptive();
@@ -40,7 +40,7 @@
method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
- method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String);
+ method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable CharSequence);
method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
}
@@ -56,7 +56,7 @@
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);
- method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
@@ -78,10 +78,6 @@
field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
}
- public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener {
- method public void onComplete();
- }
-
@Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
method @Deprecated public void onTetheringFailed();
diff --git a/packages/Connectivity/framework/jarjar-rules-proto.txt b/packages/Connectivity/framework/jarjar-rules-proto.txt
new file mode 100644
index 0000000..37b4dec1
--- /dev/null
+++ b/packages/Connectivity/framework/jarjar-rules-proto.txt
@@ -0,0 +1,3 @@
+keep android.net.NetworkCapabilitiesProto
+keep android.net.NetworkProto
+keep android.net.NetworkRequestProto
diff --git a/packages/Connectivity/framework/jarjar-rules.txt b/packages/Connectivity/framework/jarjar-rules.txt
new file mode 100644
index 0000000..0959840
--- /dev/null
+++ b/packages/Connectivity/framework/jarjar-rules.txt
@@ -0,0 +1,10 @@
+rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
+
+# TODO (b/149403767): remove the annotations from net-utils-device-common instead of here
+zap android.annotation.**
+zap com.android.net.module.annotation.**
+zap com.android.internal.annotations.**
+
+rule android.net.NetworkCapabilitiesProto* android.net.connectivity.proto.NetworkCapabilitiesProto@1
+rule android.net.NetworkProto* android.net.connectivity.proto.NetworkProto@1
+rule android.net.NetworkRequestProto* android.net.connectivity.proto.NetworkRequestProto@1
diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
index eafda4d..82dbd0f 100644
--- a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
+++ b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
@@ -42,7 +42,7 @@
private final long mByteLimit;
private final long mExpiryTimeMillis;
private final boolean mCaptive;
- private final String mVenueFriendlyName;
+ private final CharSequence mVenueFriendlyName;
private final int mVenueInfoUrlSource;
private final int mUserPortalUrlSource;
@@ -65,7 +65,7 @@
private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive,
- String venueFriendlyName, int venueInfoUrlSource, int userPortalUrlSource) {
+ CharSequence venueFriendlyName, int venueInfoUrlSource, int userPortalUrlSource) {
mRefreshTimeMillis = refreshTimeMillis;
mUserPortalUrl = userPortalUrl;
mVenueInfoUrl = venueInfoUrl;
@@ -80,7 +80,7 @@
private CaptivePortalData(Parcel p) {
this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
- p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(),
+ p.readLong(), p.readLong(), p.readBoolean(), p.readCharSequence(), p.readInt(),
p.readInt());
}
@@ -98,7 +98,7 @@
dest.writeLong(mByteLimit);
dest.writeLong(mExpiryTimeMillis);
dest.writeBoolean(mCaptive);
- dest.writeString(mVenueFriendlyName);
+ dest.writeCharSequence(mVenueFriendlyName);
dest.writeInt(mVenueInfoUrlSource);
dest.writeInt(mUserPortalUrlSource);
}
@@ -114,7 +114,7 @@
private long mBytesRemaining = -1;
private long mExpiryTime = -1;
private boolean mCaptive;
- private String mVenueFriendlyName;
+ private CharSequence mVenueFriendlyName;
private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
private @CaptivePortalDataSource int mUserPortalUrlSource =
CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
@@ -228,7 +228,7 @@
* Set the venue friendly name.
*/
@NonNull
- public Builder setVenueFriendlyName(@Nullable String venueFriendlyName) {
+ public Builder setVenueFriendlyName(@Nullable CharSequence venueFriendlyName) {
mVenueFriendlyName = venueFriendlyName;
return this;
}
@@ -321,7 +321,7 @@
* Get the venue friendly name
*/
@Nullable
- public String getVenueFriendlyName() {
+ public CharSequence getVenueFriendlyName() {
return mVenueFriendlyName;
}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.java
index 5234494..3598ebc 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.java
@@ -28,7 +28,6 @@
import android.os.RemoteException;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -70,8 +69,8 @@
/** @hide */
public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) {
- mContext = Preconditions.checkNotNull(context, "missing context");
- mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
+ mContext = Objects.requireNonNull(context, "missing context");
+ mService = Objects.requireNonNull(service, "missing IConnectivityManager");
}
/** @hide */
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 7189be1..9f63abf 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -16,10 +16,12 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
import static android.net.NetworkRequest.Type.LISTEN;
+import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
import static android.net.NetworkRequest.Type.REQUEST;
-import static android.net.NetworkRequest.Type.TRACK_BEST;
import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
import static android.net.QosCallback.QosCallbackRegistrationException;
@@ -31,11 +33,13 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -58,11 +62,12 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Range;
@@ -70,8 +75,6 @@
import com.android.connectivity.aidl.INetworkAgent;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.Protocol;
import libcore.net.event.NetworkEventDispatcher;
@@ -802,24 +805,27 @@
/**
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final String PRIVATE_DNS_MODE_OFF = "off";
/**
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
/**
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
- /**
- * The default Private DNS mode.
- *
- * This may change from release to release or may become dependent upon
- * the capabilities of the underlying platform.
- *
- * @hide
- */
- public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(value = {
+ PRIVATE_DNS_MODE_OFF,
+ PRIVATE_DNS_MODE_OPPORTUNISTIC,
+ PRIVATE_DNS_MODE_PROVIDER_HOSTNAME,
+ })
+ public @interface PrivateDnsMode {}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
@@ -834,7 +840,6 @@
private final Context mContext;
- private INetworkPolicyManager mNPManager;
private final TetheringManager mTetheringManager;
/**
@@ -907,8 +912,8 @@
/**
* @hide
- * TODO: Expose for SystemServer when becomes a module.
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public void systemReady() {
try {
mService.systemReady();
@@ -963,6 +968,33 @@
}
/**
+ * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}.
+ * Specify that the traffic for this user should by follow the default rules.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0;
+
+ /**
+ * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}.
+ * Specify that the traffic for this user should by default go on a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network
+ * if no such network is available.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ PROFILE_NETWORK_PREFERENCE_DEFAULT,
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE
+ })
+ public @interface ProfileNetworkPreference {
+ }
+
+ /**
* Specifies the preferred network type. When the device has more
* than one type available the preferred network type will be used.
*
@@ -1742,7 +1774,9 @@
// Map from type to transports.
final int NOT_FOUND = -1;
final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND);
- Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type);
+ if (transport == NOT_FOUND) {
+ throw new IllegalArgumentException("unknown legacy type: " + type);
+ }
nc.addTransportType(transport);
// Map from type to capabilities.
@@ -1847,8 +1881,8 @@
}
private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
- Preconditions.checkNotNull(network, "network cannot be null");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(network, "network cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mNetwork = network;
mExecutor = Executors.newSingleThreadExecutor();
mCallback = new ISocketKeepaliveCallback.Stub() {
@@ -2223,7 +2257,9 @@
*/
public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) {
INetworkActivityListener rl = mNetworkActivityListeners.get(l);
- Preconditions.checkArgument(rl != null, "Listener was not registered.");
+ if (rl == null) {
+ throw new IllegalArgumentException("Listener was not registered.");
+ }
try {
mService.registerNetworkActivityListener(rl);
} catch (RemoteException e) {
@@ -2251,8 +2287,8 @@
* {@hide}
*/
public ConnectivityManager(Context context, IConnectivityManager service) {
- mContext = Preconditions.checkNotNull(context, "missing context");
- mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
+ mContext = Objects.requireNonNull(context, "missing context");
+ mService = Objects.requireNonNull(service, "missing IConnectivityManager");
mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE);
sInstance = this;
}
@@ -2519,7 +2555,7 @@
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi,
final OnStartTetheringCallback callback, Handler handler) {
- Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+ Objects.requireNonNull(callback, "OnStartTetheringCallback cannot be null.");
final Executor executor = new Executor() {
@Override
@@ -2612,7 +2648,7 @@
public void registerTetheringEventCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull final OnTetheringEventCallback callback) {
- Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null.");
+ Objects.requireNonNull(callback, "OnTetheringEventCallback cannot be null.");
final TetheringEventCallback tetherCallback =
new TetheringEventCallback() {
@@ -2910,7 +2946,7 @@
public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi,
@NonNull @CallbackExecutor Executor executor,
@NonNull final OnTetheringEntitlementResultListener listener) {
- Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null.");
+ Objects.requireNonNull(listener, "TetheringEntitlementResultListener cannot be null.");
ResultReceiver wrappedListener = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -3003,8 +3039,9 @@
* HTTP proxy. A {@code null} value will clear the global HTTP proxy.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
- public void setGlobalProxy(ProxyInfo p) {
+ public void setGlobalProxy(@Nullable ProxyInfo p) {
try {
mService.setGlobalProxy(p);
} catch (RemoteException e) {
@@ -3019,6 +3056,8 @@
* if no global HTTP proxy is set.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
+ @Nullable
public ProxyInfo getGlobalProxy() {
try {
return mService.getGlobalProxy();
@@ -3205,10 +3244,6 @@
}
}
- // TODO : remove this method. It is a stopgap measure to help sheperding a number
- // of dependent changes that would conflict throughout the automerger graph. Having this
- // temporarily helps with the process of going through with all these dependent changes across
- // the entire tree.
/**
* @hide
* Register a NetworkAgent with ConnectivityService.
@@ -3218,20 +3253,8 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_FACTORY})
public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkAgentConfig config) {
- return registerNetworkAgent(na, ni, lp, nc, score, config, NetworkProvider.ID_NONE);
- }
-
- /**
- * @hide
- * Register a NetworkAgent with ConnectivityService.
- * @return Network corresponding to NetworkAgent.
- */
- @RequiresPermission(anyOf = {
- NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
- android.Manifest.permission.NETWORK_FACTORY})
- public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
+ NetworkCapabilities nc, @NonNull NetworkScore score, NetworkAgentConfig config,
+ int providerId) {
try {
return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId);
} catch (RemoteException e) {
@@ -3296,7 +3319,9 @@
}
public NetworkCallback(@Flag int flags) {
- Preconditions.checkArgument((flags & VALID_FLAGS) == flags);
+ if ((flags & VALID_FLAGS) != flags) {
+ throw new IllegalArgumentException("Invalid flags");
+ }
mFlags = flags;
}
@@ -3531,29 +3556,28 @@
}
}
- private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
/** @hide */
- public static final int CALLBACK_PRECHECK = BASE + 1;
+ public static final int CALLBACK_PRECHECK = 1;
/** @hide */
- public static final int CALLBACK_AVAILABLE = BASE + 2;
+ public static final int CALLBACK_AVAILABLE = 2;
/** @hide arg1 = TTL */
- public static final int CALLBACK_LOSING = BASE + 3;
+ public static final int CALLBACK_LOSING = 3;
/** @hide */
- public static final int CALLBACK_LOST = BASE + 4;
+ public static final int CALLBACK_LOST = 4;
/** @hide */
- public static final int CALLBACK_UNAVAIL = BASE + 5;
+ public static final int CALLBACK_UNAVAIL = 5;
/** @hide */
- public static final int CALLBACK_CAP_CHANGED = BASE + 6;
+ public static final int CALLBACK_CAP_CHANGED = 6;
/** @hide */
- public static final int CALLBACK_IP_CHANGED = BASE + 7;
+ public static final int CALLBACK_IP_CHANGED = 7;
/** @hide obj = NetworkCapabilities, arg1 = seq number */
- private static final int EXPIRE_LEGACY_REQUEST = BASE + 8;
+ private static final int EXPIRE_LEGACY_REQUEST = 8;
/** @hide */
- public static final int CALLBACK_SUSPENDED = BASE + 9;
+ public static final int CALLBACK_SUSPENDED = 9;
/** @hide */
- public static final int CALLBACK_RESUMED = BASE + 10;
+ public static final int CALLBACK_RESUMED = 10;
/** @hide */
- public static final int CALLBACK_BLK_CHANGED = BASE + 11;
+ public static final int CALLBACK_BLK_CHANGED = 11;
/** @hide */
public static String getCallbackName(int whichCallback) {
@@ -3583,7 +3607,7 @@
}
CallbackHandler(Handler handler) {
- this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper());
+ this(Objects.requireNonNull(handler, "Handler cannot be null.").getLooper());
}
@Override
@@ -3681,9 +3705,9 @@
int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
printStackTrace();
checkCallbackNotNull(callback);
- Preconditions.checkArgument(
- reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null,
- "null NetworkCapabilities");
+ if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) {
+ throw new IllegalArgumentException("null NetworkCapabilities");
+ }
final NetworkRequest request;
final String callingPackageName = mContext.getOpPackageName();
try {
@@ -4030,15 +4054,17 @@
}
private static void checkPendingIntentNotNull(PendingIntent intent) {
- Preconditions.checkNotNull(intent, "PendingIntent cannot be null.");
+ Objects.requireNonNull(intent, "PendingIntent cannot be null.");
}
private static void checkCallbackNotNull(NetworkCallback callback) {
- Preconditions.checkNotNull(callback, "null NetworkCallback");
+ Objects.requireNonNull(callback, "null NetworkCallback");
}
private static void checkTimeout(int timeoutMs) {
- Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive.");
+ if (timeoutMs <= 0) {
+ throw new IllegalArgumentException("timeoutMs must be strictly positive.");
+ }
}
/**
@@ -4249,15 +4275,33 @@
}
/**
- * @hide
+ * Registers to receive notifications about the best matching network which satisfy the given
+ * {@link NetworkRequest}. 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
+ * {@link #registerNetworkCallback} and its variants and {@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 request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} that the system will call as suitable
+ * networks change state.
+ * @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.
*/
- // TODO: Make it public api.
@SuppressLint("ExecutorRegistration")
public void registerBestMatchingNetworkCallback(@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
final NetworkCapabilities nc = request.networkCapabilities;
final CallbackHandler cbHandler = new CallbackHandler(handler);
- sendRequestForNetwork(nc, networkCallback, 0, TRACK_BEST, TYPE_NONE, cbHandler);
+ sendRequestForNetwork(nc, networkCallback, 0, LISTEN_FOR_BEST, TYPE_NONE, cbHandler);
}
/**
@@ -4300,8 +4344,9 @@
// Find all requests associated to this callback and stop callback triggers immediately.
// Callback is reusable immediately. http://b/20701525, http://b/35921499.
synchronized (sCallbacks) {
- Preconditions.checkArgument(networkCallback.networkRequest != null,
- "NetworkCallback was not registered");
+ if (networkCallback.networkRequest == null) {
+ throw new IllegalArgumentException("NetworkCallback was not registered");
+ }
if (networkCallback.networkRequest == ALREADY_UNREGISTERED) {
Log.d(TAG, "NetworkCallback was already unregistered");
return;
@@ -4352,8 +4397,13 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
- public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+ public void setAcceptUnvalidated(@NonNull Network network, boolean accept, boolean always) {
try {
mService.setAcceptUnvalidated(network, accept, always);
} catch (RemoteException e) {
@@ -4375,8 +4425,14 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
- public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) {
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+ public void setAcceptPartialConnectivity(@NonNull Network network, boolean accept,
+ boolean always) {
try {
mService.setAcceptPartialConnectivity(network, accept, always);
} catch (RemoteException e) {
@@ -4394,8 +4450,13 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
- public void setAvoidUnvalidated(Network network) {
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
+ public void setAvoidUnvalidated(@NonNull Network network) {
try {
mService.setAvoidUnvalidated(network);
} catch (RemoteException e) {
@@ -4525,7 +4586,10 @@
* Resets all connectivity manager settings back to factory defaults.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK})
public void factoryReset() {
try {
mService.factoryReset();
@@ -4603,7 +4667,7 @@
Log.e(TAG, "Can't set proxy properties", e);
}
// Must flush DNS cache as new network may have different DNS resolutions.
- InetAddress.clearDnsCache();
+ InetAddressCompat.clearDnsCache();
// Must flush socket pool as idle sockets will be bound to previous network and may
// cause subsequent fetches to be performed on old network.
NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
@@ -4728,17 +4792,6 @@
public @interface RestrictBackgroundStatus {
}
- private INetworkPolicyManager getNetworkPolicyManager() {
- synchronized (this) {
- if (mNPManager != null) {
- return mNPManager;
- }
- mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager
- .getService(Context.NETWORK_POLICY_SERVICE));
- return mNPManager;
- }
- }
-
/**
* Determines if the calling application is subject to metered network restrictions while
* running on background.
@@ -4749,7 +4802,7 @@
*/
public @RestrictBackgroundStatus int getRestrictBackgroundStatus() {
try {
- return getNetworkPolicyManager().getRestrictBackgroundByCaller();
+ return mService.getRestrictBackgroundStatusByCaller();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5057,19 +5110,6 @@
}
/**
- * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor,
- * OnSetOemNetworkPreferenceListener)}.
- * @hide
- */
- @SystemApi
- public interface OnSetOemNetworkPreferenceListener {
- /**
- * Called when setOemNetworkPreference() successfully completes.
- */
- void onComplete();
- }
-
- /**
* Used by automotive devices to set the network preferences used to direct traffic at an
* application level as per the given OemNetworkPreferences. An example use-case would be an
* automotive OEM wanting to provide connectivity for applications critical to the usage of a
@@ -5091,16 +5131,16 @@
@RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE)
public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference,
@Nullable @CallbackExecutor final Executor executor,
- @Nullable final OnSetOemNetworkPreferenceListener listener) {
+ @Nullable final Runnable listener) {
Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
if (null != listener) {
Objects.requireNonNull(executor, "Executor must be non-null");
}
- final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null :
- new IOnSetOemNetworkPreferenceListener.Stub() {
+ final IOnCompleteListener listenerInternal = listener == null ? null :
+ new IOnCompleteListener.Stub() {
@Override
public void onComplete() {
- executor.execute(listener::onComplete);
+ executor.execute(listener::run);
}
};
@@ -5112,10 +5152,56 @@
}
}
+ /**
+ * Request that a user profile is put by default on a network matching a given preference.
+ *
+ * See the documentation for the individual preferences for a description of the supported
+ * behaviors.
+ *
+ * @param profile the profile concerned.
+ * @param preference the preference for this profile.
+ * @param executor an executor to execute the listener on. Optional if listener is null.
+ * @param listener an optional listener to listen for completion of the operation.
+ * @throws IllegalArgumentException if {@code profile} is not a valid user profile.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @hide
+ */
+ // This function is for establishing per-profile default networking and can only be called by
+ // the device policy manager, running as the system server. It would make no sense to call it
+ // on a context for a user because it does not establish a setting on behalf of a user, rather
+ // it establishes a setting for a user on behalf of the DPM.
+ @SuppressLint({"UserHandle"})
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void setProfileNetworkPreference(@NonNull final UserHandle profile,
+ @ProfileNetworkPreference final int preference,
+ @Nullable @CallbackExecutor final Executor executor,
+ @Nullable final Runnable listener) {
+ if (null != listener) {
+ Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener");
+ }
+ final IOnCompleteListener proxy;
+ if (null == listener) {
+ proxy = null;
+ } else {
+ proxy = new IOnCompleteListener.Stub() {
+ @Override
+ public void onComplete() {
+ executor.execute(listener::run);
+ }
+ };
+ }
+ try {
+ mService.setProfileNetworkPreference(profile, preference, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// The first network ID of IPSec tunnel interface.
- private static final int TUN_INTF_NETID_START = 0xFC00;
+ private static final int TUN_INTF_NETID_START = 0xFC00; // 0xFC00 = 64512
// The network ID range of IPSec tunnel interface.
- private static final int TUN_INTF_NETID_RANGE = 0x0400;
+ private static final int TUN_INTF_NETID_RANGE = 0x0400; // 0x0400 = 1024
/**
* Get the network ID range reserved for IPSec tunnel interfaces.
@@ -5128,4 +5214,25 @@
public static Range<Integer> getIpSecNetIdRange() {
return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1);
}
+
+ /**
+ * Get private DNS mode from settings.
+ *
+ * @param context The Context to query the private DNS mode from settings.
+ * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @NonNull
+ @PrivateDnsMode
+ public static String getPrivateDnsMode(@NonNull Context context) {
+ final ContentResolver cr = context.getContentResolver();
+ String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
+ if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
+ // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
+ // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
+ if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+ return mode;
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/ConnectivityResources.java b/packages/Connectivity/framework/src/android/net/ConnectivityResources.java
similarity index 69%
rename from services/core/java/com/android/server/connectivity/ConnectivityResources.java
rename to packages/Connectivity/framework/src/android/net/ConnectivityResources.java
index 45cf21e..18f0de0 100644
--- a/services/core/java/com/android/server/connectivity/ConnectivityResources.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityResources.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.connectivity;
+package android.net;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -27,13 +27,14 @@
import android.content.res.Resources;
import android.util.Log;
-import com.android.server.ConnectivityService;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
/**
- * Utility to obtain the {@link ConnectivityService} {@link Resources}, in the
+ * Utility to obtain the {@link com.android.server.ConnectivityService} {@link Resources}, in the
* ServiceConnectivityResources APK.
+ * @hide
*/
public class ConnectivityResources {
private static final String RESOURCES_APK_INTENT =
@@ -44,18 +45,35 @@
private final Context mContext;
@Nullable
- private Resources mResources = null;
+ private Context mResourcesContext = null;
+
+ @Nullable
+ private static Context sTestResourcesContext = null;
public ConnectivityResources(Context context) {
mContext = context;
}
/**
- * Get the {@link Resources} of the ServiceConnectivityResources APK.
+ * Convenience method to mock all resources for the duration of a test.
+ *
+ * Call with a null context to reset after the test.
*/
- public synchronized Resources get() {
- if (mResources != null) {
- return mResources;
+ @VisibleForTesting
+ public static void setResourcesContextForTest(@Nullable Context testContext) {
+ sTestResourcesContext = testContext;
+ }
+
+ /**
+ * Get the {@link Context} of the resources package.
+ */
+ public synchronized Context getResourcesContext() {
+ if (sTestResourcesContext != null) {
+ return sTestResourcesContext;
+ }
+
+ if (mResourcesContext != null) {
+ return mResourcesContext;
}
final List<ResolveInfo> pkgs = mContext.getPackageManager()
@@ -77,7 +95,14 @@
throw new IllegalStateException("Resolved package not found", e);
}
- mResources = pkgContext.getResources();
- return mResources;
+ mResourcesContext = pkgContext;
+ return pkgContext;
+ }
+
+ /**
+ * Get the {@link Resources} of the ServiceConnectivityResources APK.
+ */
+ public Resources get() {
+ return getResourcesContext().getResources();
}
}
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
new file mode 100644
index 0000000..bbd8393
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/ConnectivitySettingsManager.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A manager class for connectivity module settings.
+ *
+ * @hide
+ */
+public class ConnectivitySettingsManager {
+
+ private ConnectivitySettingsManager() {}
+
+ /** Data activity timeout settings */
+
+ /**
+ * Inactivity timeout to track mobile data activity.
+ *
+ * If set to a positive integer, it indicates the inactivity timeout value in seconds to
+ * infer the data activity of mobile network. After a period of no activity on mobile
+ * networks with length specified by the timeout, an {@code ACTION_DATA_ACTIVITY_CHANGE}
+ * intent is fired to indicate a transition of network status from "active" to "idle". Any
+ * subsequent activity on mobile networks triggers the firing of {@code
+ * ACTION_DATA_ACTIVITY_CHANGE} intent indicating transition from "idle" to "active".
+ *
+ * Network activity refers to transmitting or receiving data on the network interfaces.
+ *
+ * Tracking is disabled if set to zero or negative value.
+ */
+ public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile";
+
+ /**
+ * Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE}
+ * but for Wifi network.
+ */
+ public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi";
+
+ /** Dns resolver settings */
+
+ /**
+ * Sample validity in seconds to configure for the system DNS resolver.
+ */
+ public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS =
+ "dns_resolver_sample_validity_seconds";
+
+ /**
+ * Success threshold in percent for use with the system DNS resolver.
+ */
+ public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT =
+ "dns_resolver_success_threshold_percent";
+
+ /**
+ * Minimum number of samples needed for statistics to be considered meaningful in the
+ * system DNS resolver.
+ */
+ public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples";
+
+ /**
+ * Maximum number taken into account for statistics purposes in the system DNS resolver.
+ */
+ public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples";
+
+ /** Network switch notification settings */
+
+ /**
+ * The maximum number of notifications shown in 24 hours when switching networks.
+ */
+ public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT =
+ "network_switch_notification_daily_limit";
+
+ /**
+ * The minimum time in milliseconds between notifications when switching networks.
+ */
+ public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS =
+ "network_switch_notification_rate_limit_millis";
+
+ /** Captive portal settings */
+
+ /**
+ * The URL used for HTTP captive portal detection upon a new connection.
+ * A 204 response code from the server is used for validation.
+ */
+ public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+
+ /**
+ * What to do when connecting a network that presents a captive portal.
+ * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+ *
+ * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+ */
+ public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+
+ /**
+ * Don't attempt to detect captive portals.
+ */
+ public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
+
+ /**
+ * When detecting a captive portal, display a notification that
+ * prompts the user to sign in.
+ */
+ public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
+
+ /**
+ * When detecting a captive portal, immediately disconnect from the
+ * network and do not reconnect to that network in the future.
+ */
+ public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ CAPTIVE_PORTAL_MODE_IGNORE,
+ CAPTIVE_PORTAL_MODE_PROMPT,
+ CAPTIVE_PORTAL_MODE_AVOID,
+ })
+ public @interface CaptivePortalMode {}
+
+ /** Global http proxy settings */
+
+ /**
+ * Host name for global http proxy. Set via ConnectivityManager.
+ */
+ public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
+
+ /**
+ * Integer host port for global http proxy. Set via ConnectivityManager.
+ */
+ public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
+
+ /**
+ * Exclusion list for global proxy. This string contains a list of
+ * comma-separated domains where the global proxy does not apply.
+ * Domains should be listed in a comma- separated list. Example of
+ * acceptable formats: ".domain1.com,my.domain2.com" Use
+ * ConnectivityManager to set/get.
+ */
+ public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST =
+ "global_http_proxy_exclusion_list";
+
+ /**
+ * The location PAC File for the proxy.
+ */
+ public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url";
+
+ /** Private dns settings */
+
+ /**
+ * The requested Private DNS mode (string), and an accompanying specifier (string).
+ *
+ * Currently, the specifier holds the chosen provider name when the mode requests
+ * a specific provider. It may be used to store the provider name even when the
+ * mode changes so that temporarily disabling and re-enabling the specific
+ * provider mode does not necessitate retyping the provider hostname.
+ */
+ public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+
+ /**
+ * The specific Private DNS provider name.
+ */
+ public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
+
+ /**
+ * Forced override of the default mode (hardcoded as "automatic", nee "opportunistic").
+ * This allows changing the default mode without effectively disabling other modes,
+ * all of which require explicit user action to enable/configure. See also b/79719289.
+ *
+ * Value is a string, suitable for assignment to PRIVATE_DNS_MODE above.
+ */
+ public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
+
+ /** Other settings */
+
+ /**
+ * The number of milliseconds to hold on to a PendingIntent based request. This delay gives
+ * the receivers of the PendingIntent an opportunity to make a new network request before
+ * the Network satisfying the request is potentially removed.
+ */
+ public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS =
+ "connectivity_release_pending_intent_delay_ms";
+
+ /**
+ * Whether the mobile data connection should remain active even when higher
+ * priority networks like WiFi are active, to help make network switching faster.
+ *
+ * See ConnectivityService for more info.
+ *
+ * (0 = disabled, 1 = enabled)
+ */
+ public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
+
+ /**
+ * Whether the wifi data connection should remain active even when higher
+ * priority networks like Ethernet are active, to keep both networks.
+ * In the case where higher priority networks are connected, wifi will be
+ * unused unless an application explicitly requests to use it.
+ *
+ * See ConnectivityService for more info.
+ *
+ * (0 = disabled, 1 = enabled)
+ */
+ public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested";
+
+ /**
+ * Whether to automatically switch away from wifi networks that lose Internet access.
+ * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always
+ * avoids such networks. Valid values are:
+ *
+ * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
+ * null: Ask the user whether to switch away from bad wifi.
+ * 1: Avoid bad wifi.
+ */
+ public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
+
+ /**
+ * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be
+ * overridden by the system based on device or application state. If null, the value
+ * specified by config_networkMeteredMultipathPreference is used.
+ */
+ public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
+ "network_metered_multipath_preference";
+}
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index f9393e3..98f3d40 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -20,7 +20,7 @@
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager;
import android.net.IConnectivityDiagnosticsCallback;
-import android.net.IOnSetOemNetworkPreferenceListener;
+import android.net.IOnCompleteListener;
import android.net.INetworkActivityListener;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
@@ -30,6 +30,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
+import android.net.NetworkScore;
import android.net.NetworkState;
import android.net.NetworkStateSnapshot;
import android.net.OemNetworkPreferences;
@@ -42,6 +43,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import com.android.connectivity.aidl.INetworkAgent;
@@ -138,7 +140,7 @@
void declareNetworkRequestUnfulfillable(in NetworkRequest request);
Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp,
- in NetworkCapabilities nc, int score, in NetworkAgentConfig config,
+ in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config,
in int factorySerialNumber);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType,
@@ -214,5 +216,10 @@
void unregisterQosCallback(in IQosCallback callback);
void setOemNetworkPreference(in OemNetworkPreferences preference,
- in IOnSetOemNetworkPreferenceListener listener);
+ in IOnCompleteListener listener);
+
+ void setProfileNetworkPreference(in UserHandle profile, int preference,
+ in IOnCompleteListener listener);
+
+ int getRestrictBackgroundStatusByCaller();
}
diff --git a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/packages/Connectivity/framework/src/android/net/IOnCompleteListener.aidl
similarity index 92%
rename from packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
rename to packages/Connectivity/framework/src/android/net/IOnCompleteListener.aidl
index 7979afc..4bb89f6c 100644
--- a/packages/Connectivity/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl
+++ b/packages/Connectivity/framework/src/android/net/IOnCompleteListener.aidl
@@ -18,6 +18,6 @@
package android.net;
/** @hide */
-oneway interface IOnSetOemNetworkPreferenceListener {
+oneway interface IOnCompleteListener {
void onComplete();
}
diff --git a/packages/Connectivity/framework/src/android/net/InetAddressCompat.java b/packages/Connectivity/framework/src/android/net/InetAddressCompat.java
new file mode 100644
index 0000000..8404441
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/InetAddressCompat.java
@@ -0,0 +1,76 @@
+/*
+ * 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.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Compatibility utility for InetAddress 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 InetAddress is also part of the base java SDK that is earlier on the
+ * classpath, the extra core platform APIs are not seen.
+ *
+ * TODO (b/183097033): remove this utility as soon as core_current is part of module_current
+ * @hide
+ */
+public class InetAddressCompat {
+
+ /**
+ * @see InetAddress#clearDnsCache()
+ */
+ public static void clearDnsCache() {
+ try {
+ InetAddress.class.getMethod("clearDnsCache").invoke(null);
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ Log.wtf(InetAddressCompat.class.getSimpleName(), "Error clearing DNS cache", e);
+ }
+ }
+
+ /**
+ * @see InetAddress#getAllByNameOnNet(String, int)
+ */
+ public static InetAddress[] getAllByNameOnNet(String host, int netId) throws
+ UnknownHostException {
+ try {
+ return (InetAddress[]) InetAddress.class.getMethod("getAllByNameOnNet",
+ String.class, int.class).invoke(null, host, netId);
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e);
+ throw new IllegalStateException("Error querying via getAllNameOnNet", e);
+ }
+ }
+
+ /**
+ * @see InetAddress#getByNameOnNet(String, int)
+ */
+ public static InetAddress getByNameOnNet(String host, int netId) throws
+ UnknownHostException {
+ try {
+ return (InetAddress) InetAddress.class.getMethod("getByNameOnNet",
+ String.class, int.class).invoke(null, host, netId);
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e);
+ throw new IllegalStateException("Error querying via getByNameOnNet", e);
+ }
+ }
+}
diff --git a/packages/Connectivity/framework/src/android/net/IpPrefix.java b/packages/Connectivity/framework/src/android/net/IpPrefix.java
index d2ee7d1..bf4481a 100644
--- a/packages/Connectivity/framework/src/android/net/IpPrefix.java
+++ b/packages/Connectivity/framework/src/android/net/IpPrefix.java
@@ -113,7 +113,7 @@
// first statement in constructor". We could factor out setting the member variables to an
// init() method, but if we did, then we'd have to make the members non-final, or "error:
// cannot assign a value to final variable address". So we just duplicate the code here.
- Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(prefix);
+ Pair<InetAddress, Integer> ipAndMask = NetworkUtils.legacyParseIpAndMask(prefix);
this.address = ipAndMask.first.getAddress();
this.prefixLength = ipAndMask.second;
checkAndMaskAddressAndPrefixLength();
diff --git a/packages/Connectivity/framework/src/android/net/LinkAddress.java b/packages/Connectivity/framework/src/android/net/LinkAddress.java
index d1bdaa0..d48b8c7 100644
--- a/packages/Connectivity/framework/src/android/net/LinkAddress.java
+++ b/packages/Connectivity/framework/src/android/net/LinkAddress.java
@@ -325,7 +325,7 @@
public LinkAddress(@NonNull String address, int flags, int scope) {
// This may throw an IllegalArgumentException; catching it is the caller's responsibility.
// TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24".
- Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
+ Pair<InetAddress, Integer> ipAndMask = NetworkUtils.legacyParseIpAndMask(address);
init(ipAndMask.first, ipAndMask.second, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN);
}
diff --git a/packages/Connectivity/framework/src/android/net/MacAddress.java b/packages/Connectivity/framework/src/android/net/MacAddress.java
index c83c23a..26a504a 100644
--- a/packages/Connectivity/framework/src/android/net/MacAddress.java
+++ b/packages/Connectivity/framework/src/android/net/MacAddress.java
@@ -25,7 +25,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
import com.android.net.module.util.MacAddressUtils;
import java.lang.annotation.Retention;
@@ -34,6 +33,7 @@
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Arrays;
+import java.util.Objects;
/**
* Representation of a MAC address.
@@ -229,7 +229,7 @@
* @hide
*/
public static @NonNull byte[] byteAddrFromStringAddr(String addr) {
- Preconditions.checkNotNull(addr);
+ Objects.requireNonNull(addr);
String[] parts = addr.split(":");
if (parts.length != ETHER_ADDR_LEN) {
throw new IllegalArgumentException(addr + " was not a valid MAC address");
@@ -275,7 +275,7 @@
// Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
// that avoids the allocation of an intermediary byte[].
private static long longAddrFromStringAddr(String addr) {
- Preconditions.checkNotNull(addr);
+ Objects.requireNonNull(addr);
String[] parts = addr.split(":");
if (parts.length != ETHER_ADDR_LEN) {
throw new IllegalArgumentException(addr + " was not a valid MAC address");
@@ -364,8 +364,8 @@
*
*/
public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) {
- Preconditions.checkNotNull(baseAddress);
- Preconditions.checkNotNull(mask);
+ Objects.requireNonNull(baseAddress);
+ Objects.requireNonNull(mask);
return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr);
}
diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java
index 46141e0..0741414 100644
--- a/packages/Connectivity/framework/src/android/net/Network.java
+++ b/packages/Connectivity/framework/src/android/net/Network.java
@@ -30,10 +30,10 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
-import com.android.okhttp.internalandroidapi.Dns;
-import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
import libcore.io.IoUtils;
+import libcore.net.http.Dns;
+import libcore.net.http.HttpURLConnectionFactory;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -142,7 +142,7 @@
* @throws UnknownHostException if the address lookup fails.
*/
public InetAddress[] getAllByName(String host) throws UnknownHostException {
- return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());
+ return InetAddressCompat.getAllByNameOnNet(host, getNetIdForResolv());
}
/**
@@ -155,7 +155,7 @@
* if the address lookup fails.
*/
public InetAddress getByName(String host) throws UnknownHostException {
- return InetAddress.getByNameOnNet(host, getNetIdForResolv());
+ return InetAddressCompat.getByNameOnNet(host, getNetIdForResolv());
}
/**
@@ -299,7 +299,7 @@
// Set configuration on the HttpURLConnectionFactory that will be good for all
// connections created by this Network. Configuration that might vary is left
// until openConnection() and passed as arguments.
- HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory();
+ HttpURLConnectionFactory urlConnectionFactory = HttpURLConnectionFactory.createInstance();
urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup
// A private connection pool just for this Network.
urlConnectionFactory.setNewConnectionPool(httpMaxConnections,
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 27aa15d..a127c6f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -37,7 +37,6 @@
import com.android.connectivity.aidl.INetworkAgent;
import com.android.connectivity.aidl.INetworkAgentRegistry;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Protocol;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -125,7 +124,10 @@
*/
public final int providerId;
- private static final int BASE = Protocol.BASE_NETWORK_AGENT;
+ // ConnectivityService parses message constants from itself and NetworkAgent with MessageUtils
+ // for debugging purposes, and crashes if some messages have the same values.
+ // TODO: have ConnectivityService store message names in different maps and remove this base
+ private static final int BASE = 200;
/**
* Sent by ConnectivityService to the NetworkAgent to inform it of
@@ -371,6 +373,14 @@
return ni;
}
+ // Temporary backward compatibility constructor
+ public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
+ @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score,
+ @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) {
+ this(context, looper, logTag, nc, lp,
+ new NetworkScore.Builder().setLegacyInt(score).build(), config, provider);
+ }
+
/**
* Create a new network agent.
* @param context a {@link Context} to get system services from.
@@ -382,10 +392,12 @@
* @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, int score,
- @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) {
+ @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
+ @NonNull NetworkScore score, @NonNull NetworkAgentConfig config,
+ @Nullable NetworkProvider provider) {
this(looper, context, logTag, nc, lp, score, config,
provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(),
getLegacyNetworkInfo(config));
@@ -395,12 +407,12 @@
public final Context context;
public final NetworkCapabilities capabilities;
public final LinkProperties properties;
- public final int score;
+ public final NetworkScore score;
public final NetworkAgentConfig config;
public final NetworkInfo info;
InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities,
- @NonNull LinkProperties properties, int score, @NonNull NetworkAgentConfig config,
- @NonNull NetworkInfo info) {
+ @NonNull LinkProperties properties, @NonNull NetworkScore score,
+ @NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) {
this.context = context;
this.capabilities = capabilities;
this.properties = properties;
@@ -412,8 +424,9 @@
private volatile InitialConfiguration mInitialConfiguration;
private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag,
- @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score,
- @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) {
+ @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
+ @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId,
+ @NonNull NetworkInfo ni) {
mHandler = new NetworkAgentHandler(looper);
LOG_TAG = logTag;
mNetworkInfo = new NetworkInfo(ni);
@@ -875,13 +888,22 @@
/**
* Must be called by the agent to update the score of this network.
*
+ * @param score the new score.
+ * @hide TODO : unhide when impl is complete
+ */
+ public final void sendNetworkScore(@NonNull NetworkScore score) {
+ Objects.requireNonNull(score);
+ queueOrSendMessage(reg -> reg.sendScore(score));
+ }
+
+ /**
+ * Must be called by the agent to update the score of this network.
+ *
* @param score the new score, between 0 and 99.
+ * deprecated use sendNetworkScore(NetworkScore) TODO : remove in S.
*/
public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) {
- if (score < 0) {
- throw new IllegalArgumentException("Score must be >= 0");
- }
- queueOrSendMessage(reg -> reg.sendScore(score));
+ sendNetworkScore(new NetworkScore.Builder().setLegacyInt(score).build());
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 058f3c9..c6dfcee 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -35,7 +35,6 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkCapabilitiesUtils;
@@ -2099,8 +2098,9 @@
}
private static void checkValidTransportType(@Transport int transport) {
- Preconditions.checkArgument(
- isValidTransport(transport), "Invalid TransportType " + transport);
+ if (!isValidTransport(transport)) {
+ throw new IllegalArgumentException("Invalid TransportType " + transport);
+ }
}
private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) {
@@ -2108,8 +2108,9 @@
}
private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
- Preconditions.checkArgument(isValidCapability(capability),
- "NetworkCapability " + capability + "out of range");
+ if (!isValidCapability(capability)) {
+ throw new IllegalArgumentException("NetworkCapability " + capability + "out of range");
+ }
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 3fd95ee..dbe3ecc 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -140,7 +140,7 @@
REQUEST,
BACKGROUND_REQUEST,
TRACK_SYSTEM_DEFAULT,
- TRACK_BEST,
+ LISTEN_FOR_BEST,
};
/**
@@ -514,6 +514,15 @@
}
/**
+ * Returns true iff. this NetworkRequest is of type LISTEN_FOR_BEST.
+ *
+ * @hide
+ */
+ public boolean isListenForBest() {
+ return type == Type.LISTEN_FOR_BEST;
+ }
+
+ /**
* Returns true iff. the contained NetworkRequest is one that:
*
* - should be associated with at most one satisfying network
diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java
new file mode 100644
index 0000000..e640737
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/NetworkScore.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.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Object representing the quality of a network as perceived by the user.
+ *
+ * A NetworkScore object represents the characteristics of a network that affects how good the
+ * network is considered for a particular use.
+ * @hide
+ */
+// TODO : @SystemApi when the implementation is complete
+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.
+ private final int mLegacyInt;
+
+ // Agent-managed policies
+ // TODO : add them here, starting from 1
+
+ // Bitmask of all the policies applied to this score.
+ private final long mPolicies;
+
+ /** @hide */
+ NetworkScore(final int legacyInt, final long policies) {
+ mLegacyInt = legacyInt;
+ mPolicies = policies;
+ }
+
+ private NetworkScore(@NonNull final Parcel in) {
+ mLegacyInt = in.readInt();
+ mPolicies = in.readLong();
+ }
+
+ public int getLegacyInt() {
+ return mLegacyInt;
+ }
+
+ @Override
+ public String toString() {
+ return "Score(" + mLegacyInt + ")";
+ }
+
+ @Override
+ public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+ dest.writeInt(mLegacyInt);
+ dest.writeLong(mPolicies);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull public static final Creator<NetworkScore> CREATOR = new Creator<>() {
+ @Override
+ @NonNull
+ public NetworkScore createFromParcel(@NonNull final Parcel in) {
+ return new NetworkScore(in);
+ }
+
+ @Override
+ @NonNull
+ public NetworkScore[] newArray(int size) {
+ return new NetworkScore[size];
+ }
+ };
+
+ /**
+ * A builder for NetworkScore.
+ */
+ public static final class Builder {
+ private static final long POLICY_NONE = 0L;
+ private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE;
+ private int mLegacyInt = INVALID_LEGACY_INT;
+
+ /**
+ * Sets the legacy int for this score.
+ *
+ * Do not rely on this. It will be gone by the time S is released.
+ *
+ * @param score the legacy int
+ * @return this
+ */
+ @NonNull
+ public Builder setLegacyInt(final int score) {
+ mLegacyInt = score;
+ return this;
+ }
+
+ /**
+ * Builds this NetworkScore.
+ * @return The built NetworkScore object.
+ */
+ @NonNull
+ public NetworkScore build() {
+ return new NetworkScore(mLegacyInt, POLICY_NONE);
+ }
+ }
+}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index 9e42bbe..c0f2628 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -27,8 +27,10 @@
import java.io.FileDescriptor;
import java.math.BigInteger;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
+import java.net.UnknownHostException;
import java.util.Locale;
import java.util.TreeSet;
@@ -212,7 +214,7 @@
@Deprecated
public static InetAddress numericToInetAddress(String addrString)
throws IllegalArgumentException {
- return InetAddress.parseNumericAddress(addrString);
+ return InetAddresses.parseNumericAddress(addrString);
}
/**
@@ -234,7 +236,7 @@
try {
String[] pieces = ipAndMaskString.split("/", 2);
prefixLength = Integer.parseInt(pieces[1]);
- address = InetAddress.parseNumericAddress(pieces[0]);
+ address = InetAddresses.parseNumericAddress(pieces[0]);
} catch (NullPointerException e) { // Null string.
} catch (ArrayIndexOutOfBoundsException e) { // No prefix length.
} catch (NumberFormatException e) { // Non-numeric prefix.
@@ -249,6 +251,47 @@
}
/**
+ * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64".
+ * @hide
+ *
+ * @deprecated This method is used only for IpPrefix and LinkAddress. Since Android S, use
+ * {@link #parseIpAndMask(String)}, if possible.
+ */
+ @Deprecated
+ public static Pair<InetAddress, Integer> legacyParseIpAndMask(String ipAndMaskString) {
+ InetAddress address = null;
+ int prefixLength = -1;
+ try {
+ String[] pieces = ipAndMaskString.split("/", 2);
+ prefixLength = Integer.parseInt(pieces[1]);
+ if (pieces[0] == null || pieces[0].isEmpty()) {
+ final byte[] bytes = new byte[16];
+ bytes[15] = 1;
+ return new Pair<InetAddress, Integer>(Inet6Address.getByAddress(
+ "ip6-localhost"/* host */, bytes, 0 /* scope_id */), prefixLength);
+ }
+
+ if (pieces[0].startsWith("[")
+ && pieces[0].endsWith("]")
+ && pieces[0].indexOf(':') != -1) {
+ pieces[0] = pieces[0].substring(1, pieces[0].length() - 1);
+ }
+ address = InetAddresses.parseNumericAddress(pieces[0]);
+ } catch (NullPointerException e) { // Null string.
+ } catch (ArrayIndexOutOfBoundsException e) { // No prefix length.
+ } catch (NumberFormatException e) { // Non-numeric prefix.
+ } catch (IllegalArgumentException e) { // Invalid IP address.
+ } catch (UnknownHostException e) { // IP address length is illegal
+ }
+
+ if (address == null || prefixLength == -1) {
+ throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString);
+ }
+
+ return new Pair<InetAddress, Integer>(address, prefixLength);
+ }
+
+ /**
* Convert a 32 char hex string into a Inet6Address.
* throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
* made into an Inet6Address
diff --git a/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java b/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java
index 48bd297..5a76cd6 100644
--- a/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java
+++ b/packages/Connectivity/framework/src/android/net/OemNetworkPreferences.java
@@ -73,6 +73,14 @@
private final Bundle mNetworkMappings;
/**
+ * Return whether this object is empty.
+ * @hide
+ */
+ public boolean isEmpty() {
+ return mNetworkMappings.keySet().size() == 0;
+ }
+
+ /**
* Return the currently built application package name to {@link OemNetworkPreference} mappings.
* @return the current network preferences map.
*/
diff --git a/packages/Connectivity/framework/src/android/net/ParseException.java b/packages/Connectivity/framework/src/android/net/ParseException.java
index bcfdd7e..ca6d012 100644
--- a/packages/Connectivity/framework/src/android/net/ParseException.java
+++ b/packages/Connectivity/framework/src/android/net/ParseException.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
/**
* Thrown when parsing failed.
@@ -25,12 +26,16 @@
public class ParseException extends RuntimeException {
public String response;
- ParseException(@NonNull String response) {
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public ParseException(@NonNull String response) {
super(response);
this.response = response;
}
- ParseException(@NonNull String response, @NonNull Throwable cause) {
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public ParseException(@NonNull String response, @NonNull Throwable cause) {
super(response, cause);
this.response = response;
}
diff --git a/packages/Connectivity/framework/src/android/net/ProxyInfo.java b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
index 229db0d..745e20f 100644
--- a/packages/Connectivity/framework/src/android/net/ProxyInfo.java
+++ b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
@@ -129,7 +129,7 @@
}
/**
- * Only used in PacProxyInstaller after Local Proxy is bound.
+ * Only used in PacProxyService after Local Proxy is bound.
* @hide
*/
public ProxyInfo(@NonNull Uri pacFileUrl, int localProxyPort) {
diff --git a/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.java b/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.java
index ce54597..7904f7a 100644
--- a/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.java
+++ b/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.java
@@ -24,7 +24,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
import com.android.net.module.util.InetAddressUtils;
import java.net.InetAddress;
@@ -153,7 +152,7 @@
* @return The {@link Builder} for chaining.
*/
public @NonNull Builder setDnsServers(@NonNull Iterable<InetAddress> dnsServers) {
- Preconditions.checkNotNull(dnsServers);
+ Objects.requireNonNull(dnsServers);
mDnsServers = dnsServers;
return this;
}
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
index a174a7b..a7a6235 100644
--- a/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
+++ b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
@@ -21,10 +21,9 @@
import android.os.IBinder;
import android.os.RemoteException;
-import com.android.internal.util.Preconditions;
-
import java.util.Arrays;
import java.util.Collection;
+import java.util.Objects;
/**
* Class that allows creation and management of per-app, test-only networks
@@ -50,7 +49,7 @@
/** @hide */
public TestNetworkManager(@NonNull ITestNetworkManager service) {
- mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager");
+ mService = Objects.requireNonNull(service, "missing ITestNetworkManager");
}
/**
@@ -93,7 +92,7 @@
*/
public void setupTestNetwork(
@NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) {
- Preconditions.checkNotNull(lp, "Invalid LinkProperties");
+ Objects.requireNonNull(lp, "Invalid LinkProperties");
setupTestNetwork(lp.getInterfaceName(), lp, isMetered, new int[0], binder);
}
diff --git a/packages/Connectivity/framework/src/android/net/TestNetworkSpecifier.java b/packages/Connectivity/framework/src/android/net/TestNetworkSpecifier.java
index b7470a5..117457d 100644
--- a/packages/Connectivity/framework/src/android/net/TestNetworkSpecifier.java
+++ b/packages/Connectivity/framework/src/android/net/TestNetworkSpecifier.java
@@ -23,8 +23,6 @@
import android.os.Parcelable;
import android.text.TextUtils;
-import com.android.internal.util.Preconditions;
-
import java.util.Objects;
/**
@@ -43,7 +41,9 @@
private final String mInterfaceName;
public TestNetworkSpecifier(@NonNull String interfaceName) {
- Preconditions.checkStringNotEmpty(interfaceName);
+ if (TextUtils.isEmpty(interfaceName)) {
+ throw new IllegalArgumentException("Empty interfaceName");
+ }
mInterfaceName = interfaceName;
}
diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
index 340141b..cd8f4c0 100644
--- a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -22,9 +22,6 @@
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.SparseArray;
-
-import com.android.internal.util.MessageUtils;
import java.util.Objects;
@@ -38,13 +35,10 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
public final class VpnTransportInfo implements TransportInfo, Parcelable {
- private static final SparseArray<String> sTypeToString =
- MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
-
/** Type of this VPN. */
- @VpnManager.VpnType public final int type;
+ public final int type;
- public VpnTransportInfo(@VpnManager.VpnType int type) {
+ public VpnTransportInfo(int type) {
this.type = type;
}
@@ -63,8 +57,7 @@
@Override
public String toString() {
- final String typeString = sTypeToString.get(type, "VPN_TYPE_???");
- return String.format("VpnTransportInfo{%s}", typeString);
+ return String.format("VpnTransportInfo{type=%d}", type);
}
@Override
diff --git a/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.java b/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.java
index bf5b26e..85b24713 100644
--- a/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.java
@@ -19,12 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityResources;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.R;
-
/**
* APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible
* way to drop unwanted network packets to save power.
@@ -36,6 +36,8 @@
*/
@SystemApi
public final class ApfCapabilities implements Parcelable {
+ private static ConnectivityResources sResources;
+
/**
* Version of APF instruction set supported for packet filtering. 0 indicates no support for
* packet filtering using APF programs.
@@ -65,6 +67,14 @@
apfPacketFormat = in.readInt();
}
+ @NonNull
+ private static synchronized ConnectivityResources getResources(@NonNull Context ctx) {
+ if (sResources == null) {
+ sResources = new ConnectivityResources(ctx);
+ }
+ return sResources;
+ }
+
@Override
public int describeContents() {
@@ -121,13 +131,43 @@
* @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames.
*/
public static boolean getApfDrop8023Frames() {
- return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames);
+ // TODO(b/183076074): remove reading resources from system resources
+ final Resources systemRes = Resources.getSystem();
+ final int id = systemRes.getIdentifier("config_apfDrop802_3Frames", "bool", "android");
+ return systemRes.getBoolean(id);
+ }
+
+ /**
+ * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames.
+ * @hide
+ */
+ public static boolean getApfDrop8023Frames(@NonNull Context context) {
+ final ConnectivityResources res = getResources(context);
+ // TODO(b/183076074): use R.bool.config_apfDrop802_3Frames directly
+ final int id = res.get().getIdentifier("config_apfDrop802_3Frames", "bool",
+ res.getResourcesContext().getPackageName());
+ return res.get().getBoolean(id);
}
/**
* @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped.
*/
public static @NonNull int[] getApfEtherTypeBlackList() {
- return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList);
+ // TODO(b/183076074): remove reading resources from system resources
+ final Resources systemRes = Resources.getSystem();
+ final int id = systemRes.getIdentifier("config_apfEthTypeBlackList", "array", "android");
+ return systemRes.getIntArray(id);
+ }
+
+ /**
+ * @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped.
+ * @hide
+ */
+ public static @NonNull int[] getApfEtherTypeDenyList(@NonNull Context context) {
+ final ConnectivityResources res = getResources(context);
+ // TODO(b/183076074): use R.array.config_apfEthTypeDenyList directly
+ final int id = res.get().getIdentifier("config_apfEthTypeDenyList", "array",
+ res.getResourcesContext().getPackageName());
+ return res.get().getIntArray(id);
}
}
diff --git a/packages/Connectivity/framework/src/android/net/util/KeepaliveUtils.java b/packages/Connectivity/framework/src/android/net/util/KeepaliveUtils.java
index bfc4563..8d7a0b3 100644
--- a/packages/Connectivity/framework/src/android/net/util/KeepaliveUtils.java
+++ b/packages/Connectivity/framework/src/android/net/util/KeepaliveUtils.java
@@ -19,12 +19,11 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityResources;
import android.net.NetworkCapabilities;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
-import com.android.internal.R;
-
/**
* Collection of utilities for socket keepalive offload.
*
@@ -52,8 +51,11 @@
public static int[] getSupportedKeepalives(@NonNull Context context) {
String[] res = null;
try {
- res = context.getResources().getStringArray(
- R.array.config_networkSupportedKeepaliveCount);
+ final ConnectivityResources connRes = new ConnectivityResources(context);
+ // TODO: use R.id.config_networkSupportedKeepaliveCount directly
+ final int id = connRes.get().getIdentifier("config_networkSupportedKeepaliveCount",
+ "array", connRes.getResourcesContext().getPackageName());
+ res = new ConnectivityResources(context).get().getStringArray(id);
} catch (Resources.NotFoundException unused) {
}
if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
diff --git a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 739ddad..0b42a00 100644
--- a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -16,8 +16,8 @@
package android.net.util;
-import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
-import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
+import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
@@ -27,6 +27,7 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.net.ConnectivityResources;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
@@ -35,7 +36,6 @@
import android.telephony.TelephonyManager;
import android.util.Log;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Arrays;
@@ -64,6 +64,7 @@
private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
private final Context mContext;
+ private final ConnectivityResources mResources;
private final Handler mHandler;
private final Runnable mAvoidBadWifiCallback;
private final List<Uri> mSettingsUris;
@@ -107,11 +108,12 @@
public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
mContext = ctx;
+ mResources = new ConnectivityResources(ctx);
mHandler = handler;
mAvoidBadWifiCallback = avoidBadWifiCallback;
mSettingsUris = Arrays.asList(
- Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
- Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
+ Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
+ Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
mResolver = mContext.getContentResolver();
mSettingObserver = new SettingObserver();
mBroadcastReceiver = new BroadcastReceiver() {
@@ -160,12 +162,16 @@
* Whether the device or carrier configuration disables avoiding bad wifi by default.
*/
public boolean configRestrictsAvoidBadWifi() {
- return (getResourcesForActiveSubId().getInteger(R.integer.config_networkAvoidBadWifi) == 0);
+ // TODO: use R.integer.config_networkAvoidBadWifi directly
+ final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi",
+ "integer", mResources.getResourcesContext().getPackageName());
+ return (getResourcesForActiveSubId().getInteger(id) == 0);
}
@NonNull
private Resources getResourcesForActiveSubId() {
- return SubscriptionManager.getResourcesForSubId(mContext, mActiveSubId);
+ return SubscriptionManager.getResourcesForSubId(
+ mResources.getResourcesContext(), mActiveSubId);
}
/**
@@ -205,8 +211,10 @@
* The default (device and carrier-dependent) value for metered multipath preference.
*/
public int configMeteredMultipathPreference() {
- return mContext.getResources().getInteger(
- R.integer.config_networkMeteredMultipathPreference);
+ // TODO: use R.integer.config_networkMeteredMultipathPreference directly
+ final int id = mResources.get().getIdentifier("config_networkMeteredMultipathPreference",
+ "integer", mResources.getResourcesContext().getPackageName());
+ return mResources.get().getInteger(id);
}
public void updateMeteredMultipathPreference() {
diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
index f0193db..18d26a7 100644
--- a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
+++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
@@ -19,11 +19,12 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.NetworkScore;
import android.net.QosSession;
import android.telephony.data.EpsBearerQosSessionAttributes;
/**
- * Interface for NetworkAgents to send network network properties.
+ * Interface for NetworkAgents to send network properties.
* @hide
*/
oneway interface INetworkAgentRegistry {
@@ -31,7 +32,7 @@
void sendLinkProperties(in LinkProperties lp);
// TODO: consider replacing this by "markConnected()" and removing
void sendNetworkInfo(in NetworkInfo info);
- void sendScore(int score);
+ void sendScore(in NetworkScore score);
void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial);
void sendSocketKeepaliveEvent(int slot, int reason);
void sendUnderlyingNetworks(in @nullable List<Network> networks);
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
index f2446b7..fa4501a 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
+++ b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
@@ -21,7 +21,7 @@
android_app {
name: "ServiceConnectivityResources",
- sdk_version: "system_current",
+ sdk_version: "module_current",
resource_dirs: [
"res",
],
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc204-mnc04/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc204-mnc04/config.xml
new file mode 100644
index 0000000..7e7025f
--- /dev/null
+++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc204-mnc04/config.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<!-- Configuration values for ConnectivityService
+ DO NOT EDIT THIS FILE for specific device configuration; instead, use a Runtime Resources
+ Overlay package following the overlayable.xml configuration in the same directory:
+ https://source.android.com/devices/architecture/rros -->
+<resources>
+ <!-- Whether the device should automatically switch away from Wi-Fi networks that lose
+ Internet access. Actual device behaviour is controlled by
+ Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
+ <integer translatable="false" name="config_networkAvoidBadWifi">0</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc310-mnc004/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc310-mnc004/config.xml
new file mode 100644
index 0000000..7e7025f
--- /dev/null
+++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc310-mnc004/config.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<!-- Configuration values for ConnectivityService
+ DO NOT EDIT THIS FILE for specific device configuration; instead, use a Runtime Resources
+ Overlay package following the overlayable.xml configuration in the same directory:
+ https://source.android.com/devices/architecture/rros -->
+<resources>
+ <!-- Whether the device should automatically switch away from Wi-Fi networks that lose
+ Internet access. Actual device behaviour is controlled by
+ Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
+ <integer translatable="false" name="config_networkAvoidBadWifi">0</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml
new file mode 100644
index 0000000..7e7025f
--- /dev/null
+++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<!-- Configuration values for ConnectivityService
+ DO NOT EDIT THIS FILE for specific device configuration; instead, use a Runtime Resources
+ Overlay package following the overlayable.xml configuration in the same directory:
+ https://source.android.com/devices/architecture/rros -->
+<resources>
+ <!-- Whether the device should automatically switch away from Wi-Fi networks that lose
+ Internet access. Actual device behaviour is controlled by
+ Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
+ <integer translatable="false" name="config_networkAvoidBadWifi">0</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml
index 7d98c76..71674e4 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml
+++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/config.xml
@@ -42,4 +42,51 @@
-->
</string-array>
-</resources>
\ No newline at end of file
+ <string-array translatable="false" name="config_legacy_networktype_restore_timers">
+ <item>2,60000</item><!-- mobile_mms -->
+ <item>3,60000</item><!-- mobile_supl -->
+ <item>4,60000</item><!-- mobile_dun -->
+ <item>5,60000</item><!-- mobile_hipri -->
+ <item>10,60000</item><!-- mobile_fota -->
+ <item>11,60000</item><!-- mobile_ims -->
+ <item>12,60000</item><!-- mobile_cbs -->
+ </string-array>
+
+ <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
+ Those frames are identified by the field Eth-type having values
+ less than 0x600 -->
+ <bool translatable="false" name="config_apfDrop802_3Frames">true</bool>
+
+ <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
+ will be dropped
+ TODO: need to put proper values, these are for testing purposes only -->
+ <integer-array translatable="false" name="config_apfEthTypeDenyList">
+ <item>0x88A2</item>
+ <item>0x88A4</item>
+ <item>0x88B8</item>
+ <item>0x88CD</item>
+ <item>0x88E3</item>
+ </integer-array>
+
+ <!-- Default supported concurrent socket keepalive slots per transport type, used by
+ ConnectivityManager.createSocketKeepalive() for calculating the number of keepalive
+ offload slots that should be reserved for privileged access. This string array should be
+ overridden by the device to present the capability of creating socket keepalives. -->
+ <!-- An Array of "[NetworkCapabilities.TRANSPORT_*],[supported keepalives] -->
+ <string-array translatable="false" name="config_networkSupportedKeepaliveCount">
+ <item>0,1</item>
+ <item>1,3</item>
+ </string-array>
+
+
+ <!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual
+ device behaviour is controlled by the metered multipath preference in
+ ConnectivitySettingsManager. This is the default value of that setting. -->
+ <integer translatable="false" name="config_networkMeteredMultipathPreference">0</integer>
+
+ <!-- Whether the device should automatically switch away from Wi-Fi networks that lose
+ Internet access. Actual device behaviour is controlled by
+ Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
+ <integer translatable="false" name="config_networkAvoidBadWifi">1</integer>
+
+</resources>
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml
index 00ec2df..25e19ce 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/packages/Connectivity/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -17,10 +17,15 @@
<overlayable name="ServiceConnectivityResourcesConfig">
<policy type="product|system|vendor">
<!-- Configuration values for ConnectivityService -->
+ <item type="array" name="config_legacy_networktype_restore_timers"/>
<item type="string" name="config_networkCaptivePortalServerUrl"/>
<item type="integer" name="config_networkTransitionTimeout"/>
<item type="array" name="config_wakeonlan_supported_interfaces"/>
-
+ <item type="bool" name="config_apfDrop802_3Frames"/>
+ <item type="array" name="config_apfEthTypeDenyList"/>
+ <item type="integer" name="config_networkMeteredMultipathPreference"/>
+ <item type="array" name="config_networkSupportedKeepaliveCount"/>
+ <item type="integer" name="config_networkAvoidBadWifi"/>
</policy>
</overlayable>
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 63edc77..4344e94 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -66,6 +66,8 @@
private static final String INCREMENTAL_DIR = "_delta";
private static final String FULL_DATA_DIR = "_full";
+ private static final String DEVICE_NAME_FOR_D2D_RESTORE_SET = "D2D";
+ private static final String DEFAULT_DEVICE_NAME_FOR_RESTORE_SET = "flash";
// The currently-active restore set always has the same (nonzero!) token
private static final long CURRENT_SET_TOKEN = 1;
@@ -603,8 +605,10 @@
existing[num++] = CURRENT_SET_TOKEN;
RestoreSet[] available = new RestoreSet[num];
+ String deviceName = mParameters.isDeviceTransfer() ? DEVICE_NAME_FOR_D2D_RESTORE_SET
+ : DEFAULT_DEVICE_NAME_FOR_RESTORE_SET;
for (int i = 0; i < available.length; i++) {
- available[i] = new RestoreSet("Local disk image", "flash", existing[i]);
+ available[i] = new RestoreSet("Local disk image", deviceName, existing[i]);
}
return available;
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index f2a0e1c..68ce7d9 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",
@@ -67,6 +70,7 @@
"SettingsLibFooterPreference",
"SettingsLibUsageProgressBarPreference",
"SettingsLibCollapsingToolbarBaseActivity",
+ "SettingsLibTwoTargetPreference",
],
}
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
index 781bfcd..1ccf417 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
@@ -51,6 +51,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
+ setSingleLineTitle(true);
super.onBindViewHolder(holder);
final View switchView = holder.findViewById(android.R.id.switch_widget);
if (switchView != null) {
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
new file mode 100644
index 0000000..f2e79b9
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -0,0 +1,13 @@
+android_library {
+ name: "SettingsLibTwoTargetPreference",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ ],
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/AndroidManifest.xml b/packages/SettingsLib/TwoTargetPreference/AndroidManifest.xml
new file mode 100644
index 0000000..120b085
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/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.widget">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout/preference_two_target.xml
similarity index 98%
rename from packages/SettingsLib/res/layout/preference_two_target.xml
rename to packages/SettingsLib/TwoTargetPreference/res/layout/preference_two_target.xml
index ff6a22d..21fcedc 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout/preference_two_target.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2017 The Android Open Source Project
+ Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/SettingsLib/res/layout/preference_two_target_divider.xml b/packages/SettingsLib/TwoTargetPreference/res/layout/preference_two_target_divider.xml
similarity index 95%
rename from packages/SettingsLib/res/layout/preference_two_target_divider.xml
rename to packages/SettingsLib/TwoTargetPreference/res/layout/preference_two_target_divider.xml
index b81dd83..bd477f8 100644
--- a/packages/SettingsLib/res/layout/preference_two_target_divider.xml
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout/preference_two_target_divider.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2017 The Android Open Source Project
+ Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/SettingsLib/TwoTargetPreference/res/values/dimens.xml b/packages/SettingsLib/TwoTargetPreference/res/values/dimens.xml
new file mode 100644
index 0000000..32a8659
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?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>
+
+ <dimen name="two_target_pref_small_icon_size">24dp</dimen>
+ <dimen name="two_target_pref_medium_icon_size">32dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
similarity index 94%
rename from packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
rename to packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index 02895a4..9130662 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,21 +14,24 @@
* limitations under the License.
*/
-package com.android.settingslib;
+package com.android.settingslib.widget;
-import android.annotation.IntDef;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.annotation.IntDef;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+/**
+ * The Base preference with two target areas divided by a vertical divider
+ */
public class TwoTargetPreference extends Preference {
@IntDef({ICON_SIZE_DEFAULT, ICON_SIZE_MEDIUM, ICON_SIZE_SMALL})
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 1a67b5e..0ee44f8 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rooi-groen)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blou-geel)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurregstelling"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Verstel hoe kleure op jou toestel vertoon. Dit kan nuttig wees wanneer jy:<br/><br/> <ol> <li> Kleure meer akkuraat wil sien</li> <li> Kleure wil verwyder om jou te help fokus</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <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">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Totdat jy dit afskakel"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Sopas"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Foonluidspreker"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Hierdie foon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Kan nie koppel nie. Skakel toestel af en weer aan"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Bedrade oudiotoestel"</string>
<string name="help_label" msgid="3528360748637781274">"Hulp en terugvoer"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 97abce1..9fb676f 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"ቀለሞች በመሣሪያዎ ላይ እንዴት እንደሚታዩ ያስተካክሉ። የሚከተሉትን ለማድረግ በሚፈልጉበት ጊዜ ይህ ጠቃሚ ሊሆን ይችላል፦<br/><br/> <ol> <li> ቀለሞችን የበለጠ ትክክለኛ በሆነ መልኩ ለመመልከት</li> <li> ትኩረት ለማድረግ እንዲረዳዎ ቀለሞችን ለማስወገድ</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"እስኪያጠፉት ድረስ"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ልክ አሁን"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"የስልክ ድምጽ ማጉያ"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ይህ ስልክ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"መገናኘት ላይ ችግር። መሳሪያውን ያጥፉት እና እንደገና ያብሩት"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ባለገመድ የኦዲዮ መሣሪያ"</string>
<string name="help_label" msgid="3528360748637781274">"እገዛ እና ግብረመልስ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index f9e12c7..cdb418a 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"يمكنك تعديل كيفية عرض الألوان على جهازك. يساعدك هذا الخيار عندما تريد تنفيذ ما يلي:<br/><br/> <ol> <li> عرض الألوان بمزيد من الدقة</li> <li> إزالة الألوان لمساعدتك على التركيز</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>
@@ -520,8 +519,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"إلى أن يتم إيقاف الوضع"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"للتو"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"مكبر صوت الهاتف"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"هذا الهاتف"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"حدثت مشكلة أثناء الاتصال. يُرجى إيقاف الجهاز ثم إعادة تشغيله."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"جهاز سماعي سلكي"</string>
<string name="help_label" msgid="3528360748637781274">"المساعدة والملاحظات والآراء"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index efd813a..442a19d 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"আপোনাৰ ডিভাইচত ৰংবোৰ কেনেকৈ প্ৰদৰ্শিত হয় সেয়া মিলাওক। এইটো আপুনি এই কাৰ্য কৰিবলৈ বিচাৰিলে সহায়ক হ\'ব পাৰে:<br/><br/> <ol> <li> ৰং অধিক সঠিককৈ চাবলৈ বিচৰা</li> <li> আপোনাক মনোযোগ দিয়াত সহায় কৰিবলৈ ৰং আঁতৰোৱা</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"আপুনি অফ নকৰা পর্যন্ত"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"এই মাত্ৰ"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ফ’নৰ স্পীকাৰ"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"এই ফ’নটো"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"সংযোগ হোৱাত সমস্যা হৈছে। ডিভাইচটো অফ কৰি পুনৰ অন কৰক"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"তাঁৰযুক্ত অডিঅ’ ডিভাইচ"</string>
<string name="help_label" msgid="3528360748637781274">"সহায় আৰু মতামত"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 7a5aec4..950a5ae 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Наладзьце адлюстраванне колераў на экране прылады. Гэта налада можа быць карыснай, калі вы захочаце:<br/><br/> <ol> <li> бачыць колеры больш дакладна;</li> <li> выдаліць колеры, якія перашкаджаюць вам сканцэнтравацца</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>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Пакуль не выключыце"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Толькі што"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Дынамік тэлефона"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Гэты тэлефон"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Праблема з падключэннем. Выключыце і зноў уключыце прыладу"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Правадная аўдыяпрылада"</string>
<string name="help_label" msgid="3528360748637781274">"Даведка і водгукі"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 675d179..4cc9abf 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"আপনার ডিভাইসে রঙগুলি কেমন দেখাবে তা অ্যাডজাস্ট করুন। যেক্ষেত্রে এটি আপনাকে সহায়তা করতে পারে:<br/><br/> <ol> <li> আরও নির্ভুলভাবে রঙ দেখতে</li> <li> রঙ সরিয়ে দিলে ফোকাস করতে সহায়তা করবে</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"যতক্ষণ না আপনি বন্ধ করছেন"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"এখনই"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ফোনের স্পিকার"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"এই ফোনটি"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"কানেক্ট করতে সমস্যা হচ্ছে। ডিভাইস বন্ধ করে আবার চালু করুন"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ওয়্যার অডিও ডিভাইস"</string>
<string name="help_label" msgid="3528360748637781274">"সহায়তা ও মতামত"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 97083ad..e4bc5a1 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ispravka boje"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Podešavanje načina na koji se boje prikazuju na uređaju. To može biti korisno kada želite:<br/><br/> <ol> <li> tačnije prikazati boje</li> <li> ukloniti boje da se lakše fokusirate</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <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">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -517,8 +516,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Dok ne isključite"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Upravo"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Zvučnik telefona"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ovaj telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Došlo je do problema prilikom povezivanja. Isključite, pa ponovo uključite uređaj"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audio uređaj"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 4889220..fa441a2 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermell-verd)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (blau-groc)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correcció de color"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajusta com es mostren els colors al teu dispositiu. Això pot ser útil quan vulguis:<br/><br/> <ol> <li> Veure els colors amb més claredat.</li> <li> Suprimir colors per poder enfocar més fàcilment.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <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 aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Fins que no el desactivis"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Ara mateix"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altaveu del telèfon"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Aquest telèfon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Hi ha hagut un problema amb la connexió. Desactiva el dispositiu i torna\'l a activar."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositiu d\'àudio amb cable"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda i suggeriments"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index e857e60..717b681 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomálie (červená a zelená)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomálie (modrá a žlutá)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekce barev"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Upravte zobrazování barev na svém zařízení. To se může hodit, když chcete:<br/><br/> <ol> <li> Aby se barvy zobrazovaly přesněji</li> <li> Odstranit barvy, abyste se mohli lépe soustředit</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <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">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Dokud tuto funkci nevypnete"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Právě teď"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Reproduktor telefonu"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Tento telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problém s připojením. Vypněte zařízení a znovu jej zapněte"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kabelové audiozařízení"</string>
<string name="help_label" msgid="3528360748637781274">"Nápověda a zpětná vazba"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 53e21ba..6b17db1 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopi (rød-grøn)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopi (blå-gul)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korriger farver"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Juster, hvordan farverne vises på skærmen. Dette kan være nyttigt, når du vil:<br/><br/> <ol> <li> Se farver mere nøjagtigt</li> <li> Fjerne farver, så du bedre kan fokusere</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <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">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Indtil du deaktiverer"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Lige nu"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefonens højttaler"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Denne telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Der kunne ikke oprettes forbindelse. Sluk og tænd enheden"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Lydenhed med ledning"</string>
<string name="help_label" msgid="3528360748637781274">"Hjælp og feedback"</string>
@@ -541,7 +539,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Begrænset profil"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Vil du tilføje en ny bruger?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dele denne enhed med andre ved at oprette ekstra brugere. Hver bruger har sit personlige område, som kan tilpasses med apps, baggrund osv. Brugerne kan også justere enhedsindstillinger, som for eksempel Wi-Fi, som påvirker alle.\n\nNår du tilføjer en ny bruger, skal vedkommende konfigurere sit område.\n\nAlle brugere kan opdatere apps for alle andre brugere. Indstillinger og tjenester for hjælpefunktioner overføres muligvis ikke til den nye bruger."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"Når du tilføjer en ny bruger, skal personen konfigurere sit rum.\n\nEnhver bruger kan opdatere apps for alle andre brugere."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"Når du tilføjer en ny bruger, skal personen konfigurere sit rum.\n\nAlle brugere kan opdatere apps for alle de andre brugere."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Vil du konfigurere brugeren nu?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Sørg for, at brugeren har mulighed for at tage enheden og konfigurere sit eget rum"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Vil du oprette en profil nu?"</string>
@@ -557,7 +555,7 @@
<string name="user_switch_to_user" msgid="6975428297154968543">"Skift til <xliff:g id="USER_NAME">%s</xliff:g>"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string>
<string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæst"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index a4a976f..bb11ed1 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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"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> Wenn Farben genauer dargestellt werden sollen</li> <li> 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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Bis zur Deaktivierung"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Gerade eben"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Smartphone-Lautsprecher"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Dieses Smartphone"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Verbindung kann nicht hergestellt werden. Schalte das Gerät aus & und wieder ein."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Netzbetriebenes Audiogerät"</string>
<string name="help_label" msgid="3528360748637781274">"Hilfe und Feedback"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 71cf5cb..fb294c8 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajusta cómo se muestran los colores en tu dispositivo. Esto puede ser útil cuando quieres:<br/><br/> <ol> <li> Ver colores con más exactitud</li> <li> Quitar colores para mejorar tu concentración</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <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">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Hasta que lo desactives"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Recién"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altavoz del teléfono"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este teléfono"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Error al establecer la conexión. Apaga el dispositivo y vuelve a encenderlo."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
<string name="help_label" msgid="3528360748637781274">"Ayuda y comentarios"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index a7d7b21..2c5eff2 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajusta el modo en que se muestran los colores en tu dispositivo. Esto puede ser útil cuando quieras hacer lo siguiente:<br/><br/> <ol> <li> Ver los colores con más precisión</li> <li> Eliminar colores para ayudarte a mantener la concentración</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <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">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Hasta que lo desactives"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"justo ahora"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altavoz del teléfono"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este teléfono"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"No se ha podido conectar; reinicia el dispositivo"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
<string name="help_label" msgid="3528360748637781274">"Ayuda y comentarios"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index add25f8..116faad 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaalia (punane-roheline)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaalia (sinine-kollane)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värvide korrigeerimine"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Kohandage seadmes värvide kuvamist. Sellest võib olla kasu, kui soovite:<br/><br/> <ol> <li> värve täpsemalt näha;</li> <li> värve eemaldada, et paremini keskenduda.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <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">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Kuni välja lülitate"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Äsja"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefoni kõlar"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"See telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Probleem ühendamisel. Lülitage seade välja ja uuesti sisse"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Juhtmega heliseade"</string>
<string name="help_label" msgid="3528360748637781274">"Abi ja tagasiside"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index c95d157..bfb4efc 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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Doitu nola bistaratzen diren koloreak gailuan. Kasu hauetan izan daiteke lagungarria:<br/><br/> <ol> <li> Koloreak zehatzago ikusi nahi dituzunean.</li> <li> 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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Zuk desaktibatu arte"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Oraintxe"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefonoaren bozgorailua"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Telefono hau"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Arazoren bat izan da konektatzean. Itzali gailua eta pitz ezazu berriro."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Audio-gailu kableduna"</string>
<string name="help_label" msgid="3528360748637781274">"Laguntza eta iritziak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 631ff5e..f3018ba 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"نحوه نمایش رنگها را در دستگاهتان تنظیم میکند. این ویژگی میتواند در موارد زیر مفید باشد:<br/><br/> <ol> <li> وقتی میخواهید رنگها را با دقت بیشتری ببینید</li> <li> وقتی میخواهید رنگها را حذف کنید تا تمرکز بیشتری داشته باشید"</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"تا زمانیکه آن را خاموش کنید"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"هماکنون"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"بلندگوی تلفن"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"این تلفن"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"مشکل در اتصال. دستگاه را خاموش و دوباره روشن کنید"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"دستگاه صوتی سیمی"</string>
<string name="help_label" msgid="3528360748637781274">"راهنما و بازخورد"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 7591c91..7089937 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (puna-vihersokeus)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (sini-keltasokeus)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värinkorjaus"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Muuta värien näkymistä laitteellasi. Tästä voi olla hyötyä, kun haluat<br/><br/> <ol> <li> nähdä värit tarkemmin</li> <li> poistaa värejä voidaksesi keskittyä paremmin</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <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">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Kunnes laitat pois päältä"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Äsken"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Puhelimen kaiutin"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Tämä puhelin"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Yhteysvirhe. Sammuta laite ja käynnistä se uudelleen."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Langallinen äänilaite"</string>
<string name="help_label" msgid="3528360748637781274">"Ohje ja palaute"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index a390257..ac8adde 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajustez l\'affichage des couleurs sur votre appareil. Ce paramètre peut être utile si vous voulez :<br/><br/> <ol> <li> Mieux distinguer les couleurs</li> <li> Enlever les couleurs pour vous aider à 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">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Jusqu\'à la désactivation"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"À l\'instant"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Haut-parleur du téléphone"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ce téléphone"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problème de connexion. Éteingez et rallumez l\'appareil"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Appareil audio à câble"</string>
<string name="help_label" msgid="3528360748637781274">"Aide et commentaires"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 8cf8c6e..e4e4293 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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajustez l\'affichage des couleurs sur votre appareil. Cette option peut vous être utile pour :<br/><br/> <ol> <li> accentuer la précision des couleurs ;</li> <li> 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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Jusqu\'à la désactivation"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"À l\'instant"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Haut-parleur du téléphone"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ce téléphone"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problème de connexion. Éteignez l\'appareil, puis rallumez-le"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Appareil audio filaire"</string>
<string name="help_label" msgid="3528360748637781274">"Aide et commentaires"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index c1ba51d..062b7b3 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (vermello-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarelo)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección da cor"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Axusta a maneira en que se mostran as cores no teu dispositivo. Esta opción pode resultarche útil se queres:<br/><br/> <ol> <li> Ver mellor as cores</li> <li> Quitar as cores para concentrarte mellor</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <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">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Ata a desactivación"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Agora mesmo"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altofalante do teléfono"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este teléfono"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Produciuse un problema coa conexión. Apaga e acende o dispositivo."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de audio con cable"</string>
<string name="help_label" msgid="3528360748637781274">"Axuda e comentarios"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 94d83e0..174d0a1 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"તમારા ડિવાઇસ પર રંગો કેવી રીતે બતાવવામાં આવે તેની ગોઠવણી કરો. આ ત્યારે સહાયરૂપ થઈ શકે છે જ્યારે તમારે:<br/><br/> <ol> <li> રંગો વધુ યોગ્ય રીતે જોવા હોય</li> <li> તમને ફોકસ કરવામાં સહાયતા રહે તે માટે રંગો કાઢી નાખવા હોય</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"તમે બંધ ન કરો ત્યાં સુધી"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"હમણાં જ"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ફોન સ્પીકર"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"આ ફોન"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"કનેક્ટ કરવામાં સમસ્યા આવી રહી છે. ડિવાઇસને બંધ કરીને ફરી ચાલુ કરો"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"વાયરવાળો ઑડિયો ડિવાઇસ"</string>
<string name="help_label" msgid="3528360748637781274">"સહાય અને પ્રતિસાદ"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index e99d4cb..3d06baa 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno – zeleno)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo – žuto)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boje"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Prilagodite način prikazivanja boja na svojem uređaju. To može biti korisno kad želite:<br/><br/> <ol> <li> vidjeti boje točnije</li> <li> ukloniti boje kako biste se lakše usredotočili</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <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">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -517,8 +516,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Dok ne isključite"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Upravo sad"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Zvučnik telefona"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ovaj telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem s povezivanjem. Isključite i ponovo uključite uređaj"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žičani audiouređaj"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 503ee60..546a038 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (piros– zöld)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (kék–sárga)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Színkorrekció"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Korrigálhatja a színek megjelenítését az eszközén. Ez a következő esetekben lehet hasznos:<br/><br/> <ol> <li> ha pontosabb színeket szeretne látni;</li> <li> ha szeretné eltávolítani a színeket, hogy jobban tudjon koncentrálni.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <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">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Kikapcsolásig"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Az imént"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefon hangszórója"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ez a telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Sikertelen csatlakozás. Kapcsolja ki az eszközt, majd kapcsolja be újra."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Vezetékes audioeszköz"</string>
<string name="help_label" msgid="3528360748637781274">"Súgó és visszajelzés"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 3686dd1..7c0963d 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Կարգավորեք գույների ցուցադրումը ձեր սարքում։ Դա կարող է օգտակար լինել, երբ դուք ուզում եք՝<br/><br/> <ol> <li> Ավելի հստակ տեսնել գույները</li> <li> Հեռացնել գույները՝ կենտրոնանալու համար</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Մինչև չանջատեք"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Հենց նոր"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Հեռախոսի բարձրախոս"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Այս հեռախոսը"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Կապի խնդիր կա: Սարքն անջատեք և նորից միացրեք:"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Լարով աուդիո սարք"</string>
<string name="help_label" msgid="3528360748637781274">"Օգնություն և հետադարձ կապ"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 035be7d..2f8bb46 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koreksi warna"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Sesuaikan cara warna ditampilkan di perangkat Anda. Ini dapat bermanfaat saat Anda ingin:<br/><br/> <ol> <li> Melihat warna dengan lebih akurat</li> <li> Menghapus warna untuk membantu Anda fokus</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <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">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Sampai Anda menonaktifkannya"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Baru saja"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Speaker ponsel"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ponsel ini"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ada masalah saat menghubungkan. Nonaktifkan perangkat & aktifkan kembali"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Perangkat audio berkabel"</string>
<string name="help_label" msgid="3528360748637781274">"Bantuan & masukan"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 718b5be..ea29258 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Litblinda (rauðgræn)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Litblinda (blágul)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Litaleiðrétting"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Stilltu litabirtingu í tækinu þínu. Þetta getur gagnast þegar þú vilt:<br/><br/> <ol> <li> Sjá liti skýrar</li> <li> Fjarlægja liti til að fókusa betur</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <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">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Þar til þú slekkur"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Rétt í þessu"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Símahátalari"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Þessi sími"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Vandamál í tengingu. Slökktu og kveiktu á tækinu"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Snúrutengt hljómtæki"</string>
<string name="help_label" msgid="3528360748637781274">"Hjálp og ábendingar"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index d2176d9..5dd7a52 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalìa (rosso-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalìa (blu-giallo)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correzione del colore"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Regola la modalità di visualizzazione dei colori sul tuo dispositivo. Può essere utile se vuoi:<br/><br/> <ol> <li> Vedere i colori con più precisione</li> <li> Rimuovere colori per mettere a fuoco più facilmente</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <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">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Fino alla disattivazione"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Adesso"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altoparlante telefono"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Questo telefono"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problema di connessione. Spegni e riaccendi il dispositivo"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo audio cablato"</string>
<string name="help_label" msgid="3528360748637781274">"Guida e feedback"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 1357cae..20d1e1b 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"ניתן לשנות את האופן שבו צבעים מוצגים במכשיר. שינוי כזה עשוי לעזור:<br/><br/> <ol> <li> להבחין בצבעים בצורה יותר מדויקת</li> <li> להסיר צבעים מסוימים כדי להתמקד</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>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"עד הכיבוי"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"הרגע"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"רמקול של טלפון"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"הטלפון הזה"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"יש בעיה בחיבור. עליך לכבות את המכשיר ולהפעיל אותו מחדש"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"התקן אודיו חוטי"</string>
<string name="help_label" msgid="3528360748637781274">"עזרה ומשוב"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 8bb6ba8..37db15c 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"დააკორექტირეთ, როგორ გამოჩნდება ფერები თქვენს მოწყობილობაზე. ეს შეიძლება დაგეხმაროთ, როდესაც გსურთ:<br/><br/> <ol> <li> ფერების მეტი სიზუსტით დანახვა</li> <li> ფერების მოცილება, რომ უკეთ კონცენტრირდეთ</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"გამორთვამდე"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ახლახან"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ტელეფონის დინამიკი"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ეს ტელეფონი"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"დაკავშირებისას წარმოიქმნა პრობლემა. გამორთეთ და კვლავ ჩართეთ მოწყობილობა"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"სადენიანი აუდიო მოწყობილობა"</string>
<string name="help_label" msgid="3528360748637781274">"დახმარება და გამოხმაურება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 6a2c21e..878966d 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Құрылғыңызда түстер қалай көрсетілетінін реттеңіз. Бұл мыналар үшін пайдалы болуы мүмкін:<br/><br/> <ol> <li> түстерді анығырақ көру</li> <li> зейініңізді жақсарту үшін түстерді өшіру</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>
@@ -503,7 +502,7 @@
<string name="cancel" msgid="5665114069455378395">"Бас тарту"</string>
<string name="okay" msgid="949938843324579502">"Жарайды"</string>
<string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Қосу"</string>
- <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"\"Мазаламау\" режимін қосу"</string>
+ <string name="zen_mode_settings_turn_on_dialog_title" msgid="2760567063190790696">"Мазаламау режимін қосу"</string>
<string name="zen_mode_settings_summary_off" msgid="3832876036123504076">"Ешқашан"</string>
<string name="zen_interruption_level_priority" msgid="5392140786447823299">"Маңыздылары ғана"</string>
<string name="zen_mode_and_condition" msgid="8877086090066332516">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Өшірілгенге дейін"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Дәл қазір"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Телефон динамигі"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Осы телефон"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Байланыс орнату қатесі шығуып жатыр. Құрылғыны өшіріп, қайта қосыңыз."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Сымды аудио құрылғысы"</string>
<string name="help_label" msgid="3528360748637781274">"Анықтама және пікір"</string>
@@ -557,8 +555,8 @@
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> пайдаланушысына ауысу"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string>
<string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string>
- <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Қонақ қосу"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты жою"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index f4cece4..708879a 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើតអ្នកប្រើប្រាស់ថ្មី…"</string>
<string name="user_nickname" msgid="262624187455825083">"ឈ្មោះហៅក្រៅ"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូលភ្ញៀវ"</string>
- <string name="guest_exit_guest" msgid="5908239569510734136">"លុបភ្ញៀវ"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"ដកភ្ញៀវចេញ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើសរូបភាព"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index a844ee6..daf7b6f 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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಬಣ್ಣಗಳು ಹೇಗೆ ಡಿಸ್ಪ್ಲೇ ಆಗುತ್ತವೆ ಎಂಬುದನ್ನು ಹೊಂದಿಸಿ. ನೀವು ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ನೋಡಲು ಬಯಸಿದಾಗ:<br/><br/> <ol> <li> ಇದು ಸಹಾಯಕವಾಗಿರುತ್ತದೆ</li> <li> ನಿಮಗೆ ಗಮನಹರಿಸಲು ಸಹಾಯ ಮಾಡಲು ಬಣ್ಣಗಳನ್ನು ತೆಗೆದುಹಾಕಿ</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"ನೀವು ಆಫ್ ಮಾಡುವವರೆಗೆ"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ಇದೀಗ"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ಫೋನ್ ಸ್ಪೀಕರ್"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ಈ ಫೋನ್"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ಕನೆಕ್ಟ್ ಮಾಡುವಾಗ ಸಮಸ್ಯೆ ಎದುರಾಗಿದೆ ಸಾಧನವನ್ನು ಆಫ್ ಮಾಡಿ ಹಾಗೂ ನಂತರ ಪುನಃ ಆನ್ ಮಾಡಿ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ವೈರ್ ಹೊಂದಿರುವ ಆಡಿಯೋ ಸಾಧನ"</string>
<string name="help_label" msgid="3528360748637781274">"ಸಹಾಯ ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆ"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 4ca6567..834200d 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Түзмөгүңүздө түстөр кантип көрүнөрүн тууралаңыз. Бул төмөнкү учурларда пайдалуу болот:<br/><br/> <ol> <li> Түстөрдү даана көрүү</li> <li> Ынтаа коюу үчүн түстөрдү өчүрүү</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Бул функция өчүрүлгөнгө чейин"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Жаңы эле"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Телефондун динамиги"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ушул телефон"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Туташууда маселе келип чыкты. Түзмөктү өчүрүп, кайра күйгүзүп көрүңүз"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Зымдуу аудио түзмөк"</string>
<string name="help_label" msgid="3528360748637781274">"Жардам/Пикир билдирүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 176c80f..0dcf1627 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"ປັບແກ້ການສະແດງສີຢູ່ອຸປະກອນຂອງທ່ານ. ນີ້ອາດມີປະໂຫຍດໃນເວລາທີ່ທ່ານຕ້ອງການ:<br/><br/> <ol> <li> ເບິ່ງເຫັນສີໄດ້ຖືກຕ້ອງຍິ່ງຂຶ້ນ</li> <li> ລຶບສີອອກເພື່ອຊ່ວຍໃຫ້ທ່ານມີສະມາທິ</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"ຈົນກວ່າທ່ານຈະປິດ"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ຕອນນີ້"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ລຳໂພງໂທລະສັບ"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ໂທລະສັບນີ້"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ເກີດບັນຫາໃນການເຊື່ອມຕໍ່. ປິດອຸປະກອນແລ້ວເປີດກັບຄືນມາໃໝ່"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ອຸປະກອນສຽງແບບມີສາຍ"</string>
<string name="help_label" msgid="3528360748637781274">"ຊ່ວຍເຫຼືອ ແລະ ຕິຊົມ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index b0ab8f4..c3f9202 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (raudona, žalia)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (mėlyna, geltona)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Spalvų taisymas"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Koreguokite, kaip spalvos rodomos jūsų įrenginyje. Tai gali būti naudinga, kai norite:<br/><br/> <ol> <li> matyti tikslesnes spalvas;</li> <li> pašalinti spalvas, kad būtų lengviau susitelkti.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nepaisyta naudojant nuostatą „<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">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Kol išjungsite"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Ką tik"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefono garsiakalbis"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Šis telefonas"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Prisijungiant kilo problema. Išjunkite įrenginį ir vėl jį įjunkite"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Laidinis garso įrenginys"</string>
<string name="help_label" msgid="3528360748637781274">"Pagalba ir atsiliepimai"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 2633f13..5090df0 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomālija (sarkans/zaļš)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomālija (zils/dzeltens)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Krāsu korekcija"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Pielāgojiet krāsu attēlojumu jūsu ierīcē. Izmantojiet šo funkciju, lai:<br/><br/> <ol> <li> skatītu precīzāku krāsu attēlojumu;</li> <li> noņemtu krāsas, kad jāpievēršas kādam uzdevumam.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <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">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -517,8 +516,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Līdz brīdim, kad izslēgsiet"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Tikko"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Tālruņa skaļrunis"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Šis tālrunis"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Radās problēma ar savienojuma izveidi. Izslēdziet un atkal ieslēdziet ierīci."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Vadu audioierīce"</string>
<string name="help_label" msgid="3528360748637781274">"Palīdzība un atsauksmes"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index e4173d0..c4890d4 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Приспособете го приказот на боите на уредот. Ова е корисно кога сакате:<br/><br/> <ol> <li> да гледате попрецизни бои</li> <li> да отстраните бои за подобра концентрација</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Додека не го исклучите"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Неодамнешни"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Телефонски звучник"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Овој телефон"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Проблем со поврзување. Исклучете го уредот и повторно вклучете го"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Жичен аудиоуред"</string>
<string name="help_label" msgid="3528360748637781274">"Помош и повратни информации"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 3cfe479..829d474 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"നിങ്ങളുടെ ഉപകരണത്തിൽ നിറങ്ങൾ എങ്ങനെ പ്രദർശിപ്പിക്കണമെന്ന് ക്രമീകരിക്കുക. ഇനിപ്പറയുന്ന കാര്യങ്ങൾ ചെയ്യാൻ ആഗ്രഹിക്കുമ്പോൾ ഇത് സഹായകരമാകും:<br/><br/> <ol> <li> നിറങ്ങൾ കൂടുതൽ കൃത്യമായി കാണാൻ</li> <li> ഫോക്കസ് ചെയ്യാൻ നിങ്ങളെ സഹായിക്കുന്നതിന് നിറങ്ങൾ നീക്കം ചെയ്യാൻ</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"നിങ്ങൾ ഓഫാക്കുന്നത് വരെ"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ഇപ്പോൾ"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ഫോൺ സ്പീക്കർ"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ഈ ഫോൺ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"കണക്റ്റ് ചെയ്യുന്നതിൽ പ്രശ്നമുണ്ടായി. ഉപകരണം ഓഫാക്കി വീണ്ടും ഓണാക്കുക"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"വയർ മുഖേന ബന്ധിപ്പിച്ച ഓഡിയോ ഉപകരണം"</string>
<string name="help_label" msgid="3528360748637781274">"സഹായവും ഫീഡ്ബാക്കും"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index f8d9089..e9fe1dd 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"तुमच्या डिव्हाइसवर रंग कसे प्रदर्शित केले जातात ते अॅडजस्ट करा. तुम्हाला </br><br> </br><br> </br><br>अधिक स्पष्टपणे रंग पाहणे </br><br> </br><br> तुम्हाला फोकस करण्यात मदत करण्यासाठी रंग काढून टाकणे</br><br> </br><br> हे करायचे असते तेव्हा उपयुक्त असू शकते."</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"तुम्ही बंद करेपर्यंत"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"आत्ताच"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"फोनचा स्पीकर"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"हा फोन"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"कनेक्ट करण्यात समस्या आली. डिव्हाइस बंद करा आणि नंतर सुरू करा"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"वायर असलेले ऑडिओ डिव्हाइस"</string>
<string name="help_label" msgid="3528360748637781274">"मदत आणि फीडबॅक"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index f10e539..d80242ef 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"သင့်စက်ပစ္စည်းတွင် အရောင်များပြသပုံကို ချိန်ညှိပါ။ ၎င်းက အောက်ပါတို့တွင် အသုံးဝင်နိုင်သည်-<br/><br/> <ol> <li> အရောင်များကို ပိုမိုတိကျစွာ မြင်လိုခြင်း</li> <li> သင်အာရုံစိုက်နိုင်ရန် အရောင်များကို ဖယ်ရှားခြင်း</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"သင်ပိတ်လိုက်သည် အထိ"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ယခုလေးတင်"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ဖုန်းစပီကာ"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ဤဖုန်း"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ချိတ်ဆက်ရာတွင် ပြဿနာရှိပါသည်။ စက်ကိုပိတ်ပြီး ပြန်ဖွင့်ပါ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ကြိုးတပ် အသံစက်ပစ္စည်း"</string>
<string name="help_label" msgid="3528360748637781274">"အကူအညီနှင့် အကြံပြုချက်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9fb68d0..7b49b40 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rød-grønn)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blå-gul)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Fargekorrigering"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Juster hvordan farger vises på enheten. Dette kan være nyttig når du vil<br/><br/> <ol> <li> se farger mer nøyaktig</li> <li> fjerne farger for å gjøre det enklere å fokusere</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <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">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> gjenstår"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Til du slår av"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Nå nettopp"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefonhøyttaler"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Denne telefonen"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Tilkoblingsproblemer. Slå enheten av og på igjen"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Lydenhet med kabel"</string>
<string name="help_label" msgid="3528360748637781274">"Hjelp og tilbakemelding"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 4c847cf..4d380a5 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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"तपाईंको यन्त्रमा रङहरू कस्ता देखिन्छन् भन्ने कुरा मिलाउनुहोस्। यो सुविधा निम्न अवस्थामा उपयोगी हुन सक्छ:<br/><br/> <ol> <li> तपाईं अझ सटीक रूपमा रङहरू देख्न चाहनुहुन्छ भने</li> <li> तपाईं कुनै कुरामा ध्यान केन्द्रित गर्न रङहरू हटाउन चाहनुहुन्छ भने</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"तपाईंले निष्क्रिय नपार्दासम्म"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"अहिले भर्खरै"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"फोनको स्पिकर"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"यो फोन"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"जोड्ने क्रममा समस्या भयो। यन्त्रलाई निष्क्रिय पारेर फेरि सक्रिय गर्नुहोस्"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"तारयुक्त अडियो यन्त्र"</string>
<string name="help_label" msgid="3528360748637781274">"मद्दत र प्रतिक्रिया"</string>
@@ -541,7 +539,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"प्रतिबन्धित प्रोफाइल"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"नयाँ प्रयोगकर्ता थप्ने हो?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"तपाईं थप प्रयोगकर्ताहरू सिर्जना गरेर ती प्रयोगकर्तालाई यो यन्त्र प्रयोग गर्न दिन सक्नुहुन्छ। हरेक प्रयोगकर्ताको आफ्नै ठाउँ हुन्छ। उनीहरू यो ठाउँमा आफ्नै एप, वालपेपर आदिका लागि प्रयोग गर्न सक्छन्। उनीहरू सबैजनालाई असर पार्ने Wi-Fi जस्ता यन्त्रका सेटिङहरू पनि परिवर्तन गर्न सक्छन्।\n\nतपाईंले नयाँ प्रयोगकर्ता थप्दा उक्त व्यक्तिले आफ्नो ठाउँ सेटअप गर्नु पर्ने हुन्छ।\n\nसबै प्रयोगकर्ता अन्य सबै प्रयोगकर्ताले प्रयोग गर्ने एपहरू अद्यावधिक गर्न सक्छन्। तर पहुँचसम्बन्धी सेटिङ तथा सेवाहरू नयाँ प्रयोगकर्तामा नसर्न सक्छ।"</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"जब तपाईंले नयाँ प्रयोगकर्ता थप्नुहुन्छ, त्यो व्यक्तिले आफ्नो ठाउँ सेट गर्न आवश्यक छ।\n\nकुनै पनि प्रयोगकर्ताले सबै अन्य प्रयोगकर्ताहरूका लागि एपहरू अद्यावधिक गर्न सक्छन्।"</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"तपाईंले नयाँ प्रयोगकर्ता थप्नुभयो भने ती प्रयोगकर्ताले आफ्नो स्पेस सेट गर्नु पर्ने हुन्छ।\n\nसबै प्रयोगकर्ताले अरू प्रयोगकर्ताका एपहरू अपडेट गर्न सक्छन्।"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"अहिले प्रयोगकर्ता सेटअप गर्ने हो?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"यी व्यक्ति यन्त्र यो यन्त्र चलाउन र आफ्नो ठाउँ सेट गर्न उपलब्ध छन् भन्ने कुरा सुनिश्चित गर्नुहोस्"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"अहिले प्रोफाइल सेटअप गर्ने हो?"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 4cb755b..518aaa9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -371,7 +371,7 @@
<string name="app_process_limit_title" msgid="8361367869453043007">"Achtergrondproceslimiet"</string>
<string name="show_all_anrs" msgid="9160563836616468726">"ANR\'s op de achtergrond"</string>
<string name="show_all_anrs_summary" msgid="8562788834431971392">"Dialoogvenster \'App reageert niet\' weergeven voor achtergrond-apps"</string>
- <string name="show_notification_channel_warnings" msgid="3448282400127597331">"Kanaalwaarschuwingen voor meldingen weergeven"</string>
+ <string name="show_notification_channel_warnings" msgid="3448282400127597331">"Kanaalwaarschuwingen voor meldingen tonen"</string>
<string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Geeft een waarschuwing op het scherm weer wanneer een app een melding post zonder geldig kanaal"</string>
<string name="force_allow_on_external" msgid="9187902444231637880">"Toestaan van apps op externe opslag afdwingen"</string>
<string name="force_allow_on_external_summary" msgid="8525425782530728238">"Hiermee komt elke app in aanmerking voor schrijven naar externe opslag, ongeacht de manifestwaarden"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index b7c93e5..b0fff21 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ପ୍ରୋଟାନୋମାଲି (ଲାଲ୍-ସବୁଜ)"</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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"ଆପଣଙ୍କ ଡିଭାଇସରେ ରଙ୍ଗଗୁଡ଼ିକ କିପରି ଡିସପ୍ଲେ ହୁଏ ତାହା ଆଡଜଷ୍ଟ କରନ୍ତୁ। ଆପଣ ଏହା କରିବାକୁ ଚାହିଁଲେ ଏହା ଉପଯୋଗୀ ହୋଇପାରେ:<br/><br/> <ol> <li> ଆହୁରି ସଠିକ୍ ଭାବେ ରଙ୍ଗଗୁଡ଼ିକୁ ଦେଖିବା</li> <li> ଆପଣଙ୍କୁ ଫୋକସ୍ କରିବାରେ ସାହାଯ୍ୟ କରିବାକୁ ରଙ୍ଗଗୁଡ଼ିକୁ କାଢ଼ିବା</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"ଆପଣ ବନ୍ଦ ନକରିବା ପର୍ଯ୍ୟନ୍ତ"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ଏହିକ୍ଷଣି"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ଫୋନ୍ ସ୍ପିକର୍"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ଏହି ଫୋନ୍"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ସଂଯୋଗ କରିବାରେ ସମସ୍ୟା ହେଉଛି। ଡିଭାଇସ୍ ବନ୍ଦ କରି ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ତାରଯୁକ୍ତ ଅଡିଓ ଡିଭାଇସ୍"</string>
<string name="help_label" msgid="3528360748637781274">"ସାହାଯ୍ୟ ଓ ମତାମତ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 896a100..71e2dba 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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"ਆਪਣੇ ਡੀਵਾਈਸ \'ਤੇ ਰੰਗਾਂ ਨੂੰ ਦਿਖਾਉਣ ਦੇ ਤਰੀਕੇ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਉਦੋਂ ਲਾਹੇਵੰਦ ਹੋ ਸਕਦਾ ਹੈ ਜਦੋਂ ਤੁਸੀਂ ਇਹ ਕਰਨਾ ਚਾਹੋਗੇ:<br/><br/> <ol> <li> ਰੰਗਾਂ ਨੂੰ ਹੋਰ ਸਟੀਕਤਾ ਨਾਲ ਦੇਖਣਾ</li> <li> ਫੋਕਸ ਕਰਨ ਵਿੱਚ ਤੁਹਾਡੀ ਮਦਦ ਲਈ ਰੰਗਾਂ ਨੂੰ ਹਟਾਉਣਾ</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਬੰਦ ਨਹੀਂ ਕਰਦੇ"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ਹੁਣੇ ਹੀ"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ਫ਼ੋਨ ਦਾ ਸਪੀਕਰ"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ਇਹ ਫ਼ੋਨ"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"ਕਨੈਕਟ ਕਰਨ ਵਿੱਚ ਸਮੱਸਿਆ ਆਈ। ਡੀਵਾਈਸ ਨੂੰ ਬੰਦ ਕਰਕੇ ਵਾਪਸ ਚਾਲੂ ਕਰੋ"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"ਤਾਰ ਵਾਲਾ ਆਡੀਓ ਡੀਵਾਈਸ"</string>
<string name="help_label" msgid="3528360748637781274">"ਮਦਦ ਅਤੇ ਵਿਚਾਰ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 663f3fc..c8d7287 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (czerwony-zielony)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (niebieski-żółty)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcja kolorów"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Dostosuj sposób wyświetlania kolorów na ekranie urządzenia. Może to być pomocne, gdy chcesz:<br/><br/> <ol> <li> dokładniej widzieć kolory;,</li> <li> usunąć wybrane kolory, aby móc skuteczniej się skupić.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <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">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Dopóki nie wyłączysz"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Przed chwilą"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Głośnik telefonu"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ten telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem z połączeniem. Wyłącz i ponownie włącz urządzenie"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Przewodowe urządzenie audio"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoc i opinie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 8eab928..1d6624d 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajuste as cores exibidas no seu dispositivo. Esta opção pode ser útil quando você quer:<br/><br/> <ol> <li> ver cores com mais precisão;</li> <li> remover cores para se concentrar melhor.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <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">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Até você desativar"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Agora"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Alto-falante do smartphone"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este smartphone"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ocorreu um problema na conexão. Desligue o dispositivo e ligue-o novamente"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fio"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index f28d70a..66d4b23 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção da cor"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajuste a visualização das cores no dispositivo. Isto pode ser útil quando pretender:<br/><br/> <ol> <li> Ver cores com maior precisão</li> <li> Remover cores para melhorar a sua concentração</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <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">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Até desativar"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Agora mesmo"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altifalante do telemóvel"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este telemóvel"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problema ao ligar. Desligue e volte a ligar o dispositivo."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fios"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda e comentários"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 8eab928..1d6624d 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajuste as cores exibidas no seu dispositivo. Esta opção pode ser útil quando você quer:<br/><br/> <ol> <li> ver cores com mais precisão;</li> <li> remover cores para se concentrar melhor.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <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">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Até você desativar"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Agora"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Alto-falante do smartphone"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este smartphone"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ocorreu um problema na conexão. Desligue o dispositivo e ligue-o novamente"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispositivo de áudio com fio"</string>
<string name="help_label" msgid="3528360748637781274">"Ajuda e feedback"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 0a4e462..3e77ab6 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (roșu-verde)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (albastru-galben)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corecția culorii"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ajustați modul în care se afișează culorile pe dispozitiv. Acest lucru poate fi util când doriți să:<br/><br/> <ol> <li> vedeți culorile mai bine</li> <li> eliminați culorile pentru a vă ajuta să vă concentrați</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <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">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -517,8 +516,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Până când dezactivați"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Chiar acum"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Difuzorul telefonului"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Acest telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problemă la conectare. Opriți și reporniți dispozitivul."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Dispozitiv audio cu fir"</string>
<string name="help_label" msgid="3528360748637781274">"Ajutor și feedback"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index b030715..b342287 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Настройте цветопередачу на экране устройства. Эта функция может помочь:<br/><br/> <ol> <li> сделать цвета более четкими;</li> <li> убрать цвета, чтобы вам проще было сфокусироваться.</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>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Пока вы не отключите"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Только что"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Встроенный динамик"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Этот смартфон"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Ошибка подключения. Выключите и снова включите устройство."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Проводное аудиоустройство"</string>
<string name="help_label" msgid="3528360748637781274">"Справка/отзыв"</string>
@@ -559,7 +557,7 @@
<string name="user_switch_to_user" msgid="6975428297154968543">"Переключиться на этот аккаунт: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string>
<string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Добавить гостя"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гость"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index d9d5351..b24ac8e 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (červená a zelená)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (modrá a žltá)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Úprava farieb"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Upravte si zobrazovanie farieb v zariadení. Môže to byť užitočné, ak chcete:<br/><br/> <ol> <li> zobraziť presnejšie viac farieb;y</li> <li> odstrániť farby, aby ste sa mohli lepšie sústrediť.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <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">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Dokým funkciu nevypnete"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Teraz"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Reproduktor telefónu"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Tento telefón"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Pri pripájaní sa vyskytol problém. Zariadenie vypnite a znova zapnite."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Audio zariadenie s káblom"</string>
<string name="help_label" msgid="3528360748637781274">"Pomocník a spätná väzba"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 81b1b60..626402a 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (rdeča – zelena)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (modra – rumena)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Popravljanje barv"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Prilagodite prikaz barv v napravi. To je uporabno, ko želite:<br/><br/> <ol> <li> videti bolj prave barve;</li> <li> odstraniti barve, da se lažje osredotočite.</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <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">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Dokler ne izklopite"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Pravkar"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Zvočnik telefona"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ta telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Težava pri povezovanju. Napravo izklopite in znova vklopite."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Žična zvočna naprava"</string>
<string name="help_label" msgid="3528360748637781274">"Pomoč in povratne informacije"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index b71e51f..4f22348 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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Rregullo mënyrën se si ngjyrat afishohen në pajisjen tënde. Kjo mund të jetë e dobishme kur dëshiron që:<br/><br/> <ol> <li> T\'i shikosh ngjyrat me më shumë saktësi</li> <li> 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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Deri sa ta çaktivizosh"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Pikërisht tani"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Altoparlanti i telefonit"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Ky telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem me lidhjen. Fike dhe ndize përsëri pajisjen"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Pajisja audio me tel"</string>
<string name="help_label" msgid="3528360748637781274">"Ndihma dhe komentet"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 2ec2ded..069b6a5 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rött-grönt)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blått-gult)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Färgkorrigering"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Ändra hur färger visas på enheten. Det kan vara ett bra hjälpmedel när du vill<br/><br/> <ol> <li> att färger ska visas mer exakt</li> <li> ta bort färger för att fokusera bättre</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <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">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Tills du inaktiverar funktionen"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Nyss"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefonens högtalare"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Den här telefonen"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Det gick inte att ansluta. Stäng av enheten och slå på den igen"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Ljudenhet med kabelanslutning"</string>
<string name="help_label" msgid="3528360748637781274">"Hjälp och feedback"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 410a4aa..0fb620c 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -555,7 +555,7 @@
<string name="user_switch_to_user" msgid="6975428297154968543">"Badili utumie <xliff:g id="USER_NAME">%s</xliff:g>"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string>
<string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Ongeza mgeni"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 14eb25d..3106a69 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"சாதனத்தில் வண்ணங்கள் காண்பிக்கப்படும் விதத்தைச் சரிசெய்யலாம். இதன் மூலம் நீங்கள் விரும்பும்போதெல்லாம்:<br/><br/> <ol> <li> வண்ணங்களை மிகத் தெளிவாகப் பார்க்கலாம்</li> <li> கவனம் சிதறாமல் இருக்க வண்ணங்களை நீக்கலாம்</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"ஆஃப் செய்யும் வரை"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"சற்றுமுன்"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"மொபைல் ஸ்பீக்கர்"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"இந்த மொபைல்"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"இணைப்பதில் சிக்கல். சாதனத்தை ஆஃப் செய்து மீண்டும் ஆன் செய்யவும்"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"வயருடன்கூடிய ஆடியோ சாதனம்"</string>
<string name="help_label" msgid="3528360748637781274">"உதவியும் கருத்தும்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index e652ab7..1f78c0c 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"మీ పరికరంపై రంగులు కనిపించే విధానాన్ని అడ్జస్ట్ చేయండి. మీకు కావలసినప్పుడు, ఇది సహాయకరంగా ఉంటుంది:<br/><br/> <ol> <li> మరింత ఖచ్చితంగా రంగులను చూడండి</li> <li> మీరు ఫోకస్ చేయడంలో సహాయపడటానికి రంగులను తీసివేయండి</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"మీరు ఆఫ్ చేసే వరకు"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ఇప్పుడే"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"ఫోన్ స్పీకర్"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"ఈ ఫోన్"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"కనెక్ట్ చేయడంలో సమస్య ఉంది. పరికరాన్ని ఆఫ్ చేసి, ఆపై తిరిగి ఆన్ చేయండి"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"వైర్ గల ఆడియో పరికరం"</string>
<string name="help_label" msgid="3528360748637781274">"సహాయం & ఫీడ్బ్యాక్"</string>
@@ -557,7 +555,7 @@
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు మార్చు"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్ను క్రియేట్ చేస్తోంది…"</string>
<string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"గెస్ట్ను జోడించండి"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
<string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 5fbf5d4..ef516c4 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (kırmızı-yeşil)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (mavi-sarı)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Renk düzeltme"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Renklerin cihazınızda nasıl görüntüleneceğini düzenleyin Bu, şunları yapmak istediğinizde kullanışlı olur:<br/><br/> <ol> <li> Renkleri daha doğru görmek</li> <li> Odaklanmanıza yardımcı olması için renkleri kaldırmak</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</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">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Siz kapatana kadar"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Az önce"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Telefon hoparlörü"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Bu telefon"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Bağlanırken sorun oluştu. Cihazı kapatıp tekrar açın"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Kablolu ses cihazı"</string>
<string name="help_label" msgid="3528360748637781274">"Yardım ve geri bildirim"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 4511a4b..81528ad 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Налаштуйте відтворення кольорів на екрані пристрою. Це може бути корисно, якщо ви хочете:<br/><br/> <ol> <li> точніше відтворювати кольори;</li> <li> вилучити кольори, щоб зосередитися на головному.</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>
@@ -518,8 +517,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Доки не вимкнути"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Щойно"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Динамік телефона"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Цей телефон"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Не вдається підключитися. Перезавантажте пристрій."</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Дротовий аудіопристрій"</string>
<string name="help_label" msgid="3528360748637781274">"Довідка й відгуки"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index e61d281..798c885 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"آپ کے آلے پر رنگوں کے ڈسپلے ہونے کے طریقے کو ایڈجسٹ کریں۔ یہ خصوصیت درج ذیل کے لیے مددگار ثابت ہو سکتی ہے:<br/><br/> <ol> <li> جب آپ رنگوں کو مزید درست طریقے سے دیکھنا چاہیں </li> <li> فوکس کرنے میں مدد کے لیے رنگوں کو ہٹادیں</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"یہاں تک کہ آپ آف کر دیں"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"ابھی ابھی"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"فون اسپیکر"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"یہ فون"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"منسلک کرنے میں مسئلہ پیش آ گیا۔ آلہ کو آف اور بیک آن کریں"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"وائرڈ آڈیو آلہ"</string>
<string name="help_label" msgid="3528360748637781274">"مدد اور تاثرات"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 0730c6c..6efc0be 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -556,7 +556,7 @@
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string>
<string name="user_nickname" msgid="262624187455825083">"Nik"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string>
- <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmonni olib tashlash"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 7d1bde3..67923c5 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/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 (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"调整设备上的颜色显示方式。此设置适用于以下情况:<br/><br/> <ol> <li>想要更准确地查看颜色</li> <li>想要去除颜色,以便集中注意力</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>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"直到您将其关闭"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"刚刚"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"手机扬声器"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"这部手机"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"连接时遇到问题。请关闭并重新开启设备"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有线音频设备"</string>
<string name="help_label" msgid="3528360748637781274">"帮助和反馈"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 06b5cd0..bf69b31 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -424,7 +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>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"調整裝置顯示顏色的方式。這項設定適用於以下情況:<br/><br/> <ol> <li> 想讓裝置更準確地顯示顏色</li> <li> 移除顏色以提高專注力</li> </ol>"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"調整裝置顯示顏色嘅方式。呢項設定喺以下情況適用:<br/><br/> <ol> <li> 想令裝置更加準確咁顯示顏色</li> <li> 移除顏色嚟提高專注力</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>
@@ -515,7 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"直至您關閉為止"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"剛剛"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"手機喇叭"</string>
- <string name="media_transfer_this_phone" msgid="7194341457812151531">"這支手機"</string>
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"這部手機"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"無法連接,請關閉裝置然後重新開機"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"有線音響裝置"</string>
<string name="help_label" msgid="3528360748637781274">"說明與意見反映"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 2297cbe..7b0274a 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -424,8 +424,7 @@
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"I-Protanomaly (bomvu-luhlaza)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"I-Tritanomaly (luhlaza okwesibhakabhaka-phuzi)"</string>
<string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ukulungiswa kombala"</string>
- <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (8625527799885140826) -->
- <skip />
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="8625527799885140826">"Lungisa indlela imibala eboniswa ngayo kudivayisi yakkho. Lokhu kungaba usizo lapho ufuna:<br/><br/> <ol> <li> Ukubona imibala ngokunembilie</li> <li> Ukususa imibala ukuze ugxile</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<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">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string>
@@ -516,8 +515,7 @@
<string name="zen_mode_forever" msgid="3339224497605461291">"Uze uvale isikrini"</string>
<string name="time_unit_just_now" msgid="3006134267292728099">"Khona manje"</string>
<string name="media_transfer_this_device_name" msgid="2716555073132169240">"Isipikha sefoni"</string>
- <!-- no translation found for media_transfer_this_phone (7194341457812151531) -->
- <skip />
+ <string name="media_transfer_this_phone" msgid="7194341457812151531">"Le foni"</string>
<string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Inkinga yokuxhumeka. Vala idivayisi futhi uphinde uyivule"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Idivayisi yomsindo enentambo"</string>
<string name="help_label" msgid="3528360748637781274">"Usizo nempendulo"</string>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index ef4b97f..9d5b231 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -32,9 +32,6 @@
<dimen name="user_spinner_padding_sides">20dp</dimen>
<dimen name="user_spinner_item_height">56dp</dimen>
- <dimen name="two_target_pref_small_icon_size">24dp</dimen>
- <dimen name="two_target_pref_medium_icon_size">32dp</dimen>
-
<!-- Lock icon for preferences locked by admin -->
<dimen name="restricted_icon_padding">4dp</dimen>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index efa9f3c..d801f1b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1067,8 +1067,8 @@
<![CDATA[
Adjust how colors display on your device. This can be helpful when you want to:<br/><br/>
<ol>
- <li> See colors more accurately</li>
- <li> Remove colors to help you focus</li>
+ <li> See colors more accurately</li>
+ <li> Remove colors to help you focus</li>
</ol>
]]></string>
<!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index ad7e995..fc8b587 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -27,6 +27,8 @@
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
+import com.android.settingslib.widget.TwoTargetPreference;
+
/**
* Preference class that supports being disabled by a user restriction
* set by a device admin.
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 9c0c80b..bf4242f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -23,7 +23,6 @@
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.media.AudioManager;
-import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.TetheringManager;
import android.net.vcn.VcnTransportInfo;
@@ -37,6 +36,7 @@
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
@@ -439,8 +439,7 @@
}
public static boolean isWifiOnly(Context context) {
- return !context.getSystemService(ConnectivityManager.class)
- .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ return !context.getSystemService(TelephonyManager.class).isDataCapable();
}
/** Returns if the automatic storage management feature is turned on or not. **/
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 2528ac1..f180776 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1496,6 +1496,13 @@
}
}
+ /**
+ * Whether the packages for the user have been initialized.
+ */
+ public boolean isUserAdded(int userId) {
+ return mEntriesMap.contains(userId);
+ }
+
public interface Callbacks {
void onRunningStateChanged(boolean running);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 8fd1910..129aca4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -386,7 +386,6 @@
case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
- case BluetoothDevice.UNBOND_REASON_REMOVED:
errorMsg = R.string.bluetooth_pairing_error_message;
break;
default:
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
index e7a20b3..c88ed8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
@@ -1,6 +1,7 @@
# Default reviewers for this and subdirectories.
andychou@google.com
arcwang@google.com
+changbetty@google.com
goldmanj@google.com
qal@google.com
wengsu@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
index 3bb3a0c..7f12cc8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.wifi.WifiManager;
@@ -28,7 +29,6 @@
import com.android.settingslib.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
-import java.net.InetAddress;
import java.util.Iterator;
/**
@@ -93,19 +93,19 @@
* @return the formatted and newline-separated IP addresses, or null if none.
*/
private static String getDefaultIpAddresses(ConnectivityManager cm) {
- LinkProperties prop = cm.getActiveLinkProperties();
+ LinkProperties prop = cm.getLinkProperties(cm.getActiveNetwork());
return formatIpAddresses(prop);
}
private static String formatIpAddresses(LinkProperties prop) {
if (prop == null) return null;
- Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
+ Iterator<LinkAddress> iter = prop.getAllLinkAddresses().iterator();
// If there are no entries, return null
if (!iter.hasNext()) return null;
// Concatenate all available addresses, newline separated
StringBuilder addresses = new StringBuilder();
while (iter.hasNext()) {
- addresses.append(iter.next().getHostAddress());
+ addresses.append(iter.next().getAddress().getHostAddress());
if (iter.hasNext()) addresses.append("\n");
}
return addresses.toString();
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
index 092cbf3..60bcf37 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageController.java
@@ -16,7 +16,6 @@
package com.android.settingslib.net;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
@@ -59,7 +58,6 @@
PERIOD_BUILDER, Locale.getDefault());
private final Context mContext;
- private final ConnectivityManager mConnectivityManager;
private final INetworkStatsService mStatsService;
private final NetworkPolicyManager mPolicyManager;
private final NetworkStatsManager mNetworkStatsManager;
@@ -71,7 +69,6 @@
public DataUsageController(Context context) {
mContext = context;
- mConnectivityManager = ConnectivityManager.from(context);
mStatsService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
mPolicyManager = NetworkPolicyManager.from(mContext);
@@ -236,7 +233,7 @@
public boolean isMobileDataSupported() {
// require both supported network and ready SIM
- return mConnectivityManager.isNetworkSupported(TYPE_MOBILE)
+ return getTelephonyManager().isDataCapable()
&& getTelephonyManager().getSimState() == SIM_STATE_READY;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
index 0bde5c0..f3b600c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -2,6 +2,7 @@
andychou@google.com
arcwang@google.com
asapperstein@google.com
+changbetty@google.com
goldmanj@google.com
qal@google.com
wengsu@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 841a49e..cbfd4d8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -75,7 +75,8 @@
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
- private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+ private final NetworkCallback mNetworkCallback =
+ new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
@Override
public void onAvailable(
Network network, NetworkCapabilities networkCapabilities,
@@ -131,7 +132,8 @@
}
}
};
- private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+ private final NetworkCallback mDefaultNetworkCallback =
+ new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
// network is now the default network, and its capabilities are nc.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 6a4d650..4bff78f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -353,21 +353,6 @@
}
@Test
- public void showUnbondMessage_reasonRemoved_showCorrectedErrorCode() {
- mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
- mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
- mIntent.putExtra(BluetoothDevice.EXTRA_REASON, BluetoothDevice.UNBOND_REASON_REMOVED);
- when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
- when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
-
- mContext.sendBroadcast(mIntent);
-
- verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
- eq(R.string.bluetooth_pairing_error_message));
- }
-
- @Test
public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
similarity index 91%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
rename to packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
index 3f0ba13..aaec909 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.settingslib;
+package com.android.settingslib.widget;
-import static com.android.settingslib.TwoTargetPreference.ICON_SIZE_DEFAULT;
-import static com.android.settingslib.TwoTargetPreference.ICON_SIZE_MEDIUM;
-import static com.android.settingslib.TwoTargetPreference.ICON_SIZE_SMALL;
+import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_DEFAULT;
+import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
+import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_SMALL;
import static com.google.common.truth.Truth.assertThat;
@@ -32,6 +32,8 @@
import androidx.preference.PreferenceViewHolder;
+import com.android.settingslib.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index d10ff40..5d4078d 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -252,4 +252,8 @@
<!-- Default for Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW -->
<bool name="def_enable_non_resizable_multi_window">true</bool>
+
+ <!-- Default for Settings.Secure.ACCESSIBILITY_BUTTON_MODE -->
+ <integer name="def_accessibility_button_mode">1</integer>
+
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index e8e10a4..db9b83e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -281,5 +281,6 @@
VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
new InclusiveFloatRangeValidator(0.0f, 1.0f));
VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 6568bff..268603f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2445,8 +2445,8 @@
R.bool.def_auto_time_zone); // Sync timezone to NITZ
loadSetting(stmt, Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
- ("1".equals(SystemProperties.get("ro.kernel.qemu")) ||
- res.getBoolean(R.bool.def_stay_on_while_plugged_in))
+ ("1".equals(SystemProperties.get("ro.boot.qemu"))
+ || res.getBoolean(R.bool.def_stay_on_while_plugged_in))
? 1 : 0);
loadIntegerSetting(stmt, Settings.Global.WIFI_SLEEP_POLICY,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a0b9528..7288371 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1990,6 +1990,13 @@
dumpSetting(s, p,
Settings.Secure.CARRIER_APPS_HANDLED,
SecureSettingsProto.CARRIER_APPS_HANDLED);
+
+ final long clipboardToken = p.start(SecureSettingsProto.CLIPBOARD);
+ dumpSetting(s, p,
+ Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
+ SecureSettingsProto.Clipboard.SHOW_ACCESS_NOTIFICATIONS);
+ p.end(clipboardToken);
+
dumpSetting(s, p,
Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
SecureSettingsProto.CMAS_ADDITIONAL_BROADCAST_PKG);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 400742b..081f3f6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3399,7 +3399,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 198;
+ private static final int SETTINGS_VERSION = 199;
private final int mUserId;
@@ -4897,6 +4897,36 @@
currentVersion = 198;
}
+ if (currentVersion == 198) {
+ // Version 198: Set the default value for accessibility button. If the user
+ // uses accessibility button in the navigation bar to trigger their
+ // accessibility features (check if ACCESSIBILITY_BUTTON_TARGETS has value)
+ // then leave accessibility button mode in the navigation bar, otherwise, set it
+ // to the floating menu.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting accessibilityButtonMode = secureSettings.getSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_MODE);
+ if (accessibilityButtonMode.isNull()) {
+ if (isAccessibilityButtonInNavigationBarOn(secureSettings)) {
+ secureSettings.insertSettingLocked(Secure.ACCESSIBILITY_BUTTON_MODE,
+ String.valueOf(
+ Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR),
+ /*tag= */ null, /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ } else {
+ final int defAccessibilityButtonMode =
+ getContext().getResources().getInteger(
+ R.integer.def_accessibility_button_mode);
+ secureSettings.insertSettingLocked(Secure.ACCESSIBILITY_BUTTON_MODE,
+ String.valueOf(defAccessibilityButtonMode), /* tag= */
+ null, /* makeDefault= */ true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 199;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
@@ -5075,5 +5105,15 @@
}
return items;
}
+
+ private boolean isAccessibilityButtonInNavigationBarOn(SettingsState secureSettings) {
+ final boolean hasValueInA11yBtnTargets = !TextUtils.isEmpty(
+ secureSettings.getSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_TARGETS).getValue());
+ final int navigationMode = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
+
+ return hasValueInA11yBtnTargets && (navigationMode != NAV_BAR_MODE_GESTURAL);
+ }
}
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b4194fd..8d8e442 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -187,6 +187,7 @@
<uses-permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS" />
<uses-permission android:name="android.permission.MANAGE_SENSORS" />
<uses-permission android:name="android.permission.MANAGE_AUDIO_POLICY" />
+ <uses-permission android:name="android.permission.QUERY_AUDIO_STATE" />
<uses-permission android:name="android.permission.MANAGE_CAMERA" />
<!-- Permissions needed to test system only camera devices -->
<uses-permission android:name="android.permission.CAMERA" />
@@ -429,6 +430,11 @@
<!-- Permission required for CTS test - FontManagerTest -->
<uses-permission android:name="android.permission.UPDATE_FONTS" />
+ <!-- Permission required for hotword detection service CTS tests -->
+ <uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
+
+ <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION"/>
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b6fd286..6574353 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -54,7 +54,7 @@
name: "SystemUI-sensors",
srcs: [
"src/com/android/systemui/util/sensors/ThresholdSensor.java",
- ]
+ ],
}
android_library {
@@ -99,7 +99,7 @@
"SystemUI-tags",
"SystemUI-proto",
"dagger2",
- "jsr330"
+ "jsr330",
],
manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e0097df..d3d7a95 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -602,6 +602,9 @@
android:resource="@xml/people_space_widget_info" />
</receiver>
+ <receiver android:name=".people.widget.PeopleSpaceWidgetPinnedReceiver"
+ android:enabled="true"/>
+
<!-- Widget service -->
<service android:name=".people.widget.PeopleSpaceWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 9e67e4b..d6204db 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -25,7 +25,10 @@
name: "SystemUIPluginLib",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "bcsmartspace/src/**/*.java",
+ ],
static_libs: [
"PluginCoreLib",
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
new file mode 100644
index 0000000..f8a9a045
--- /dev/null
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -0,0 +1,44 @@
+/*
+ * 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.plugins;
+
+import android.os.Parcelable;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.util.List;
+
+/**
+ * Interface to provide SmartspaceTargets to BcSmartspace.
+ */
+@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION)
+public interface BcSmartspaceDataPlugin extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
+ int VERSION = 1;
+
+ /** Register a listener to get Smartspace data. */
+ void registerListener(SmartspaceTargetListener listener);
+
+ /** Unregister a listener. */
+ void unregisterListener(SmartspaceTargetListener listener);
+
+ /** Provides Smartspace data to registered listeners. */
+ interface SmartspaceTargetListener {
+ /** Each Parcelable is a SmartspaceTarget that represents a card. */
+ void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 0d348e2..1fde6c9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -95,4 +95,15 @@
/** Call to report a ProximityEvent to the FalsingManager. */
void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent);
+
+ /** Adds a {@link FalsingBeliefListener}. */
+ void addFalsingBeliefListener(FalsingBeliefListener listener);
+
+ /** Removes a {@link FalsingBeliefListener}. */
+ void removeFalsingBeliefListener(FalsingBeliefListener listener);
+
+ /** Listener that is alerted when falsing belief level crosses a predfined threshold. */
+ interface FalsingBeliefListener {
+ void onFalse();
+ }
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
index 71cdaf5..384e02d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
@@ -22,7 +22,6 @@
android:clipToPadding="false">
<include
- style="@style/BouncerSecurityContainer"
layout="@layout/keyguard_host_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index b6a41c2..6b4fbdb 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -77,8 +77,9 @@
android:fontFamily="@font/clock"
android:typeface="monospace"
android:elegantTextHeight="false"
+ android:singleLine="true"
dozeWeight="200"
- lockScreenWeight="300"
+ lockScreenWeight="400"
/>
</FrameLayout>
<FrameLayout
@@ -109,8 +110,6 @@
<com.android.systemui.statusbar.phone.NotificationIconContainer
android:id="@+id/left_aligned_notification_icon_container"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_shelf_height"
android:layout_marginTop="@dimen/widget_vertical_padding"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 6ae759c..50ffbc8 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -59,124 +59,127 @@
</com.android.keyguard.AlphaOptimizedRelativeLayout>
<LinearLayout
android:id="@+id/row1"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key1"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pinEntry"
androidprv:digit="1"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key2"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pinEntry"
androidprv:digit="2"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key3"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/pinEntry"
androidprv:digit="3"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/row2"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key4"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pinEntry"
androidprv:digit="4"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key5"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pinEntry"
androidprv:digit="5"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key6"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/pinEntry"
androidprv:digit="6"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/row3"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key7"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pinEntry"
androidprv:digit="7"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key8"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pinEntry"
androidprv:digit="8"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key9"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/pinEntry"
androidprv:digit="9"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/row4"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
>
<com.android.keyguard.NumPadButton
android:id="@+id/delete_button"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
android:contentDescription="@string/keyboardview_keycode_delete"
style="@style/NumPadKey.Delete"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key0"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pinEntry"
androidprv:digit="0"
/>
<com.android.keyguard.NumPadButton
android:id="@+id/key_enter"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
style="@style/NumPadKey.Enter"
android:contentDescription="@string/keyboardview_keycode_enter"
/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index f709424..297c20e 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -71,121 +71,124 @@
/>
</RelativeLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key1"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="1"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key2"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="2"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key3"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="3"
/>
</LinearLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key4"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="4"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key5"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="5"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key6"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="6"
/>
</LinearLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key7"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="7"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key8"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="8"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key9"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="9"
/>
</LinearLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
>
<com.android.keyguard.NumPadButton
android:id="@+id/delete_button"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
android:contentDescription="@string/keyboardview_keycode_delete"
style="@style/NumPadKey.Delete"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key0"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/simPinEntry"
androidprv:digit="0"
/>
<com.android.keyguard.NumPadButton
android:id="@+id/key_enter"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
style="@style/NumPadKey.Enter"
android:contentDescription="@string/keyboardview_keycode_enter"
/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index 2f9fed6..388919e 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -72,121 +72,125 @@
/>
</RelativeLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key1"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pukEntry"
androidprv:digit="1"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key2"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pukEntry"
androidprv:digit="2"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key3"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/pukEntry"
androidprv:digit="3"
/>
</LinearLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
+
>
<com.android.keyguard.NumPadKey
android:id="@+id/key4"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pukEntry"
androidprv:digit="4"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key5"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pukEntry"
androidprv:digit="5"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key6"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/pukEntry"
androidprv:digit="6"
/>
</LinearLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
>
<com.android.keyguard.NumPadKey
android:id="@+id/key7"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pukEntry"
androidprv:digit="7"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key8"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pukEntry"
androidprv:digit="8"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key9"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
androidprv:textView="@+id/pukEntry"
androidprv:digit="9"
/>
</LinearLayout>
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:layout_gravity="center_horizontal"
>
<com.android.keyguard.NumPadButton
android:id="@+id/delete_button"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
android:contentDescription="@string/keyboardview_keycode_delete"
style="@style/NumPadKey.Delete"
/>
<com.android.keyguard.NumPadKey
android:id="@+id/key0"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
+ android:layout_marginEnd="@dimen/num_pad_key_margin_end"
androidprv:textView="@+id/pukEntry"
androidprv:digit="0"
/>
<com.android.keyguard.NumPadButton
android:id="@+id/key_enter"
- android:layout_width="0px"
+ android:layout_width="@dimen/num_pad_key_width"
android:layout_height="match_parent"
- android:layout_weight="1"
style="@style/NumPadKey.Enter"
android:contentDescription="@string/keyboardview_keycode_enter"
/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 4db2280..9f3ca74 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -20,8 +20,6 @@
<!-- This is a view that shows general status information in Keyguard. -->
<com.android.keyguard.KeyguardSliceView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
diff --git a/packages/SystemUI/res-keyguard/values-sw360dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw360dp/dimens.xml
new file mode 100644
index 0000000..be39030
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values-sw360dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <!-- Spacing around each button used for PIN view -->
+ <dimen name="num_pad_key_width">84dp</dimen>
+ <dimen name="num_pad_row_margin_bottom">12dp</dimen>
+ <dimen name="num_pad_key_margin_end">20dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/integers.xml b/packages/SystemUI/res-keyguard/values-sw600dp/integers.xml
new file mode 100644
index 0000000..a35fa3a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values-sw600dp/integers.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.
+ -->
+<resources>
+ <!-- This needs to be specified in an integer, rather than a style, as it can change in response
+ to device config changes, and we need to be able to change it without re-inflation.
+
+ 0x11 = center -->
+ <integer name="keyguard_host_view_gravity">0x11</integer>
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 115a156..07bd2e6 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -38,7 +38,7 @@
<!-- Margin around the various security views -->
<dimen name="keyguard_security_view_top_margin">8dp</dimen>
- <dimen name="keyguard_security_view_lateral_margin">36dp</dimen>
+ <dimen name="keyguard_security_view_lateral_margin">20dp</dimen>
<dimen name="keyguard_eca_top_margin">18dp</dimen>
@@ -87,5 +87,7 @@
<dimen name="disappear_y_translation">-32dp</dimen>
<!-- Spacing around each button used for PIN view -->
- <dimen name="num_pad_key_margin">2dp</dimen>
+ <dimen name="num_pad_key_width">72dp</dimen>
+ <dimen name="num_pad_row_margin_bottom">6dp</dimen>
+ <dimen name="num_pad_key_margin_end">12dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/integers.xml b/packages/SystemUI/res-keyguard/values/integers.xml
new file mode 100644
index 0000000..6f14dc9b
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values/integers.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.
+ -->
+<resources>
+ <!-- This needs to be specified in an integer, rather than a style, as it can change in response
+ to device config changes, and we need to be able to change it without re-inflation.
+
+ 0x50 = bottom, 0x01 = center_horizontal -->
+ <integer name="keyguard_host_view_gravity">0x51</integer>
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 8f42cbe..2ffc992 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -100,10 +100,6 @@
<item name="android:shadowRadius">?attr/shadowRadius</item>
</style>
- <style name="BouncerSecurityContainer">
- <item name="android:layout_gravity">center_horizontal|bottom</item>
- </style>
-
<style name="PasswordTheme" parent="Theme.SystemUI">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:colorControlNormal">?android:attr/textColorPrimary</item>
diff --git a/packages/SystemUI/res/color/remote_input_send.xml b/packages/SystemUI/res/color/remote_input_send.xml
index fe2ffaa..bd91ef9 100644
--- a/packages/SystemUI/res/color/remote_input_send.xml
+++ b/packages/SystemUI/res/color/remote_input_send.xml
@@ -16,6 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="true" android:color="@android:color/white" />
- <item android:color="#4dffffff" /> <!-- 30% white -->
+ <item android:state_enabled="true" android:color="?android:attr/colorAccent" />
+ <item android:color="?android:attr/colorAccent" android:alpha=".3" />
</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/remote_input_text.xml b/packages/SystemUI/res/color/remote_input_text.xml
index 8e18e16..33eeb77 100644
--- a/packages/SystemUI/res/color/remote_input_text.xml
+++ b/packages/SystemUI/res/color/remote_input_text.xml
@@ -16,6 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="true" android:color="@color/remote_input_text_enabled" /> <!-- white -->
- <item android:color="#99ffffff" /> <!-- 60% white -->
+ <item android:state_enabled="true" android:color="?android:attr/textColorTertiary" />
+ <item android:color="?android:attr/textColorTertiary" android:alpha=".6" />
</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml b/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml
new file mode 100644
index 0000000..5148668
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml
@@ -0,0 +1,27 @@
+<?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"
+ android:shape="rectangle">
+ <corners
+ android:bottomLeftRadius="@dimen/accessibility_floating_menu_small_single_radius"
+ android:bottomRightRadius="0dp"
+ android:topLeftRadius="@dimen/accessibility_floating_menu_small_single_radius"
+ android:topRightRadius="0dp"/>
+ <solid
+ android:color="@color/accessibility_floating_menu_background"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
index 8efe053..73b02f4 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
@@ -18,33 +18,20 @@
android:paddingMode="stack" >
<item android:id="@android:id/background"
android:gravity="center_vertical|fill_horizontal">
- <layer-list>
- <item>
- <shape
- android:tint="?android:attr/colorControlActivated"
- android:alpha="?android:attr/disabledAlpha">
- <size android:height="@dimen/rounded_slider_height" />
- <solid android:color="@color/white_disabled" />
- <corners android:radius="@dimen/rounded_slider_corner_radius" />
- </shape>
- </item>
- <item
- android:gravity="center_vertical|left"
- android:height="@dimen/rounded_slider_icon_size"
- android:width="@dimen/rounded_slider_icon_size"
- android:left="@dimen/rounded_slider_icon_inset">
- <com.android.systemui.util.AlphaTintDrawableWrapper
- android:drawable="@drawable/ic_brightness"
- android:tint="?android:attr/colorControlActivated" />
- </item>
- </layer-list>
+ <inset
+ android:insetLeft="@dimen/rounded_slider_track_inset"
+ android:insetRight="@dimen/rounded_slider_track_inset" >
+ <shape>
+ <size android:height="@dimen/rounded_slider_track_width" />
+ <corners android:radius="@dimen/rounded_slider_track_corner_radius" />
+ <solid android:color="?android:attr/textColorPrimary" />
+ </shape>
+ </inset>
</item>
<item android:id="@android:id/progress"
android:gravity="center_vertical|fill_horizontal">
- <clip
+ <com.android.systemui.util.RoundedCornerProgressDrawable
android:drawable="@drawable/brightness_progress_full_drawable"
- android:clipOrientation="horizontal"
- android:gravity="left"
/>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
index 5bc2773..41140a7 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
@@ -26,10 +26,10 @@
</item>
<item
android:id="@+id/slider_icon"
- android:gravity="center_vertical|left"
+ android:gravity="center_vertical|right"
android:height="@dimen/rounded_slider_icon_size"
android:width="@dimen/rounded_slider_icon_size"
- android:left="@dimen/rounded_slider_icon_inset">
+ android:right="@dimen/rounded_slider_icon_inset">
<com.android.systemui.util.AlphaTintDrawableWrapper
android:drawable="@drawable/ic_brightness"
android:tint="?android:attr/colorBackground"
diff --git a/packages/SystemUI/res/drawable/fingerprint_bg.xml b/packages/SystemUI/res/drawable/fingerprint_bg.xml
new file mode 100644
index 0000000..2b0ab6f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/fingerprint_bg.xml
@@ -0,0 +1,25 @@
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <solid
+ android:color="?android:attr/colorBackground"/>
+
+ <size
+ android:width="64dp"
+ android:height="64dp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/ic_message.xml b/packages/SystemUI/res/drawable/ic_message.xml
new file mode 100644
index 0000000..8219eee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_message.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:width="48dp"
+ android:height="48dp">
+ <path
+ android:pathData="M40 4H8C5.79 4 4.02 5.79 4.02 8L4 44l8 -8h28c2.21 0 4 -1.79 4 -4V8c0 -2.21 -1.79 -4 -4 -4zM12 18h24v4H12v-4zm16 10H12v-4h16v4zm8 -12H12v-4h24v4z"
+ android:fillColor="#FF000000" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_photo_camera.xml b/packages/SystemUI/res/drawable/ic_photo_camera.xml
new file mode 100644
index 0000000..63cd4e2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_photo_camera.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_wallet.xml b/packages/SystemUI/res/drawable/ic_qs_wallet.xml
new file mode 100644
index 0000000..e146eab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_wallet.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,
+ -0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
new file mode 100644
index 0000000..f7357b2
--- /dev/null
+++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/accessibility_floating_menu_padding"
+ android:paddingEnd="@dimen/accessibility_floating_menu_padding"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <View
+ android:id="@+id/icon_view"
+ android:layout_width="@dimen/accessibility_floating_menu_small_width_height"
+ android:layout_height="@dimen/accessibility_floating_menu_small_width_height"/>
+
+ <View
+ android:id="@+id/transparent_divider"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/accessibility_floating_menu_padding"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index d36c1a8..42f14f3 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -67,7 +67,7 @@
android:textSize="15sp"
android:ellipsize="end"
android:maxLines="1"
- style="@style/TextAppearance.NotificationInfo.Title" />
+ style="@style/TextAppearance.NotificationImportanceChannel" />
<Switch
android:id="@+id/toggle"
@@ -80,7 +80,7 @@
<View
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="@color/notification_channel_dialog_separator"
+ android:background="?android:attr/colorAccent"
/>
<!-- ChannelRows get added dynamically -->
@@ -90,7 +90,7 @@
<View
android:layout_width="match_parent"
android:layout_height="1dp"
- android:background="@color/notification_channel_dialog_separator"
+ android:background="?android:attr/colorAccent"
/>
<RelativeLayout
android:id="@+id/bottom_actions"
diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
index c863e02..87de286 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml
@@ -53,7 +53,7 @@
android:textSize="14sp"
android:ellipsize="end"
android:maxLines="1"
- style="@style/TextAppearance.NotificationInfo.Title"
+ style="@style/TextAppearance.NotificationImportanceChannel"
/>
<TextView
@@ -67,7 +67,7 @@
android:ellipsize="end"
android:maxLines="1"
android:layout_below="@id/channel_name"
- style="@style/TextAppearance.NotificationInfo.Secondary"
+ style="@style/TextAppearance.NotificationImportanceApp"
/>
</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
index b1c1328..b1a37bf 100644
--- a/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_large_avatar_tile.xml
@@ -28,48 +28,27 @@
<LinearLayout
android:orientation="horizontal"
android:gravity="center"
+ android:layout_gravity="center"
android:paddingVertical="2dp"
android:paddingHorizontal="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <LinearLayout
- android:background="@drawable/people_space_new_story_outline"
- android:id="@+id/person_icon_with_story"
- android:gravity="center_horizontal"
- android:layout_width="60dp"
- android:layout_height="60dp">
- <ImageView
- android:id="@+id/person_icon_inside_ring"
- android:layout_marginEnd="4dp"
- android:layout_marginStart="4dp"
- android:layout_marginBottom="4dp"
- android:layout_marginTop="4dp"
- android:layout_width="52dp"
- android:layout_height="52dp"/>
- </LinearLayout>
<ImageView
- android:id="@+id/person_icon_only"
+ android:id="@+id/person_icon"
android:layout_width="60dp"
android:layout_height="60dp"/>
-
<ImageView
- android:id="@+id/package_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginStart="-20dp"
- android:layout_marginTop="22dp"/>
-
+ android:id="@+id/availability"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/circle_green_10dp"/>
<LinearLayout
android:orientation="vertical"
- android:paddingStart="8dp"
+ android:paddingStart="4dp"
+ android:gravity="top"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <ImageView
- android:id="@+id/availability"
- android:layout_width="10dp"
- android:layout_height="10dp"
- android:background="@drawable/circle_green_10dp"/>
<TextView
android:id="@+id/name"
android:text="@string/empty_user_name"
diff --git a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml b/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
deleted file mode 100644
index 9ea7aa3..0000000
--- a/packages/SystemUI/res/layout/people_space_notification_content_tile.xml
+++ /dev/null
@@ -1,154 +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="match_parent"
- android:orientation="vertical">
- <RelativeLayout
- android:background="@drawable/people_space_tile_view_card"
- android:id="@+id/item"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <include layout="@layout/punctuation_layout"/>
- <RelativeLayout
- android:gravity="start"
- android:id="@+id/column_one"
- android:paddingVertical="10dp"
- android:paddingStart="8dp"
- android:layout_width="wrap_content"
- android:layout_height="match_parent">
- <TextView
- android:id="@+id/subtext"
- android:layout_toStartOf="@+id/content_layout"
- android:layout_alignParentStart="true"
- android:layout_alignParentTop="true"
- android:gravity="top|start"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="12sp"
- android:maxWidth="60dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="1"
- android:ellipsize="end"/>
- <LinearLayout
- android:orientation="horizontal"
- android:id="@+id/avatar_and_app_icon"
- android:layout_alignParentBottom="true"
- android:layout_alignParentStart="true"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <LinearLayout
- android:id="@+id/person_icon_with_story"
- android:background="@drawable/people_space_new_story_outline"
- android:gravity="center_horizontal"
- android:layout_width="48dp"
- android:layout_height="48dp">
- <ImageView
- android:id="@+id/person_icon_inside_ring"
- android:layout_marginEnd="4dp"
- android:layout_marginStart="4dp"
- android:layout_marginBottom="4dp"
- android:layout_marginTop="4dp"
- android:layout_width="40dp"
- android:layout_height="40dp"/>
- </LinearLayout>
- <ImageView
- android:id="@+id/person_icon_only"
- android:layout_width="48dp"
- android:layout_height="48dp"/>
- <ImageView
- android:id="@id/package_icon"
- android:layout_marginStart="-16dp"
- android:layout_marginTop="18dp"
- android:paddingBottom="10dp"
- android:paddingEnd="8dp"
- android:layout_width="28dp"
- android:layout_height="32dp"/>
- </LinearLayout>
- </RelativeLayout>
- <LinearLayout
- android:id="@+id/content_layout"
- android:paddingBottom="4dp"
- android:paddingStart="4dp"
- android:paddingTop="8dp"
- android:paddingEnd="8dp"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@id/column_one"
- android:gravity="top|end"
- android:orientation="vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <TextView
- android:id="@+id/content"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:gravity="top|end"
- android:textSize="12sp"
- android:paddingTop="2dp"
- android:paddingEnd="4dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:ellipsize="end"/>
- <LinearLayout
- android:id="@+id/content_background"
- android:background="@drawable/people_space_content_background"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <ImageView
- android:id="@+id/image"
- android:adjustViewBounds="true"
- android:maxHeight="44dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:scaleType="centerCrop"/>
- </LinearLayout>
- </LinearLayout>
- <LinearLayout
- android:id="@+id/person_label"
- android:paddingBottom="10dp"
- android:paddingEnd="8dp"
- android:gravity="start|bottom"
- android:layout_toEndOf="@id/column_one"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/content_layout"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <ImageView
- android:id="@+id/availability"
- android:layout_width="10dp"
- android:layout_height="10dp"
- android:paddingVertical="2dp"
- android:background="@drawable/circle_green_10dp"/>
- <TextView
- android:id="@+id/name"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"
- android:maxLines="1"
- android:ellipsize="end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout>
- </RelativeLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
index 3300495..6a606e1 100644
--- a/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
+++ b/packages/SystemUI/res/layout/people_space_small_avatar_tile.xml
@@ -1,44 +1,69 @@
<?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.
- -->
+ ~ 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.
+ -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <RelativeLayout
+ <LinearLayout
android:background="@drawable/people_space_tile_view_card"
android:id="@+id/item"
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:padding="8dp"
android:layout_width="match_parent"
- android:layout_height="match_parent">
- <RelativeLayout
- android:gravity="start"
- android:id="@+id/column_one"
- android:paddingVertical="8dp"
- android:paddingStart="8dp"
- android:layout_width="wrap_content"
- android:layout_height="match_parent">
+ android:layout_height="wrap_content">
+ <LinearLayout
+ android:orientation="horizontal"
+ android:gravity="top"
+ 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"/>
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/circle_green_10dp"/>
<LinearLayout
- android:orientation="horizontal"
+ 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"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textSize="12sp"
+ 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_alignParentStart="true"
- android:layout_alignParentTop="true"
- android:layout_width="60dp"
- android:layout_height="60dp">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:gravity="center"
@@ -46,105 +71,33 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"/>
- <ImageView
- android:id="@+id/status_defined_icon"
- android:gravity="start|top"
- android:layout_marginStart="-52dp"
- android:layout_marginTop="8dp"
- android:layout_width="18dp"
- android:layout_height="18dp"/>
</LinearLayout>
- <LinearLayout
- android:orientation="horizontal"
- android:id="@+id/avatar_and_app_icon"
- android:layout_alignParentBottom="true"
- android:layout_alignParentStart="true"
- android:paddingStart="4dp"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <LinearLayout
- android:id="@+id/person_icon_with_story"
- android:background="@drawable/people_space_new_story_outline"
- android:gravity="center_horizontal"
- android:layout_width="48dp"
- android:layout_height="48dp">
- <ImageView
- android:id="@+id/person_icon_inside_ring"
- android:layout_marginEnd="4dp"
- android:layout_marginStart="4dp"
- android:layout_marginBottom="4dp"
- android:layout_marginTop="4dp"
- android:layout_width="40dp"
- android:layout_height="40dp"/>
- </LinearLayout>
- <ImageView
- android:id="@+id/person_icon_only"
- android:layout_width="48dp"
- android:layout_height="48dp"/>
- <ImageView
- android:id="@id/package_icon"
- android:layout_marginStart="-16dp"
- android:layout_marginTop="18dp"
- android:paddingBottom="10dp"
- android:paddingEnd="8dp"
- android:layout_width="28dp"
- android:layout_height="32dp"/>
- </LinearLayout>
- </RelativeLayout>
- <LinearLayout
- android:id="@+id/content_layout"
- android:paddingTop="10dp"
- android:paddingBottom="4dp"
- android:paddingHorizontal="8dp"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@id/column_one"
- android:gravity="top|end"
- android:orientation="vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
<TextView
- android:id="@+id/status"
+ 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="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
+ android:singleLine="false"
android:ellipsize="end"/>
+ </LinearLayout>
</LinearLayout>
<LinearLayout
- android:id="@+id/person_label"
- android:paddingBottom="10dp"
- android:paddingEnd="8dp"
- android:gravity="start|bottom"
- android:layout_toEndOf="@id/column_one"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/content_layout"
- android:orientation="vertical"
+ android:gravity="bottom"
+ android:orientation="horizontal"
+ android:paddingTop="4dp"
+ android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <TextView
- android:id="@+id/time"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="12sp"
- android:paddingVertical="2dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="1"
- android:visibility="gone"
- android:ellipsize="end"/>
- <ImageView
- android:id="@+id/availability"
- android:layout_width="10dp"
- android:layout_height="10dp"
- android:paddingVertical="2dp"
- android:background="@drawable/circle_green_10dp"/>
+
<TextView
android:id="@+id/name"
+ android:gravity="center_vertical"
+ android:layout_weight="1"
+ android:text="@string/empty_user_name"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
android:textColor="?android:attr/textColorPrimary"
android:textSize="14sp"
@@ -152,6 +105,13 @@
android:ellipsize="end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <ImageView
+ android:id="@+id/predefined_icon"
+ android:gravity="end"
+ android:paddingStart="6dp"
+ android:layout_width="24dp"
+ android:layout_height="18dp"/>
</LinearLayout>
- </RelativeLayout>
-</LinearLayout>
\ No newline at end of file
+ </LinearLayout>
+</LinearLayout>
+
diff --git a/packages/SystemUI/res/layout/people_space_small_view.xml b/packages/SystemUI/res/layout/people_space_small_view.xml
new file mode 100644
index 0000000..7b1abae
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_small_view.xml
@@ -0,0 +1,59 @@
+<?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.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/people_space_tile_view_card"
+ android:orientation="vertical"
+ android:paddingHorizontal="4dp"
+ android:paddingVertical="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" />
+
+ <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/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:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="14sp" />
+ </LinearLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
index b969a15..4798b43 100644
--- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
@@ -38,7 +38,6 @@
android:id="@+id/device_management_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/monitoring_title_device_owned"
style="@style/DeviceManagementDialogTitle"
android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding"
/>
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index b5d48b4..43182eb 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -19,7 +19,6 @@
<!-- LinearLayout -->
<com.android.systemui.statusbar.policy.RemoteInputView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:theme="@style/systemui_theme_remote_input"
android:id="@+id/remote_input"
android:layout_height="match_parent"
android:layout_width="match_parent">
@@ -33,6 +32,10 @@
android:paddingBottom="4dp"
android:paddingStart="16dp"
android:paddingEnd="12dp"
+ android:layout_marginRight="5dp"
+ android:layout_marginLeft="20dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginBottom="20dp"
android:gravity="start|center_vertical"
android:textAppearance="?android:attr/textAppearance"
android:textColor="@color/remote_input_text"
@@ -53,6 +56,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
+ android:paddingBottom="20dp"
android:paddingStart="12dp"
android:paddingEnd="24dp"
android:id="@+id/remote_input_send"
@@ -66,6 +70,7 @@
android:id="@+id/remote_input_progress"
android:layout_width="24dp"
android:layout_height="24dp"
+ android:layout_marginBottom="10dp"
android:layout_marginEnd="6dp"
android:layout_gravity="center"
android:visibility="invisible"
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view.xml b/packages/SystemUI/res/layout/udfps_keyguard_view.xml
index 0199ccb..562040b 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view.xml
@@ -20,7 +20,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <!-- TODO: add background protection -->
+ <!-- Background protection -->
+ <ImageView
+ android:id="@+id/udfps_keyguard_fp_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/fingerprint_bg"
+ android:visibility="gone"/>
<!-- Fingerprint -->
<ImageView
diff --git a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
index e4b6e07..e9c8389 100644
--- a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
+++ b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl
@@ -1,11 +1,54 @@
precision mediump float;
+#define GAMMA 2.2
+#define INV_GAMMA 1.0 / GAMMA
// The actual wallpaper texture.
uniform sampler2D uTexture;
+uniform float uExposure;
varying vec2 vTextureCoordinates;
+// Following the Rec. ITU-R BT.709.
+float relativeLuminance(vec3 color) {
+ return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
+}
+
+// Adjusts the exposure of some luminance value.
+float relativeExposureCompensation(in float lum, in float ev) {
+ return lum * pow(2.0, ev);
+}
+
+vec4 srgbToLinear(in vec4 color) {
+ vec4 linearColor = vec4(color);
+ linearColor.rgb = pow(linearColor.rgb, vec3(GAMMA));
+ return linearColor;
+}
+
+vec4 linearToSrgb(in vec4 color) {
+ vec4 srgbColor = vec4(color);
+ srgbColor.rgb = pow(srgbColor.rgb, vec3(INV_GAMMA));
+ return srgbColor;
+}
+
+/*
+ * Normalizes a value inside a range to a normalized range [0,1].
+ */
+float normalizedRange(in float value, in float inMin, in float inMax) {
+ float valueClamped = clamp(value, inMin, inMax);
+ return (value - inMin) / (inMax - inMin);
+}
+
void main() {
- // gets the pixel value of the wallpaper for this uv coordinates on screen.
- gl_FragColor = texture2D(uTexture, vTextureCoordinates);
+ // Gets the pixel value of the wallpaper for this uv coordinates on screen.
+ vec4 color = srgbToLinear(texture2D(uTexture, vTextureCoordinates));
+ float lum = relativeLuminance(color.rgb);
+
+ // Transform it using the S curve created by the smoothstep. This will increase the contrast.
+ lum = smoothstep(0., 1., lum) + 0.001;
+
+ lum = relativeExposureCompensation(lum, mix(-15., 10., uExposure));
+ lum = mix(clamp(lum, 0.0, 1.0), 1.0, normalizedRange(uExposure, 0.55, 1.0));
+ color.rgb *= lum;
+
+ gl_FragColor = linearToSrgb(color);
}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 37ec576..d571f2f 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -102,4 +102,6 @@
<color name="privacy_circle_camera">#81C995</color> <!-- g300 -->
<color name="privacy_circle_microphone_location">#FCAD70</color> <!--o300 -->
+ <!-- Accessibility floating menu -->
+ <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4241724..0076c51 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -115,8 +115,6 @@
<color name="notification_section_header_label_color">@color/GM2_grey_900</color>
<color name="notification_section_clear_all_btn_color">@color/GM2_grey_700</color>
- <!-- The divider view for the notification channel editor half-shelf -->
- <color name="notification_channel_dialog_separator">@color/GM2_grey_200</color>
<color name="assist_orb_color">#ffffff</color>
@@ -153,10 +151,9 @@
<color name="minimize_dock_shadow_end">#00000000</color>
<color name="default_remote_input_background">@*android:color/notification_default_color</color>
- <color name="remote_input_text_enabled">#ffffffff</color>
<color name="remote_input_hint">#99ffffff</color>
- <color name="remote_input_accent">#eeeeee</color>
+ <color name="remote_input_accent">?android:attr/colorAccent</color>
<color name="quick_step_track_background_background_dark">#1F000000</color>
<color name="quick_step_track_background_background_light">#33FFFFFF</color>
@@ -277,4 +274,8 @@
<!-- TODO(b/178093014) Colors for privacy dialog. These should be changed to the new palette -->
<color name="privacy_circle_camera">#1E8E3E</color> <!-- g600 -->
<color name="privacy_circle_microphone_location">#E8710A</color> <!--o600 -->
+
+ <!-- Accessibility floating menu -->
+ <color name="accessibility_floating_menu_background">#CCFFFFFF</color> <!-- 80% -->
+ <color name="accessibility_floating_menu_stroke_dark">#26FFFFFF</color> <!-- 15% -->
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 92d9ca9..af6df32 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -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
+ 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 -->
@@ -588,4 +588,7 @@
<!-- Determines whether the shell features all run on another thread. -->
<bool name="config_enableShellMainThread">false</bool>
+
+ <!-- Determines whether to allow the nav bar handle to be forced to be opaque. -->
+ <bool name="allow_force_nav_bar_handle_opaque">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2062104..b050945 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1356,11 +1356,28 @@
<dimen name="people_space_image_radius">20dp</dimen>
<dimen name="people_space_widget_background_padding">6dp</dimen>
- <dimen name="rounded_slider_height">48dp</dimen>
+ <!-- Accessibility floating menu -->
+ <dimen name="accessibility_floating_menu_elevation">5dp</dimen>
+ <dimen name="accessibility_floating_menu_stroke_width">1dp</dimen>
+ <dimen name="accessibility_floating_menu_stroke_inset">-2dp</dimen>
+ <dimen name="accessibility_floating_menu_margin">16dp</dimen>
+ <dimen name="accessibility_floating_menu_padding">6dp</dimen>
+ <dimen name="accessibility_floating_menu_small_width_height">36dp</dimen>
+ <dimen name="accessibility_floating_menu_small_single_radius">25dp</dimen>
+ <dimen name="accessibility_floating_menu_small_multiple_radius">20dp</dimen>
+ <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
+ <dimen name="accessibility_floating_menu_large_single_radius">33dp</dimen>
+ <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
+
+ <dimen name="rounded_slider_height">44dp</dimen>
<!-- rounded_slider_height / 2 -->
- <dimen name="rounded_slider_corner_radius">24dp</dimen>
- <!-- rounded_slider_height / 2 -->
- <dimen name="rounded_slider_icon_size">24dp</dimen>
- <!-- rounded_slider_icon_size / 2 -->
+ <dimen name="rounded_slider_corner_radius">22dp</dimen>
+ <dimen name="rounded_slider_icon_size">20dp</dimen>
+ <!-- (rounded_slider_height - rounded_slider_icon_size) / 2 -->
<dimen name="rounded_slider_icon_inset">12dp</dimen>
+ <!-- rounded_slider_corner_radius - rounded_slider_track_corner_radius -->
+ <dimen name="rounded_slider_track_inset">18dp</dimen>
+ <dimen name="rounded_slider_track_width">8dp</dimen>
+ <!-- rounded_slider_track_width / 2 -->
+ <dimen name="rounded_slider_track_corner_radius">4dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index e7dc196..52a97b1 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,6 +38,8 @@
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
+ <bool name="flag_wallet">false</bool>
+
<!-- The new animations to/from lockscreen and AOD! -->
<bool name="flag_lockscreen_animations">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6207449..6858965 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -851,8 +851,6 @@
<string name="quick_settings_wifi_label">Wi-Fi</string>
<!-- QuickSettings: Internet [CHAR LIMIT=NONE] -->
<string name="quick_settings_internet_label">Internet</string>
- <!-- QuickSettings: Airplane-safe [CHAR LIMIT=NONE] -->
- <string name="quick_settings_airplane_safe_label">Airplane-safe</string>
<!-- QuickSettings: networks available [CHAR LIMIT=NONE] -->
<string name="quick_settings_networks_available">Networks available</string>
<!-- QuickSettings: networks unavailable [CHAR LIMIT=NONE] -->
@@ -1259,6 +1257,9 @@
<!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the organization can monitor network traffic on that device. The placeholder is the organization's name. [CHAR LIMIT=100] -->
<string name="quick_settings_disclosure_named_management_monitoring"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> owns this device and may monitor network traffic</string>
+ <!-- Disclosure at the bottom of Quick Settings that indicates that the user's financed device belongs to the Creditor. The placeholder is the Creditor's name. [CHAR LIMIT=100] -->
+ <string name="quick_settings_financed_disclosure_named_management">This device is provided by <xliff:g id="organization_name" example="Foo, Inc.">%s</xliff:g></string>
+
<!-- Disclosure at the bottom of Quick Settings that indicates that the user's device belongs to their organization, and the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=100] -->
<string name="quick_settings_disclosure_management_named_vpn">This device belongs to your organization and is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
@@ -1298,6 +1299,9 @@
<!-- Disclosure at the bottom of Quick Settings that indicates that the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=100] -->
<string name="quick_settings_disclosure_named_vpn">This device is connected to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
+ <!-- Monitoring dialog title for financed device [CHAR LIMIT=60] -->
+ <string name="monitoring_title_financed_device">This device is provided by <xliff:g id="organization_name" example="Foo, Inc.">%s</xliff:g></string>
+
<!-- Monitoring dialog title for device owned devices [CHAR LIMIT=35] -->
<string name="monitoring_title_device_owned">Device management</string>
@@ -1332,6 +1336,9 @@
<!-- Dialog that a user can access via Quick Settings. The dialog describes what the IT admin can monitor (and the changes they can make) on the user's device. [CHAR LIMIT=NONE]-->
<string name="monitoring_description_named_management">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g>.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin.</string>
+ <!-- Dialog that a user can access via Quick Settings. The dialog describes what a Creditor can monitor (and the changes they can make) on the user's financed device. [CHAR LIMIT=NONE]-->
+ <string name="monitoring_financed_description_named_management"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> may be able to access data associated with this device, manage apps, and change this device\’s settings.\n\nIf you have questions, contact <xliff:g id="organization_name" example="Foo, Inc.">%2$s</xliff:g>.</string>
+
<!-- Dialog that a user can access via Quick Settings. The dialog describes what the IT admin can monitor (and the changes they can make) on the user's device. [CHAR LIMIT=NONE]-->
<string name="monitoring_description_management">This device belongs to your organization.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin.</string>
@@ -1614,6 +1621,12 @@
<!-- Name of the alarm status bar icon. -->
<string name="status_bar_alarm">Alarm</string>
+ <!-- Wallet strings -->
+ <!-- Wallet empty state, title [CHAR LIMIT=32] -->
+ <string name="wallet_title">Wallet</string>
+ <!-- Secondary label of the quick access wallet tile. [CHAR LIMIT=32] -->
+ <string name="wallet_secondary_label">Ready</string>
+
<!-- Name of the work status bar icon. -->
<string name="status_bar_work">Work profile</string>
@@ -2297,6 +2310,9 @@
<!-- accessibility label for button to select user [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_user">Signed in as <xliff:g name="user" example="John">%s</xliff:g></string>
+ <!-- Accessibility description (not shown on the screen) of action to open user switcher when the multi-user icon is clicked. It will read as "Double-tap to choose user" in screen readers [CHAR LIMIT=NONE] -->
+ <string name="accessibility_quick_settings_choose_user_action">choose user</string>
+
<!-- Content description for no internet connection [CHAR LIMIT=NONE] -->
<string name="data_connection_no_internet">No internet</string>
@@ -2651,12 +2667,18 @@
<!-- Content description for magnification mode switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_description">Magnification switch</string>
<!-- A11y state description for magnification mode switch that device is in full-screen mode. [CHAR LIMIT=NONE] -->
- <string name="magnification_mode_switch_state_full_screen">Magnify entire screen</string>
+ <string name="magnification_mode_switch_state_full_screen">Magnify full screen</string>
<!-- A11y state description for magnification mode switch that device is in window mode. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_state_window">Magnify part of screen</string>
<!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_click_label">Switch</string>
+ <!-- Accessibility floating menu strings -->
+ <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] -->
+ <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string>
+ <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
+ <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
+
<!-- Device Controls strings -->
<!-- Device Controls empty state, title [CHAR LIMIT=30] -->
<string name="quick_controls_title">Device controls</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fb885cb..ff9ea01 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -384,10 +384,6 @@
<!-- Overridden by values-television/styles.xml with tv-specific settings -->
<style name="volume_dialog_theme" parent="qs_theme"/>
- <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light">
- <item name="android:colorAccent">@color/remote_input_accent</item>
- </style>
-
<style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index 35188d8..d2bff18 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -15,10 +15,11 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="140dp"
- android:minHeight="55dp"
- android:minResizeWidth="110dp"
- android:minResizeHeight="55dp"
+ android:minWidth="120dp"
+ android:minHeight="54dp"
+ android:minResizeWidth="60dp"
+ android:minResizeHeight="54dp"
+ android:maxResizeHeight="207dp"
android:updatePeriodMillis="60000"
android:description="@string/people_tile_description"
android:previewLayout="@layout/people_space_placeholder_layout"
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 09e9675a..f98a959 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -41,6 +41,7 @@
srcs: [
"src/**/*.java",
"src/**/I*.aidl",
+ ":wm_shell-aidls",
],
static_libs: [
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 72e4061..f50c3c92 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -20,7 +20,6 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.Choreographer;
-import android.view.Surface;
import android.view.SurfaceControl;
/**
@@ -91,24 +90,6 @@
.setPosition(leash, positionX, positionY);
}
- public void reset(SurfaceControl.Transaction tx, SurfaceControl leash, Rect destinationBounds,
- @Surface.Rotation int rotation) {
- resetScale(tx, leash, destinationBounds);
- if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
- final int degree = (rotation == Surface.ROTATION_90) ? -90 : 90;
- mTmpTransform.setRotate(degree, 0, 0);
- tx.setMatrix(leash, mTmpTransform, mTmpFloat9);
- }
- resetCornerRadius(tx, leash);
- crop(tx, leash, destinationBounds);
- }
-
- public void resetScale(SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect destinationBounds) {
- tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
- .setPosition(leash, destinationBounds.left, destinationBounds.top);
- }
-
public void resetCornerRadius(SurfaceControl.Transaction tx, SurfaceControl leash) {
tx.setCornerRadius(leash, 0);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 49e86f5..3da3085 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -16,11 +16,6 @@
package com.android.systemui.shared.recents;
-import android.app.PendingIntent;
-import android.app.PictureInPictureParams;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -28,26 +23,15 @@
import android.os.UserHandle;
import android.view.MotionEvent;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-import com.android.systemui.shared.recents.ISplitScreenListener;
-import com.android.systemui.shared.recents.IStartingWindowListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteTransitionCompat;
/**
* Temporary callbacks into SystemUI.
- * Next id = 44
*/
interface ISystemUiProxy {
/**
- * Proxies SurfaceControl.screenshotToBuffer().
- * @Removed
- * GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer,
- * int maxLayer, boolean useIdentityTransform, int rotation) = 0;
- */
-
- /**
* Begins screen pinning on the provided {@param taskId}.
*/
void startScreenPinning(int taskId) = 1;
@@ -63,15 +47,9 @@
Rect getNonMinimizedSplitScreenSecondaryBounds() = 7;
/**
- * Control the {@param alpha} of the back button in the navigation bar and {@param animate} if
- * needed from current value
- * @deprecated
- */
- void setBackButtonAlpha(float alpha, boolean animate) = 8;
-
- /**
* Control the {@param alpha} of the option nav bar button (back-button in 2 button mode
- * and home bar in no-button mode) and {@param animate} if needed from current value
+ * and home handle & background in gestural mode). The {@param animate} is currently only
+ * supported for 2 button mode.
*/
void setNavBarButtonAlpha(float alpha, boolean animate) = 19;
@@ -121,11 +99,6 @@
void stopScreenPinning() = 17;
/**
- * Sets the shelf height and visibility.
- */
- void setShelfHeight(boolean visible, int shelfHeight) = 20;
-
- /**
* Handle the provided image as if it was a screenshot.
*
* Deprecated, use handleImageBundleAsScreenshot with image bundle and UserTask
@@ -145,27 +118,12 @@
void notifySwipeToHomeFinished() = 23;
/**
- * Sets listener to get pinned stack animation callbacks.
- */
- void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) = 24;
-
- /**
* Notifies that quickstep will switch to a new task
* @param rotation indicates which Surface.Rotation the gesture was started in
*/
void onQuickSwitchToNewTask(int rotation) = 25;
/**
- * Start the one-handed mode.
- */
- void startOneHandedMode() = 26;
-
- /**
- * Stop the one-handed mode.
- */
- void stopOneHandedMode() = 27;
-
- /**
* Handle the provided image as if it was a screenshot.
*/
void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
@@ -176,88 +134,5 @@
*/
void expandNotificationPanel() = 29;
- /**
- * Notifies that Activity is about to be swiped to home with entering PiP transition and
- * queries the destination bounds for PiP depends on Launcher's rotation and shelf height.
- *
- * @param componentName ComponentName represents the Activity
- * @param activityInfo ActivityInfo tied to the Activity
- * @param pictureInPictureParams PictureInPictureParams tied to the Activity
- * @param launcherRotation Launcher rotation to calculate the PiP destination bounds
- * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
- * @return destination bounds the PiP window should land into
- */
- Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
- in PictureInPictureParams pictureInPictureParams,
- int launcherRotation, int shelfHeight) = 30;
-
- /**
- * Notifies the swiping Activity to PiP onto home transition is finished
- *
- * @param componentName ComponentName represents the Activity
- * @param destinationBounds the destination bounds the PiP window lands into
- */
- void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 31;
-
- /**
- * Registers a RemoteTransitionCompat that will handle transitions. This parameter bundles an
- * IRemoteTransition and a filter that must pass for it.
- */
- void registerRemoteTransition(in RemoteTransitionCompat remoteTransition) = 32;
-
- /** Unegisters a RemoteTransitionCompat that will handle transitions. */
- void unregisterRemoteTransition(in RemoteTransitionCompat remoteTransition) = 33;
-
-// SplitScreen APIs...copied from SplitScreen.java
- /**
- * Stage position isn't specified normally meaning to use what ever it is currently set to.
- */
- //int STAGE_POSITION_UNDEFINED = -1;
- /**
- * Specifies that a stage is positioned at the top half of the screen if
- * in portrait mode or at the left half of the screen if in landscape mode.
- */
- //int STAGE_POSITION_TOP_OR_LEFT = 0;
- /**
- * Specifies that a stage is positioned at the bottom half of the screen if
- * in portrait mode or at the right half of the screen if in landscape mode.
- */
- //int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
-
- /**
- * Stage type isn't specified normally meaning to use what ever the default is.
- * E.g. exit split-screen and launch the app in fullscreen.
- */
- //int STAGE_TYPE_UNDEFINED = -1;
- /**
- * The main stage type.
- * @see MainStage
- */
- //int STAGE_TYPE_MAIN = 0;
- /**
- * The side stage type.
- * @see SideStage
- */
- //int STAGE_TYPE_SIDE = 1;
-
- void registerSplitScreenListener(in ISplitScreenListener listener) = 34;
- void unregisterSplitScreenListener(in ISplitScreenListener listener) = 35;
-
- /** Hides the side-stage if it is currently visible. */
- void setSideStageVisibility(in boolean visible) = 36;
- /** Removes the split-screen stages. */
- void exitSplitScreen() = 37;
- /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
- void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 38;
- void startTask(in int taskId, in int stage, in int position, in Bundle options) = 39;
- void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
- in Bundle options, in UserHandle user) = 40;
- void startIntent(
- in PendingIntent intent, in Intent fillInIntent, in int stage, in int position,
- in Bundle options) = 41;
- void removeFromSideStage(in int taskId) = 42;
- /**
- * Sets listener to get task launching callbacks.
- */
- void setStartingWindowListener(IStartingWindowListener listener) = 43;
+ // Next id = 44
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 937c1df..41840af 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -41,6 +41,18 @@
public static final String KEY_EXTRA_INPUT_MONITOR = "extra_input_monitor";
public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
+ // See IPip.aidl
+ public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See ISplitScreen.aidl
+ public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+ // See IOneHanded.aidl
+ public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+ // See IShellTransitions.aidl
+ public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+ "extra_shell_shell_transitions";
+ // See IStartingWindow.aidl
+ public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+ "extra_shell_starting_window";
public static final String NAV_BAR_MODE_2BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index c2d52a7..bf0d29a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -76,10 +76,14 @@
* accordingly. This should be called before `finish`
* @param taskId Task id of the Activity in PiP mode.
* @param destinationBounds Bounds of the PiP window on home.
+ * @param windowCrop bounds to crop as part of final transform.
+ * @param float9 An array of 9 floats to be used as matrix transform.
*/
- public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+ public void setFinishTaskBounds(int taskId, Rect destinationBounds, Rect windowCrop,
+ float[] float9) {
try {
- mAnimationController.setFinishTaskBounds(taskId, destinationBounds);
+ mAnimationController.setFinishTaskBounds(taskId, destinationBounds, windowCrop,
+ float9);
} catch (RemoteException e) {
Log.d(TAG, "Failed to set finish task bounds", e);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 400bf15..2f38f0a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.TransitionOldType;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import android.os.RemoteException;
import android.util.Log;
@@ -35,6 +36,8 @@
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
+import java.util.ArrayList;
+
/**
* @see RemoteAnimationAdapter
*/
@@ -100,6 +103,52 @@
};
}
+ private static class CounterRotator {
+ SurfaceControl mSurface = null;
+ ArrayList<SurfaceControl> mRotateChildren = null;
+
+ void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
+ float displayW, float displayH) {
+ if (rotateDelta == 0) return;
+ mRotateChildren = new ArrayList<>();
+ // We want to counter-rotate, so subtract from 4
+ rotateDelta = 4 - (rotateDelta + 4) % 4;
+ mSurface = new SurfaceControl.Builder()
+ .setName("Transition Unrotate")
+ .setContainerLayer()
+ .setParent(parent)
+ .build();
+ // column-major
+ if (rotateDelta == 1) {
+ t.setMatrix(mSurface, 0, 1, -1, 0);
+ t.setPosition(mSurface, displayW, 0);
+ } else if (rotateDelta == 2) {
+ t.setMatrix(mSurface, -1, 0, 0, -1);
+ t.setPosition(mSurface, displayW, displayH);
+ } else if (rotateDelta == 3) {
+ t.setMatrix(mSurface, 0, -1, 1, 0);
+ t.setPosition(mSurface, 0, displayH);
+ }
+ t.show(mSurface);
+ }
+
+ void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
+ if (mSurface == null) return;
+ t.reparent(child, mSurface);
+ mRotateChildren.add(child);
+ }
+
+ void cleanUp(SurfaceControl rootLeash) {
+ if (mSurface == null) return;
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (int i = mRotateChildren.size() - 1; i >= 0; --i) {
+ t.reparent(mRotateChildren.get(i), rootLeash);
+ }
+ t.remove(mSurface);
+ t.apply();
+ }
+ }
+
private static IRemoteTransition.Stub wrapRemoteTransition(
final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
return new IRemoteTransition.Stub() {
@@ -116,17 +165,46 @@
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
boolean isReturnToHome = false;
+ TransitionInfo.Change launcherTask = null;
+ TransitionInfo.Change wallpaper = null;
+ int launcherLayer = 0;
+ int rotateDelta = 0;
+ float displayW = 0;
+ float displayH = 0;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getTaskInfo() != null
&& change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
isReturnToHome = change.getMode() == TRANSIT_OPEN
|| change.getMode() == TRANSIT_TO_FRONT;
- break;
+ launcherTask = change;
+ launcherLayer = info.getChanges().size() - i;
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ wallpaper = change;
+ }
+ if (change.getParent() == null && change.getEndRotation() >= 0
+ && change.getEndRotation() != change.getStartRotation()) {
+ rotateDelta = change.getEndRotation() - change.getStartRotation();
+ displayW = change.getEndAbsBounds().width();
+ displayH = change.getEndAbsBounds().height();
+ }
+ }
+
+ // Prepare for rotation if there is one
+ final CounterRotator counterLauncher = new CounterRotator();
+ final CounterRotator counterWallpaper = new CounterRotator();
+ if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
+ counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterLauncher.mSurface != null) {
+ t.setLayer(counterLauncher.mSurface, launcherLayer);
}
}
if (isReturnToHome) {
+ if (counterLauncher.mSurface != null) {
+ t.setLayer(counterLauncher.mSurface, info.getChanges().size() * 3);
+ }
// Need to "boost" the closing things since that's what launcher expects.
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -136,6 +214,7 @@
if (!TransitionInfo.isIndependent(change, info)) continue;
if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
t.setLayer(leash, info.getChanges().size() * 3 - i);
+ counterLauncher.addChild(t, leash);
}
}
// Make wallpaper visible immediately since launcher apparently won't do this.
@@ -143,6 +222,18 @@
t.show(wallpapersCompat[i].leash.getSurfaceControl());
t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f);
}
+ } else {
+ if (launcherTask != null) {
+ counterLauncher.addChild(t, launcherTask.getLeash());
+ }
+ if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
+ counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterWallpaper.mSurface != null) {
+ t.setLayer(counterWallpaper.mSurface, -1);
+ counterWallpaper.addChild(t, wallpaper.getLeash());
+ }
+ }
}
t.apply();
@@ -150,6 +241,8 @@
@Override
public void run() {
try {
+ counterLauncher.cleanUp(info.getRootLeash());
+ counterWallpaper.cleanUp(info.getRootLeash());
finishCallback.onTransitionFinished(null /* wct */);
} catch (RemoteException e) {
Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 87f6b82..246476a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -56,6 +56,7 @@
public final boolean isNotInRecents;
public final Rect contentInsets;
public final PictureInPictureParams pictureInPictureParams;
+ public final int rotationChange;
private final SurfaceControl mStartLeash;
@@ -74,6 +75,7 @@
contentInsets = app.contentInsets;
activityType = app.windowConfiguration.getActivityType();
pictureInPictureParams = app.pictureInPictureParams;
+ rotationChange = 0;
mStartLeash = app.startLeash;
}
@@ -102,7 +104,7 @@
localBounds = new Rect(change.getEndAbsBounds());
localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
sourceContainerBounds = null;
- screenSpaceBounds = change.getEndAbsBounds();
+ screenSpaceBounds = new Rect(change.getEndAbsBounds());
prefixOrderIndex = order;
// TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.
contentInsets = new Rect(0, 0, 0, 0);
@@ -115,6 +117,7 @@
}
pictureInPictureParams = null;
mStartLeash = null;
+ rotationChange = change.getEndRotation() - change.getStartRotation();
}
public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 7ba9069..06155bc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -137,8 +137,11 @@
mWrapped.hideCurrentInputMethod();
}
- @Override public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
- if (mWrapped != null) mWrapped.setFinishTaskBounds(taskId, destinationBounds);
+ @Override public void setFinishTaskBounds(int taskId, Rect destinationBounds,
+ Rect windowCrop, float[] float9) {
+ if (mWrapped != null) {
+ mWrapped.setFinishTaskBounds(taskId, destinationBounds, windowCrop, float9);
+ }
}
@Override public void finish(boolean toHome, boolean sendUserLeaveHint) {
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
index 64b3d73..c918d98 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -38,8 +38,10 @@
* The time's text color is a gradient that changes its colors based on its controller.
*/
public class AnimatableClockView extends TextView {
- private static final CharSequence FORMAT_12_HOUR = "hh\nmm";
- private static final CharSequence FORMAT_24_HOUR = "HH\nmm";
+ private static final CharSequence DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm";
+ private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
+ private static final CharSequence SINGLE_LINE_FORMAT_12_HOUR = "h:mm";
+ private static final CharSequence SINGLE_LINE_FORMAT_24_HOUR = "H:mm";
private static final long ANIM_DURATION = 300;
private final Calendar mTime = Calendar.getInstance();
@@ -55,6 +57,8 @@
private TextAnimator mTextAnimator = null;
private Runnable mOnTextAnimatorInitialized;
+ private boolean mIsSingleLine;
+
public AnimatableClockView(Context context) {
this(context, null, 0, 0);
}
@@ -78,6 +82,15 @@
} finally {
ta.recycle();
}
+
+ ta = context.obtainStyledAttributes(
+ attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
+ try {
+ mIsSingleLine = ta.getBoolean(android.R.styleable.TextView_singleLine, false);
+ } finally {
+ ta.recycle();
+ }
+
refreshFormat();
}
@@ -171,7 +184,16 @@
void refreshFormat() {
final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
- mFormat = use24HourFormat ? FORMAT_24_HOUR : FORMAT_12_HOUR;
+ if (mIsSingleLine && use24HourFormat) {
+ mFormat = SINGLE_LINE_FORMAT_24_HOUR;
+ } else if (!mIsSingleLine && use24HourFormat) {
+ mFormat = DOUBLE_LINE_FORMAT_24_HOUR;
+ } else if (mIsSingleLine && !use24HourFormat) {
+ mFormat = SINGLE_LINE_FORMAT_12_HOUR;
+ } else {
+ mFormat = DOUBLE_LINE_FORMAT_12_HOUR;
+ }
+
mDescFormat = getBestDateTimePattern(getContext(), use24HourFormat ? "Hm" : "hm");
refreshTime();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index a580663..9f32c03 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -185,8 +185,8 @@
if (timeoutMs == 0) {
mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
}
+ mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
}
- mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
}
protected void verifyPasswordAndUnlock() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 93ed0ea..13a84fb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -174,7 +174,7 @@
if (mode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
final int startEndPadding = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
- 12,
+ 32,
getResources().getDisplayMetrics());
setPaddingRelative(startEndPadding, 0, startEndPadding, 0);
mSmallClockFrame.setVisibility(GONE);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index ea60f0d..72dd72e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -29,6 +29,7 @@
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -180,6 +181,7 @@
/** Initialize the Controller. */
public void onInit() {
mKeyguardSecurityContainerController.init();
+ updateResources();
}
@Override
@@ -467,5 +469,23 @@
mSecurityCallback.finish(strongAuth, currentUser);
}
+ /**
+ * 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 gravity = mView.getResources().getInteger(R.integer.keyguard_host_view_gravity);
+ // Android SysUI uses a FrameLayout as the top-level, but Auto uses RelativeLayout.
+ // We're just changing the gravity here though (which can't be applied to RelativeLayout),
+ // so only attempt the update if mView is inside a FrameLayout.
+ if (mView.getLayoutParams() instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mView.getLayoutParams();
+ if (lp.gravity != gravity) {
+ lp.gravity = gravity;
+ mView.setLayoutParams(lp);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index de64f07..40190c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -31,6 +31,7 @@
* A Base class for all Keyguard password/pattern/pin related inputs.
*/
public abstract class KeyguardInputView extends LinearLayout {
+ private Runnable mOnFinishImeAnimationRunnable;
public KeyguardInputView(Context context) {
super(context);
@@ -47,7 +48,8 @@
abstract CharSequence getTitle();
- void animateForIme(float interpolatedFraction) {
+ void animateForIme(float interpolatedFraction, boolean appearingAnim) {
+ return;
}
boolean disallowInterceptTouch(MotionEvent event) {
@@ -85,4 +87,14 @@
};
}
+ public void setOnFinishImeAnimationRunnable(Runnable onFinishImeAnimationRunnable) {
+ mOnFinishImeAnimationRunnable = onFinishImeAnimationRunnable;
+ }
+
+ public void runOnFinishImeAnimationRunnable() {
+ if (mOnFinishImeAnimationRunnable != null) {
+ mOnFinishImeAnimationRunnable.run();
+ mOnFinishImeAnimationRunnable = null;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 533bec1..cd9627f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.view.WindowInsets.Type.ime;
+
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
@@ -23,15 +25,25 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
@@ -41,6 +53,8 @@
private final int mDisappearYTranslation;
+ private static final long IME_DISAPPEAR_DURATION_MS = 125;
+
// A delay constant to be used in a workaround for the situation where InputMethodManagerService
// is not switched to the new user yet.
// TODO: Remove this by ensuring such a race condition never happens.
@@ -150,9 +164,63 @@
}
@Override
- public void animateForIme(float interpolatedFraction) {
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ getWindowInsetsController().controlWindowInsetsAnimation(ime(),
+ 100,
+ Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
+
+ @Override
+ public void onReady(@NonNull WindowInsetsAnimationController controller,
+ int types) {
+ ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
+ anim.addUpdateListener(animation -> {
+ if (controller.isCancelled()) {
+ return;
+ }
+ Insets shownInsets = controller.getShownStateInsets();
+ Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
+ (int) (-shownInsets.bottom / 4
+ * anim.getAnimatedFraction())));
+ controller.setInsetsAndAlpha(insets,
+ (float) animation.getAnimatedValue(),
+ anim.getAnimatedFraction());
+ });
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ controller.finish(false);
+ runOnFinishImeAnimationRunnable();
+ finishRunnable.run();
+ }
+ });
+ anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ anim.start();
+ }
+
+ @Override
+ public void onFinished(
+ @NonNull WindowInsetsAnimationController controller) {
+ }
+
+ @Override
+ public void onCancelled(
+ @Nullable WindowInsetsAnimationController controller) {
+ }
+ });
+ return true;
+ }
+
+
+ @Override
+ public void animateForIme(float interpolatedFraction, boolean appearingAnim) {
animate().cancel();
- setAlpha(Math.max(interpolatedFraction, getAlpha()));
+ setAlpha(appearingAnim
+ ? Math.max(interpolatedFraction, getAlpha())
+ : 1 - interpolatedFraction);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 57b8cf0..e45dd8b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -189,23 +189,22 @@
return;
}
if (wasDisabled) {
- mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+ showInput();
}
}
@Override
public void onResume(int reason) {
super.onResume(reason);
-
- mPasswordEntry.requestFocus();
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput();
}
}
private void showInput() {
- mPasswordEntry.post(() -> {
- if (mPasswordEntry.isFocused() && mView.isShown()) {
+ mView.post(() -> {
+ if (mView.isShown()) {
+ mPasswordEntry.requestFocus();
mInputMethodManager.showSoftInput(
mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
}
@@ -214,7 +213,18 @@
@Override
public void onPause() {
- super.onPause();
+ if (!mPasswordEntry.isVisibleToUser()) {
+ // Reset all states directly and then hide IME when the screen turned off.
+ super.onPause();
+ } else {
+ // In order not to break the IME hide animation by resetting states too early after
+ // the password checked, make sure resetting states after the IME hiding animation
+ // finished.
+ mView.setOnFinishImeAnimationRunnable(() -> {
+ mPasswordEntry.clearFocus();
+ super.onPause();
+ });
+ }
mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 4f48bb4..e16c01a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -109,7 +109,7 @@
// Treat single-sized patterns as erroneous taps.
if (pattern.size() == 1) {
mFalsingCollector.updateFalseConfidence(FalsingClassifier.Result.falsed(
- 0.7, "empty pattern input"));
+ 0.7, getClass().getSimpleName(), "empty pattern input"));
}
mLockPatternView.enableInput();
onPatternChecked(userId, false, 0, false /* not valid - too short */);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index eaf8516..4887767 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -23,11 +23,9 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.provider.Settings;
import android.util.AttributeSet;
@@ -42,12 +40,9 @@
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
-import android.view.WindowInsetsAnimationControlListener;
-import android.view.WindowInsetsAnimationController;
import android.view.WindowManager;
import android.widget.FrameLayout;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -130,6 +125,9 @@
WindowInsetsAnimation.Bounds bounds) {
if (!mDisappearAnimRunning) {
beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
+ } else {
+ beginJankInstrument(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
return bounds;
@@ -138,25 +136,28 @@
@Override
public WindowInsets onProgress(WindowInsets windowInsets,
List<WindowInsetsAnimation> list) {
- if (mDisappearAnimRunning) {
- mSecurityViewFlipper.setTranslationY(
- mInitialBounds.bottom - mFinalBounds.bottom);
- } else {
- int translationY = 0;
- float interpolatedFraction = 1f;
- for (WindowInsetsAnimation animation : list) {
- if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
- continue;
- }
- interpolatedFraction = animation.getInterpolatedFraction();
-
- final int paddingBottom = (int) MathUtils.lerp(
- mInitialBounds.bottom - mFinalBounds.bottom, 0,
- interpolatedFraction);
- translationY += paddingBottom;
+ float start = mDisappearAnimRunning
+ ? -(mFinalBounds.bottom - mInitialBounds.bottom)
+ : mInitialBounds.bottom - mFinalBounds.bottom;
+ float end = mDisappearAnimRunning
+ ? -((mFinalBounds.bottom - mInitialBounds.bottom) * 0.75f)
+ : 0f;
+ int translationY = 0;
+ float interpolatedFraction = 1f;
+ for (WindowInsetsAnimation animation : list) {
+ if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
+ continue;
}
- mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction);
+ interpolatedFraction = animation.getInterpolatedFraction();
+
+ final int paddingBottom = (int) MathUtils.lerp(
+ start, end,
+ interpolatedFraction);
+ translationY += paddingBottom;
}
+ mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction,
+ !mDisappearAnimRunning);
+
return windowInsets;
}
@@ -164,7 +165,10 @@
public void onEnd(WindowInsetsAnimation animation) {
if (!mDisappearAnimRunning) {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
- mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f);
+ mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f,
+ true /* appearingAnim */);
+ } else {
+ endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
}
};
@@ -522,63 +526,6 @@
public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
- if (securitySelection == SecurityMode.Password) {
- mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
- IME_DISAPPEAR_DURATION_MS,
- Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
-
-
- @Override
- public void onReady(@NonNull WindowInsetsAnimationController controller,
- int types) {
- ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
- anim.addUpdateListener(animation -> {
- if (controller.isCancelled()) {
- return;
- }
- Insets shownInsets = controller.getShownStateInsets();
- Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
- (int) (-shownInsets.bottom / 4
- * anim.getAnimatedFraction())));
- controller.setInsetsAndAlpha(insets,
- (float) animation.getAnimatedValue(),
- anim.getAnimatedFraction());
- });
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- beginJankInstrument(
- InteractionJankMonitor
- .CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- endJankInstrument(
- InteractionJankMonitor
- .CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
- controller.finish(false);
- }
- });
- anim.setDuration(IME_DISAPPEAR_DURATION_MS);
- anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- anim.start();
- }
-
- @Override
- public void onFinished(
- @NonNull WindowInsetsAnimationController controller) {
- mDisappearAnimRunning = false;
- }
-
- @Override
- public void onCancelled(
- @Nullable WindowInsetsAnimationController controller) {
- cancelJankInstrument(
- InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
- }
- });
- }
}
private void beginJankInstrument(int cuj) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 7eac903..ccba1d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -48,7 +48,6 @@
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -74,8 +73,6 @@
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
private final SecurityCallback mSecurityCallback;
private final ConfigurationController mConfigurationController;
- private final KeyguardViewController mKeyguardViewController;
- private final FalsingManager mFalsingManager;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
@@ -97,13 +94,8 @@
}
mTouchDown = MotionEvent.obtain(ev);
} else if (mTouchDown != null) {
- boolean tapResult = mFalsingManager.isFalseTap(true, 0.6);
- if (tapResult
- || ev.getActionMasked() == MotionEvent.ACTION_UP
+ if (ev.getActionMasked() == MotionEvent.ACTION_UP
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- if (tapResult) {
- mKeyguardViewController.reset(true);
- }
mTouchDown.recycle();
mTouchDown = null;
}
@@ -207,9 +199,7 @@
KeyguardStateController keyguardStateController,
SecurityCallback securityCallback,
KeyguardSecurityViewFlipperController securityViewFlipperController,
- ConfigurationController configurationController,
- KeyguardViewController keyguardViewController,
- FalsingManager falsingManager) {
+ ConfigurationController configurationController) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -222,8 +212,6 @@
mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
mKeyguardSecurityCallback);
mConfigurationController = configurationController;
- mKeyguardViewController = keyguardViewController;
- mFalsingManager = falsingManager;
}
@Override
@@ -523,8 +511,6 @@
private final KeyguardStateController mKeyguardStateController;
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
private final ConfigurationController mConfigurationController;
- private final KeyguardViewController mKeyguardViewController;
- private final FalsingManager mFalsingManager;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -537,9 +523,7 @@
UiEventLogger uiEventLogger,
KeyguardStateController keyguardStateController,
KeyguardSecurityViewFlipperController securityViewFlipperController,
- ConfigurationController configurationController,
- KeyguardViewController keyguardViewController,
- FalsingManager falsingManager) {
+ ConfigurationController configurationController) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -550,8 +534,6 @@
mKeyguardStateController = keyguardStateController;
mSecurityViewFlipperController = securityViewFlipperController;
mConfigurationController = configurationController;
- mKeyguardViewController = keyguardViewController;
- mFalsingManager = falsingManager;
}
public KeyguardSecurityContainerController create(
@@ -560,7 +542,7 @@
mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
- mConfigurationController, mKeyguardViewController, mFalsingManager);
+ mConfigurationController);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 75ef4b32..e01e17d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -87,10 +87,10 @@
* Translate the entire view, and optionally inform the wrapped view of the progress
* so it can animate with the parent.
*/
- public void animateForIme(int translationY, float interpolatedFraction) {
+ public void animateForIme(int translationY, float interpolatedFraction, boolean appearingAnim) {
super.setTranslationY(translationY);
KeyguardInputView v = getSecurityView();
- if (v != null) v.animateForIme(interpolatedFraction);
+ if (v != null) v.animateForIme(interpolatedFraction, appearingAnim);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 97d6e97..6cee4a4 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -15,6 +15,7 @@
*/
package com.android.keyguard;
+import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -23,7 +24,6 @@
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.view.ContextThemeWrapper;
-import android.view.ViewGroup;
import androidx.annotation.StyleRes;
@@ -34,11 +34,12 @@
* Provides background color and radius animations for key pad buttons.
*/
class NumPadAnimator {
- private ValueAnimator mAnimator;
+ private AnimatorSet mAnimator;
+ private ValueAnimator mExpandAnimator;
+ private ValueAnimator mContractAnimator;
private GradientDrawable mBackground;
private RippleDrawable mRipple;
private GradientDrawable mRippleMask;
- private int mMargin;
private int mNormalColor;
private int mHighlightColor;
private int mStyle;
@@ -52,32 +53,37 @@
reloadColors(context);
- mMargin = context.getResources().getDimensionPixelSize(R.dimen.num_pad_key_margin);
-
// Actual values will be updated later, usually during an onLayout() call
- mAnimator = ValueAnimator.ofFloat(0f);
- mAnimator.setDuration(100);
- mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- mAnimator.setRepeatMode(ValueAnimator.REVERSE);
- mAnimator.setRepeatCount(1);
- mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ mAnimator = new AnimatorSet();
+ mExpandAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mExpandAnimator.setDuration(50);
+ mExpandAnimator.setInterpolator(Interpolators.LINEAR);
+ mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator anim) {
mBackground.setCornerRadius((float) anim.getAnimatedValue());
mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
}
});
- }
-
- void updateMargin(ViewGroup.MarginLayoutParams lp) {
- lp.setMargins(mMargin, mMargin, mMargin, mMargin);
+ mContractAnimator = ValueAnimator.ofFloat(1f, 0f);
+ mContractAnimator.setStartDelay(33);
+ mContractAnimator.setDuration(417);
+ mContractAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mContractAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator anim) {
+ mBackground.setCornerRadius((float) anim.getAnimatedValue());
+ mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
+ }
+ });
+ mAnimator.playSequentially(mExpandAnimator, mContractAnimator);
}
void onLayout(int height) {
float startRadius = height / 2f;
float endRadius = height / 4f;
mBackground.setCornerRadius(startRadius);
- mAnimator.setFloatValues(startRadius, endRadius);
+ mExpandAnimator.setFloatValues(startRadius, endRadius);
+ mContractAnimator.setFloatValues(endRadius, startRadius);
}
void start() {
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 5db3349..b76499a 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,12 +17,14 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.VectorDrawable;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.MotionEvent;
-import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -32,20 +34,19 @@
*/
public class NumPadButton extends AlphaOptimizedImageButton {
+ @Nullable
private NumPadAnimator mAnimator;
public NumPadButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
- attrs.getStyleAttribute());
- }
-
- @Override
- public void setLayoutParams(ViewGroup.LayoutParams params) {
- if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
-
- super.setLayoutParams(params);
+ Drawable background = getBackground();
+ if (background instanceof LayerDrawable) {
+ mAnimator = new NumPadAnimator(context, (LayerDrawable) background,
+ attrs.getStyleAttribute());
+ } else {
+ mAnimator = null;
+ }
}
@Override
@@ -93,9 +94,11 @@
* By default, the new layout will be enabled. Invoking will revert to the old style
*/
public void disableNewLayout() {
- mAnimator = null;
- ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
- setBackground(getContext().getResources().getDrawable(
- R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ if (mAnimator != null) {
+ mAnimator = null;
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+ setBackground(getContext().getResources().getDrawable(
+ R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index e6a9d4f..89c1a7f 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -31,6 +32,8 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -48,6 +51,7 @@
private int mTextViewResId;
private PasswordTextView mTextView;
+ @Nullable
private NumPadAnimator mAnimator;
private View.OnClickListener mListener = new View.OnClickListener() {
@@ -127,8 +131,13 @@
setContentDescription(mDigitText.getText().toString());
- mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
- R.style.NumPadKey);
+ Drawable background = getBackground();
+ if (background instanceof LayerDrawable) {
+ mAnimator = new NumPadAnimator(context, (LayerDrawable) background,
+ R.style.NumPadKey);
+ } else {
+ mAnimator = null;
+ }
}
/**
@@ -136,10 +145,12 @@
*/
public void disableNewLayout() {
findViewById(R.id.klondike_text).setVisibility(View.VISIBLE);
- mAnimator = null;
- ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
- setBackground(getContext().getResources().getDrawable(
- R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ if (mAnimator != null) {
+ mAnimator = null;
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+ setBackground(getContext().getResources().getDrawable(
+ R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ }
}
/**
@@ -167,13 +178,6 @@
}
@Override
- public void setLayoutParams(ViewGroup.LayoutParams params) {
- if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
-
- super.setLayoutParams(params);
- }
-
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 5ffc283..b80f8bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -121,9 +121,19 @@
public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- setFocusableInTouchMode(true);
- setFocusable(true);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
+ TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.View);
+ try {
+ // If defined, use the provided values. If not, set them to true by default.
+ boolean isFocusable = a.getBoolean(android.R.styleable.View_focusable,
+ /* defValue= */ true);
+ boolean isFocusableInTouchMode = a.getBoolean(
+ android.R.styleable.View_focusableInTouchMode, /* defValue= */ true);
+ setFocusable(isFocusable);
+ setFocusableInTouchMode(isFocusableInTouchMode);
+ } finally {
+ a.recycle();
+ }
+ a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
try {
mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
mGravity = a.getInt(R.styleable.PasswordTextView_android_gravity, Gravity.CENTER);
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 1765627..cd53a34 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -326,6 +326,9 @@
if (mBatteryPercentView != null) {
if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
+ if (mBatteryPercentView == null) {
+ return;
+ }
if (estimate != null) {
mBatteryPercentView.setText(estimate);
setContentDescription(getContext().getString(
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ae4c8e5..06b486e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -34,6 +34,9 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.clock.ClockManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -284,6 +287,8 @@
@Inject Lazy<IWindowManager> mIWindowManager;
@Inject Lazy<OverviewProxyService> mOverviewProxyService;
@Inject Lazy<NavigationModeController> mNavBarModeController;
+ @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
+ @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
@Inject Lazy<EnhancedEstimates> mEnhancedEstimates;
@Inject Lazy<VibratorHelper> mVibratorHelper;
@Inject Lazy<IStatusBarService> mIStatusBarService;
@@ -294,6 +299,7 @@
@Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
@Inject Lazy<AppOpsController> mAppOpsController;
@Inject Lazy<NavigationBarController> mNavigationBarController;
+ @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
@Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper;
@@ -470,6 +476,11 @@
mProviders.put(NavigationModeController.class, mNavBarModeController::get);
+ mProviders.put(AccessibilityButtonModeObserver.class,
+ mAccessibilityButtonModeObserver::get);
+ mProviders.put(AccessibilityButtonTargetsObserver.class,
+ mAccessibilityButtonListController::get);
+
mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get);
mProviders.put(VibratorHelper.class, mVibratorHelper::get);
@@ -489,6 +500,9 @@
mProviders.put(NavigationBarController.class, mNavigationBarController::get);
+ mProviders.put(AccessibilityFloatingMenuController.class,
+ mAccessibilityFloatingMenuController::get);
+
mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
mProviders.put(NotificationLockscreenUserManager.class,
mNotificationLockscreenUserManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index a9b4c49..8b68ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -25,15 +25,20 @@
import android.os.SystemClock;
import android.os.Trace;
import android.service.wallpaper.WallpaperService;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.MathUtils;
import android.util.Size;
+import android.view.Choreographer;
import android.view.SurfaceHolder;
+import android.view.WindowManager;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.glwallpaper.EglHelper;
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -50,18 +55,23 @@
private static final String TAG = ImageWallpaper.class.getSimpleName();
// We delayed destroy render context that subsequent render requests have chance to cancel it.
// This is to avoid destroying then recreating render context in a very short time.
- private static final int DELAY_FINISH_RENDERING = 1000;
+ private static final int DELAY_FINISH_RENDERING = 3000;
private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
private static final boolean DEBUG = false;
- private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
+ private final StatusBarStateController mStatusBarStateController;
+ private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
+ private final ArraySet<RectF> mColorAreas = new ArraySet<>();
+ private float mShift;
+ private volatile int mPages;
private HandlerThread mWorker;
// scaled down version
private Bitmap mMiniBitmap;
@Inject
- public ImageWallpaper() {
+ public ImageWallpaper(StatusBarStateController statusBarStateController) {
super();
+ mStatusBarStateController = statusBarStateController;
}
@Override
@@ -84,7 +94,9 @@
mMiniBitmap = null;
}
- class GLEngine extends Engine {
+
+ class GLEngine extends Engine implements StatusBarStateController.StateListener,
+ Choreographer.FrameCallback {
// Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
// set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
@VisibleForTesting
@@ -95,7 +107,12 @@
private ImageWallpaperRenderer mRenderer;
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
- private boolean mNeedRedraw;
+ private int mWidth = 1;
+ private int mHeight = 1;
+ private int mImgWidth = 1;
+ private int mImgHeight = 1;
+ private volatile float mDozeAmount;
+ private volatile boolean mNewDozeValue = false;
GLEngine() {
}
@@ -111,12 +128,23 @@
// Deferred init renderer because we need to get wallpaper by display context.
mRenderer = getRendererInstance();
setFixedSizeAllowed(true);
- setOffsetNotificationsEnabled(false);
updateSurfaceSize();
+ Rect window = getDisplayContext()
+ .getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ mHeight = window.height();
+ mWidth = window.width();
mMiniBitmap = null;
if (mWorker != null && mWorker.getThreadHandler() != null) {
- mWorker.getThreadHandler().post(this::updateMiniBitmap);
+ mWorker.getThreadHandler().post(() -> {
+ updateMiniBitmap();
+ Choreographer.getInstance().postFrameCallback(GLEngine.this);
+ });
}
+
+ mDozeAmount = mStatusBarStateController.getDozeAmount();
+ mStatusBarStateController.addCallback(this);
}
EglHelper getEglHelperInstance() {
@@ -127,6 +155,41 @@
return new ImageWallpaperRenderer(getDisplayContext());
}
+ @Override
+ public void onOffsetsChanged(float xOffset, float yOffset,
+ float xOffsetStep, float yOffsetStep,
+ int xPixelOffset, int yPixelOffset) {
+ if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
+ final int pages;
+ if (xOffsetStep > 0 && xOffsetStep <= 1) {
+ pages = (int) (1 / xOffsetStep + 1);
+ } else {
+ pages = 1;
+ }
+ if (pages == mPages) return;
+ mPages = pages;
+ updateShift();
+ mWorker.getThreadHandler().post(() ->
+ computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
+ }
+
+ private void updateShift() {
+ if (mImgHeight == 0) {
+ mShift = 0;
+ return;
+ }
+ // calculate shift
+ float imgWidth = (float) mImgWidth / (float) mImgHeight;
+ float displayWidth =
+ (float) mWidth / (float) mHeight;
+ // if need to shift
+ if (imgWidth > displayWidth) {
+ mShift = imgWidth / imgWidth - displayWidth / imgWidth;
+ } else {
+ mShift = 0;
+ }
+ }
+
private void updateMiniBitmap() {
mRenderer.useBitmap(b -> {
int size = Math.min(b.getWidth(), b.getHeight());
@@ -134,6 +197,8 @@
if (size > MIN_SURFACE_WIDTH) {
scale = (float) MIN_SURFACE_WIDTH / (float) size;
}
+ mImgHeight = b.getHeight();
+ mImgWidth = b.getWidth();
mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()),
Math.round(scale * b.getHeight()), false);
computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
@@ -158,11 +223,14 @@
public void onDestroy() {
mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
+ Choreographer.getInstance().removeFrameCallback(this);
mRenderer.finish();
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
});
+
+ mStatusBarStateController.removeCallback(this);
}
@Override
@@ -173,6 +241,9 @@
@Override
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
mWorker.getThreadHandler().post(() -> {
+ if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) {
+ setOffsetNotificationsEnabled(true);
+ }
Bitmap bitmap = mMiniBitmap;
if (bitmap == null) {
mLocalColorsToAdd.addAll(regions);
@@ -184,6 +255,7 @@
private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
+ mColorAreas.addAll(regions);
try {
notifyLocalColorsChanged(regions, colors);
} catch (RuntimeException e) {
@@ -193,14 +265,45 @@
@Override
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
- // No-OP
+ mWorker.getThreadHandler().post(() -> {
+ mColorAreas.removeAll(regions);
+ mLocalColorsToAdd.removeAll(regions);
+ if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) {
+ setOffsetNotificationsEnabled(false);
+ }
+ });
+ }
+
+ private RectF pageToImgRect(RectF area) {
+ float pageWidth = 1f / (float) mPages;
+ if (pageWidth < 1 && pageWidth >= 0) pageWidth = 1;
+ float imgWidth = (float) mImgWidth / (float) mImgHeight;
+ float displayWidth =
+ (float) mWidth / (float) mHeight;
+ float expansion = imgWidth > displayWidth ? displayWidth / imgWidth : 1;
+ int page = (int) Math.floor(area.centerX() / pageWidth);
+ float shiftWidth = mShift * page * pageWidth;
+ RectF imgArea = new RectF();
+ imgArea.bottom = area.bottom;
+ imgArea.top = area.top;
+ imgArea.left = MathUtils.constrain(area.left % pageWidth, 0, 1)
+ * expansion + shiftWidth;
+ imgArea.right = MathUtils.constrain(area.right % pageWidth, 0, 1)
+ * expansion + shiftWidth;
+ if (imgArea.left > imgArea.right) {
+ // take full page
+ imgArea.left = shiftWidth;
+ imgArea.right = 1 - (mShift - shiftWidth);
+ }
+ return imgArea;
}
private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
Bitmap b) {
List<WallpaperColors> colors = new ArrayList<>(areas.size());
+ updateShift();
for (int i = 0; i < areas.size(); i++) {
- RectF area = areas.get(i);
+ RectF area = pageToImgRect(areas.get(i));
if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
colors.add(null);
continue;
@@ -236,9 +339,16 @@
@Override
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
if (mWorker == null) return;
+ mDozeAmount = mStatusBarStateController.getDozeAmount();
mWorker.getThreadHandler().post(this::drawFrame);
}
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ mDozeAmount = linear;
+ mNewDozeValue = true;
+ }
+
private void drawFrame() {
preRender();
requestRender();
@@ -294,6 +404,7 @@
&& frame.width() > 0 && frame.height() > 0;
if (readyToRender) {
+ mRenderer.setExposureValue(1 - mDozeAmount);
mRenderer.onDrawFrame();
if (!mEglHelper.swapBuffer()) {
Log.e(TAG, "drawFrame failed!");
@@ -351,5 +462,14 @@
mEglHelper.dump(prefix, fd, out, args);
mRenderer.dump(prefix, fd, out, args);
}
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if (mNewDozeValue) {
+ drawFrame();
+ mNewDozeValue = false;
+ }
+ Choreographer.getInstance().postFrameCallback(this);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f210d50..f28d113 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -74,7 +74,7 @@
Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
- Key.HAS_SEEN_PRIORITY_ONBOARDING
+ Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S
})
// TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
public @interface Key {
@@ -124,7 +124,7 @@
String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet";
String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
/** Tracks whether the user has seen the onboarding screen for priority conversations */
- String HAS_SEEN_PRIORITY_ONBOARDING = "HasUserSeenPriorityOnboarding";
+ String HAS_SEEN_PRIORITY_ONBOARDING_IN_S = "HasUserSeenPriorityOnboardingInS";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
new file mode 100644
index 0000000..9bedb1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+
+import android.annotation.IntDef;
+import android.annotation.MainThread;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Inject;
+
+/**
+ * Observes changes of the accessibility button mode
+ * {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE} and notify its listeners.
+ */
+@MainThread
+@SysUISingleton
+public class AccessibilityButtonModeObserver extends
+ SecureSettingsContentObserver<AccessibilityButtonModeObserver.ModeChangedListener> {
+
+ private static final String TAG = "A11yButtonModeObserver";
+
+ private static final int ACCESSIBILITY_BUTTON_MODE_DEFAULT =
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR,
+ ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU
+ })
+ public @interface AccessibilityButtonMode {}
+
+ /** Listener for accessibility button mode changes. */
+ public interface ModeChangedListener {
+
+ /**
+ * Called when accessibility button mode changes.
+ *
+ * @param mode Current accessibility button mode
+ */
+ void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode);
+ }
+
+ @Inject
+ public AccessibilityButtonModeObserver(Context context) {
+ super(context, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
+ }
+
+ @Override
+ void onValueChanged(ModeChangedListener listener, String value) {
+ final int mode = parseAccessibilityButtonMode(value);
+ listener.onAccessibilityButtonModeChanged(mode);
+ }
+
+ /**
+ * Gets the current accessibility button mode from the current user's settings.
+ *
+ * See {@link Settings.Secure#ACCESSIBILITY_BUTTON_MODE}.
+ */
+ public int getCurrentAccessibilityButtonMode() {
+ final String value = getSettingsValue();
+
+ return parseAccessibilityButtonMode(value);
+ }
+
+ private int parseAccessibilityButtonMode(String value) {
+ int mode;
+
+ try {
+ mode = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Invalid string for " + e);
+ mode = ACCESSIBILITY_BUTTON_MODE_DEFAULT;
+ }
+
+ return mode;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
new file mode 100644
index 0000000..b32ebcc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
@@ -0,0 +1,66 @@
+/*
+ * 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.accessibility;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for tracking the current accessibility button list.
+ *
+ * @see Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS
+ */
+@MainThread
+@SysUISingleton
+public class AccessibilityButtonTargetsObserver extends
+ SecureSettingsContentObserver<AccessibilityButtonTargetsObserver.TargetsChangedListener> {
+
+ /** Listener for accessibility button targets changes. */
+ public interface TargetsChangedListener {
+
+ /**
+ * Called when accessibility button targets changes.
+ *
+ * @param targets Current content of {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}
+ */
+ void onAccessibilityButtonTargetsChanged(String targets);
+ }
+
+ @Inject
+ public AccessibilityButtonTargetsObserver(Context context) {
+ super(context, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ }
+
+ @Override
+ void onValueChanged(TargetsChangedListener listener, String value) {
+ listener.onAccessibilityButtonTargetsChanged(value);
+ }
+
+ /** Returns the current string from settings key
+ * {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}. */
+ @Nullable
+ public String getCurrentAccessibilityButtonTargets() {
+ return getSettingsValue();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index c1cf8d3..9f77b7d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import android.annotation.NonNull;
+import android.annotation.UiContext;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
@@ -69,7 +70,7 @@
private final MagnificationGestureDetector mGestureDetector;
private boolean mSingleTapDetected = false;
- MagnificationModeSwitch(Context context) {
+ MagnificationModeSwitch(@UiContext Context context) {
this(context, createView(context));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
index 50f6e9f..1a01ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+
import android.annotation.MainThread;
import android.content.Context;
import android.hardware.display.DisplayManager;
@@ -106,10 +108,9 @@
@Override
protected MagnificationModeSwitch createInstance(Display display) {
- final Context context = (display.getDisplayId() == Display.DEFAULT_DISPLAY)
- ? mContext
- : mContext.createDisplayContext(display);
- return new MagnificationModeSwitch(context);
+ final Context uiContext = mContext.createWindowContext(display,
+ TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
+ return new MagnificationModeSwitch(uiContext);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
new file mode 100644
index 0000000..4f8d866
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
@@ -0,0 +1,121 @@
+/*
+ * 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.accessibility;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides basic methods for adding, removing arbitrary listeners and inquiry given {@code
+ * secureSettingsKey} value; it must comes from {@link Settings.Secure}.
+ *
+ * This abstract class is intended to be subclassed and specialized to maintain
+ * a registry of listeners of specific types and dispatch changes to them.
+ *
+ * @param <T> The listener type
+ */
+public abstract class SecureSettingsContentObserver<T> {
+
+ private final ContentResolver mContentResolver;
+ @VisibleForTesting
+ final ContentObserver mContentObserver;
+
+ private final String mKey;
+
+ @VisibleForTesting
+ final List<T> mListeners = new ArrayList<>();
+
+ protected SecureSettingsContentObserver(Context context, String secureSettingsKey) {
+ mKey = secureSettingsKey;
+ mContentResolver = context.getContentResolver();
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateValueChanged();
+ }
+ };
+ }
+
+ /**
+ * Registers a listener to receive updates from given settings key {@code secureSettingsKey}.
+ *
+ * @param listener A listener to be added to receive the changes
+ */
+ public void addListener(@NonNull T listener) {
+ Objects.requireNonNull(listener, "listener must be non-null");
+
+ mListeners.add(listener);
+
+ if (mListeners.size() == 1) {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(mKey), /* notifyForDescendants= */
+ false, mContentObserver);
+ }
+ }
+
+ /**
+ * Unregisters a listener previously registered with {@link #addListener(T listener)}.
+ *
+ * @param listener A listener to be removed from receiving the changes
+ */
+ public void removeListener(@NonNull T listener) {
+ Objects.requireNonNull(listener, "listener must be non-null");
+
+ mListeners.remove(listener);
+
+ if (mListeners.isEmpty()) {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+ }
+
+ /**
+ * Gets the value from the current user's secure settings.
+ *
+ * See {@link Settings.Secure}.
+ */
+ public final String getSettingsValue() {
+ return Settings.Secure.getString(mContentResolver, mKey);
+ }
+
+ private void updateValueChanged() {
+ final String value = getSettingsValue();
+ final int listenerSize = mListeners.size();
+ for (int i = 0; i < listenerSize; i++) {
+ onValueChanged(mListeners.get(i), value);
+ }
+ }
+
+ /**
+ * Called when the registered value from {@code secureSettingsKey} changes.
+ *
+ * @param listener A listener could be used to receive the updates
+ * @param value Content changed value from {@code secureSettingsKey}
+ */
+ abstract void onValueChanged(T listener, String value);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 1bf7474..c051b69 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -53,12 +53,18 @@
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.util.Assert;
import java.util.Locale;
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Class to register system actions with accessibility framework.
*/
@@ -128,23 +134,39 @@
public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT =
AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13
+ public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
+ AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE; // 15
+
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- private SystemActionsBroadcastReceiver mReceiver;
+ private final SystemActionsBroadcastReceiver mReceiver;
private Locale mLocale;
- private AccessibilityManager mA11yManager;
+ private final AccessibilityManager mA11yManager;
+ private final Lazy<StatusBar> mStatusBar;
+ private final NotificationShadeWindowController mNotificationShadeController;
+ private final StatusBarWindowCallback mNotificationShadeCallback;
+ private boolean mDismissNotificationShadeActionRegistered;
@Inject
- public SystemActions(Context context) {
+ public SystemActions(Context context,
+ NotificationShadeWindowController notificationShadeController,
+ Lazy<StatusBar> statusBar) {
super(context);
mReceiver = new SystemActionsBroadcastReceiver();
mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
mA11yManager = (AccessibilityManager) mContext.getSystemService(
Context.ACCESSIBILITY_SERVICE);
+ mNotificationShadeController = notificationShadeController;
+ // Saving in instance variable since to prevent GC since
+ // NotificationShadeWindowController.registerCallback() only keeps weak references.
+ mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing) ->
+ registerOrUnregisterDismissNotificationShadeAction();
+ mStatusBar = statusBar;
}
@Override
public void start() {
+ mNotificationShadeController.registerCallback(mNotificationShadeCallback);
mContext.registerReceiverForAllUsers(
mReceiver,
mReceiver.createIntentFilter(),
@@ -210,6 +232,32 @@
mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
mA11yManager.registerSystemAction(
actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
+ registerOrUnregisterDismissNotificationShadeAction();
+ }
+
+ private void registerOrUnregisterDismissNotificationShadeAction() {
+ Assert.isMainThread();
+
+ // Saving state in instance variable since this callback is called quite often to avoid
+ // binder calls
+ StatusBar statusBar = mStatusBar.get();
+ if (statusBar.isPanelExpanded() && !statusBar.isKeyguardShowing()) {
+ if (!mDismissNotificationShadeActionRegistered) {
+ mA11yManager.registerSystemAction(
+ createRemoteAction(
+ R.string.accessibility_system_action_dismiss_notification_shade,
+ SystemActionsBroadcastReceiver
+ .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE),
+ SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+ mDismissNotificationShadeActionRegistered = true;
+ }
+ } else {
+ if (mDismissNotificationShadeActionRegistered) {
+ mA11yManager.unregisterSystemAction(
+ SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+ mDismissNotificationShadeActionRegistered = false;
+ }
+ }
}
/**
@@ -261,10 +309,15 @@
R.string.accessibility_system_action_on_screen_a11y_shortcut_chooser_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER;
break;
- case SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
+ case SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
labelId = R.string.accessibility_system_action_hardware_a11y_shortcut_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT;
break;
+ case SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
+ labelId = R.string.accessibility_system_action_dismiss_notification_shade;
+ intent = SystemActionsBroadcastReceiver
+ .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE;
+ break;
default:
return;
}
@@ -369,6 +422,10 @@
mA11yManager.performAccessibilityShortcut();
}
+ private void handleAccessibilityDismissNotificationShade() {
+ mStatusBar.get().animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, false /* force */);
+ }
+
private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -384,6 +441,8 @@
"SYSTEM_ACTION_ACCESSIBILITY_BUTTON_MENU";
private static final String INTENT_ACTION_ACCESSIBILITY_SHORTCUT =
"SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
+ private static final String INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
+ "SYSTEM_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE";
private PendingIntent createPendingIntent(Context context, String intentAction) {
switch (intentAction) {
@@ -397,7 +456,8 @@
case INTENT_ACTION_TAKE_SCREENSHOT:
case INTENT_ACTION_ACCESSIBILITY_BUTTON:
case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
- case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
+ case INTENT_ACTION_ACCESSIBILITY_SHORTCUT:
+ case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
Intent intent = new Intent(intentAction);
intent.setPackage(context.getPackageName());
return PendingIntent.getBroadcast(context, 0, intent,
@@ -422,6 +482,7 @@
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
+ intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
return intentFilter;
}
@@ -473,6 +534,10 @@
handleAccessibilityShortcut();
break;
}
+ case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
+ handleAccessibilityDismissNotificationShade();
+ break;
+ }
default:
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index f52dcd5..cdd6942 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.content.Context;
@@ -82,9 +84,8 @@
@Override
protected WindowMagnificationAnimationController createInstance(Display display) {
- final Context context = (display.getDisplayId() == Display.DEFAULT_DISPLAY)
- ? mContext
- : mContext.createDisplayContext(display);
+ final Context windowContext = mContext.createWindowContext(display,
+ TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
final WindowMagnificationController controller = new WindowMagnificationController(
mContext,
mHandler, new SfVsyncFrameCallbackProvider(), null,
@@ -92,7 +93,7 @@
final int navBarMode = mNavigationModeController.addListener(
controller::onNavigationModeChanged);
controller.onNavigationModeChanged(navBarMode);
- return new WindowMagnificationAnimationController(context, controller);
+ return new WindowMagnificationAnimationController(windowContext, controller);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 24d8388..5758b15 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -20,6 +20,7 @@
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.annotation.UiContext;
import android.content.Context;
import android.content.res.Resources;
import android.os.RemoteException;
@@ -69,8 +70,8 @@
@MagnificationState
private int mState = STATE_DISABLED;
- WindowMagnificationAnimationController(
- Context context, WindowMagnificationController controller) {
+ WindowMagnificationAnimationController(@UiContext Context context,
+ WindowMagnificationController controller) {
this(context, controller, newValueAnimator(context.getResources()));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 3f7d2d8..2b666f1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -24,6 +24,7 @@
import android.animation.PropertyValuesHolder;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UiContext;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
@@ -134,7 +135,7 @@
@Nullable
private MirrorWindowControl mMirrorWindowControl;
- WindowMagnificationController(Context context, @NonNull Handler handler,
+ WindowMagnificationController(@UiContext Context context, @NonNull Handler handler,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
@NonNull WindowMagnifierCallback callback) {
@@ -147,7 +148,7 @@
mDisplayId = mContext.getDisplayId();
mRotation = display.getRotation();
- mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mWm = context.getSystemService(WindowManager.class);
mResources = mContext.getResources();
mScale = mResources.getInteger(R.integer.magnification_default_scale);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
new file mode 100644
index 0000000..7b4ce61
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.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 com.android.systemui.accessibility.floatingmenu;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+
+import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
+import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Contains logic for an accessibility floating menu view.
+ */
+public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
+ private static final int DEFAULT_FADE_EFFECT_ENABLED = 1;
+ private static final float DEFAULT_OPACITY_VALUE = 0.55f;
+ private final Context mContext;
+ private final AccessibilityFloatingMenuView mMenuView;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private final ContentObserver mContentObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
+ }
+ };
+
+ private final ContentObserver mSizeContentObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.setSizeType(getSizeType(mContext));
+ }
+ };
+
+ private final ContentObserver mFadeOutContentObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
+ getOpacityValue(mContext));
+ }
+ };
+
+ public AccessibilityFloatingMenu(Context context) {
+ mContext = context;
+ mMenuView = new AccessibilityFloatingMenuView(context);
+ }
+
+ @VisibleForTesting
+ AccessibilityFloatingMenu(Context context, AccessibilityFloatingMenuView menuView) {
+ mContext = context;
+ mMenuView = menuView;
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mMenuView.isShowing();
+ }
+
+ @Override
+ public void show() {
+ if (isShowing()) {
+ return;
+ }
+
+ mMenuView.show();
+ mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
+ mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
+ getOpacityValue(mContext));
+ mMenuView.setSizeType(getSizeType(mContext));
+ mMenuView.setShapeType(getShapeType(mContext));
+
+ registerContentObservers();
+ }
+
+ @Override
+ public void hide() {
+ if (!isShowing()) {
+ return;
+ }
+
+ mMenuView.hide();
+
+ unregisterContentObservers();
+ }
+
+ private static boolean isFadeEffectEnabled(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ DEFAULT_FADE_EFFECT_ENABLED) == /* enable */ 1;
+ }
+
+ private static float getOpacityValue(Context context) {
+ return Settings.Secure.getFloat(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_OPACITY,
+ DEFAULT_OPACITY_VALUE);
+ }
+
+ private static int getSizeType(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
+ }
+
+ private static int getShapeType(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
+ ShapeType.OVAL);
+ }
+
+ private void registerContentObservers() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+ /* notifyForDescendants */ false, mContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
+ /* notifyForDescendants */ false, mSizeContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
+ /* notifyForDescendants */ false, mFadeOutContentObserver,
+ UserHandle.USER_CURRENT);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
+ /* notifyForDescendants */ false, mFadeOutContentObserver,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void unregisterContentObservers() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ mContext.getContentResolver().unregisterContentObserver(mSizeContentObserver);
+ mContext.getContentResolver().unregisterContentObserver(mFadeOutContentObserver);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
new file mode 100644
index 0000000..112e9ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -0,0 +1,145 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.MainThread;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
+import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/** A controller to handle the lifecycle of accessibility floating menu. */
+@MainThread
+@SysUISingleton
+public class AccessibilityFloatingMenuController implements
+ AccessibilityButtonModeObserver.ModeChangedListener,
+ AccessibilityButtonTargetsObserver.TargetsChangedListener,
+ AccessibilityManager.AccessibilityStateChangeListener {
+
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+ private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+ private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
+
+ @VisibleForTesting
+ IAccessibilityFloatingMenu mFloatingMenu;
+ private int mBtnMode;
+ private String mBtnTargets;
+
+ @Inject
+ public AccessibilityFloatingMenuController(Context context,
+ AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
+ mContext = context;
+ mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
+ mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+
+ mAccessibilityButtonModeObserver.addListener(this);
+ mAccessibilityButtonTargetsObserver.addListener(this);
+ mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+ mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
+
+ // Accessibility floating menu widget needs accessibility service to work, but system
+ // accessibility might be unavailable during the phone get booted, hence it needs to wait
+ // for accessibility manager callback to work.
+ mAccessibilityManager.addAccessibilityStateChangeListener(this);
+ if (mAccessibilityManager.isEnabled()) {
+ handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
+ mAccessibilityManager.removeAccessibilityStateChangeListener(this);
+ }
+ }
+
+ /**
+ * Handles visibility of the accessibility floating menu when accessibility button mode changes.
+ *
+ * @param mode Current accessibility button mode.
+ */
+ @Override
+ public void onAccessibilityButtonModeChanged(@AccessibilityButtonMode int mode) {
+ mBtnMode = mode;
+ handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
+ }
+
+ /**
+ * Handles visibility of the accessibility floating menu when accessibility button targets
+ * changes.
+ * List should come from {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
+ * @param targets Current accessibility button list.
+ */
+ @Override
+ public void onAccessibilityButtonTargetsChanged(String targets) {
+ mBtnTargets = targets;
+ handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
+ }
+
+ /**
+ * Handles visibility of the accessibility floating menu when system accessibility state
+ * changes.
+ * If system accessibility become available onAccessibilityStateChanged(true), then we don't
+ * need to listen to this listener anymore.
+ *
+ * @param enabled Whether accessibility is enabled.
+ */
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ if (enabled) {
+ handleFloatingMenuVisibility(mBtnMode, mBtnTargets);
+ }
+
+ mAccessibilityManager.removeAccessibilityStateChangeListener(this);
+ }
+
+ private void handleFloatingMenuVisibility(@AccessibilityButtonMode int mode, String targets) {
+ if (shouldShowFloatingMenu(mode, targets)) {
+ showFloatingMenu();
+ } else {
+ destroyFloatingMenu();
+ }
+ }
+
+ private boolean shouldShowFloatingMenu(@AccessibilityButtonMode int mode, String targets) {
+ return mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU && !TextUtils.isEmpty(targets);
+ }
+
+ private void showFloatingMenu() {
+ if (mFloatingMenu == null) {
+ mFloatingMenu = new AccessibilityFloatingMenu(mContext);
+ }
+
+ mFloatingMenu.show();
+ }
+
+ private void destroyFloatingMenu() {
+ if (mFloatingMenu == null) {
+ return;
+ }
+
+ mFloatingMenu.hide();
+ mFloatingMenu = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
new file mode 100644
index 0000000..ab05c2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -0,0 +1,690 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static android.util.MathUtils.constrain;
+import static android.util.MathUtils.sq;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
+
+import androidx.annotation.DimenRes;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Accessibility floating menu is used for the actions of accessibility features, it's also the
+ * action set.
+ *
+ * <p>The number of items would depend on strings key
+ * {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
+ */
+public class AccessibilityFloatingMenuView extends FrameLayout
+ implements RecyclerView.OnItemTouchListener {
+ private static final int INDEX_MENU_ITEM = 0;
+ private static final int FADE_OUT_DURATION_MS = 1000;
+ private static final int FADE_EFFECT_DURATION_MS = 3000;
+ private static final int SNAP_TO_LOCATION_DURATION_MS = 150;
+ private static final int MIN_WINDOW_X = 0;
+ private static final int MIN_WINDOW_Y = 0;
+ private static final float LOCATION_Y_PERCENTAGE = 0.8f;
+
+ private boolean mIsFadeEffectEnabled;
+ private boolean mIsShowing;
+ private boolean mIsDownInEnlargedTouchArea;
+ private boolean mIsDragging = false;
+ @Alignment
+ private int mAlignment = Alignment.RIGHT;
+ @SizeType
+ private int mSizeType = SizeType.SMALL;
+ @VisibleForTesting
+ @ShapeType
+ int mShapeType = ShapeType.OVAL;
+ private int mTemporaryShapeType;
+ @RadiusType
+ private int mRadiusType = RadiusType.LEFT_HALF_OVAL;
+ private int mMargin;
+ private int mPadding;
+ private int mScreenHeight;
+ private int mScreenWidth;
+ private int mIconWidth;
+ private int mIconHeight;
+ private int mInset;
+ private int mDownX;
+ private int mDownY;
+ private int mRelativeToPointerDownX;
+ private int mRelativeToPointerDownY;
+ private float mRadius;
+ private float mPercentageY = LOCATION_Y_PERCENTAGE;
+ private float mSquareScaledTouchSlop;
+ private final RecyclerView mListView;
+ private final AccessibilityTargetAdapter mAdapter;
+ private float mFadeOutValue;
+ private final ValueAnimator mFadeOutAnimator;
+ @VisibleForTesting
+ final ValueAnimator mDragAnimator;
+ private final Handler mUiHandler;
+ @VisibleForTesting
+ final WindowManager.LayoutParams mCurrentLayoutParams;
+ private final WindowManager mWindowManager;
+ private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+
+ @IntDef({
+ SizeType.SMALL,
+ SizeType.LARGE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface SizeType {
+ int SMALL = 0;
+ int LARGE = 1;
+ }
+
+ @IntDef({
+ ShapeType.OVAL,
+ ShapeType.HALF_OVAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ShapeType {
+ int OVAL = 0;
+ int HALF_OVAL = 1;
+ }
+
+ @IntDef({
+ RadiusType.LEFT_HALF_OVAL,
+ RadiusType.OVAL,
+ RadiusType.RIGHT_HALF_OVAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RadiusType {
+ int LEFT_HALF_OVAL = 0;
+ int OVAL = 1;
+ int RIGHT_HALF_OVAL = 2;
+ }
+
+ @IntDef({
+ Alignment.LEFT,
+ Alignment.RIGHT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Alignment {
+ int LEFT = 0;
+ int RIGHT = 1;
+ }
+
+ public AccessibilityFloatingMenuView(Context context) {
+ this(context, new RecyclerView(context));
+ }
+
+ @VisibleForTesting
+ AccessibilityFloatingMenuView(Context context,
+ RecyclerView listView) {
+ super(context);
+
+ mListView = listView;
+ mWindowManager = context.getSystemService(WindowManager.class);
+ mCurrentLayoutParams = createDefaultLayoutParams();
+ mAdapter = new AccessibilityTargetAdapter(mTargets);
+ mUiHandler = createUiHandler();
+
+ mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue);
+ mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
+ mFadeOutAnimator.addUpdateListener(
+ (animation) -> setAlpha((float) animation.getAnimatedValue()));
+
+ mDragAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mDragAnimator.setDuration(SNAP_TO_LOCATION_DURATION_MS);
+ mDragAnimator.setInterpolator(new OvershootInterpolator());
+ mDragAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAlignment = calculateCurrentAlignment();
+ mPercentageY = calculateCurrentPercentageY();
+
+ updateLocationWith(mAlignment, mPercentageY);
+ updateMarginsWith(mAlignment);
+
+ updateInsetWith(getResources().getConfiguration().uiMode, mAlignment);
+
+ mRadiusType = (mAlignment == Alignment.RIGHT)
+ ? RadiusType.LEFT_HALF_OVAL
+ : RadiusType.RIGHT_HALF_OVAL;
+ updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
+
+ fadeOut();
+ }
+ });
+
+ updateDimensions();
+ initListView();
+ updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
+ @NonNull MotionEvent event) {
+ final int currentRawX = (int) event.getRawX();
+ final int currentRawY = (int) event.getRawY();
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ fadeIn();
+
+ mDownX = currentRawX;
+ mDownY = currentRawY;
+ mRelativeToPointerDownX = mCurrentLayoutParams.x - mDownX;
+ mRelativeToPointerDownY = mCurrentLayoutParams.y - mDownY;
+ mListView.animate().translationX(0);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mIsDragging
+ || hasExceededTouchSlop(mDownX, mDownY, currentRawX, currentRawY)) {
+ if (!mIsDragging) {
+ mIsDragging = true;
+ setRadius(mRadius, RadiusType.OVAL);
+ setInset(0, 0);
+ }
+
+ mTemporaryShapeType =
+ isMovingTowardsScreenEdge(mAlignment, currentRawX, mDownX)
+ ? ShapeType.HALF_OVAL
+ : ShapeType.OVAL;
+ final int newWindowX = currentRawX + mRelativeToPointerDownX;
+ final int newWindowY = currentRawY + mRelativeToPointerDownY;
+ mCurrentLayoutParams.x = constrain(newWindowX, MIN_WINDOW_X, getMaxWindowX());
+ mCurrentLayoutParams.y = constrain(newWindowY, MIN_WINDOW_Y, getMaxWindowY());
+ mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsDragging) {
+ mIsDragging = false;
+
+ final int maxX = getMaxWindowX();
+ final int endX = mCurrentLayoutParams.x > ((MIN_WINDOW_X + maxX) / 2)
+ ? maxX : MIN_WINDOW_X;
+ final int endY = mCurrentLayoutParams.y;
+ snapToLocation(endX, endY);
+
+ setShapeType(mTemporaryShapeType);
+
+ // Avoid triggering the listener of the item.
+ return true;
+ }
+
+ // Must switch the oval shape type before tapping the corresponding item in the
+ // list view, otherwise it can't work on it.
+ if (mShapeType == ShapeType.HALF_OVAL) {
+ setShapeType(ShapeType.OVAL);
+
+ return true;
+ }
+
+ fadeOut();
+ break;
+ default: // Do nothing
+ }
+
+ // not consume all the events here because keeping the scroll behavior of list view.
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
+ // Do Nothing
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean b) {
+ // Do Nothing
+ }
+
+ void show() {
+ if (isShowing()) {
+ return;
+ }
+
+ mIsShowing = true;
+ mWindowManager.addView(this, mCurrentLayoutParams);
+ setSystemGestureExclusion();
+ }
+
+ void hide() {
+ if (!isShowing()) {
+ return;
+ }
+
+ mIsShowing = false;
+ mWindowManager.removeView(this);
+ setSystemGestureExclusion();
+ }
+
+ boolean isShowing() {
+ return mIsShowing;
+ }
+
+ void onTargetsChanged(List<AccessibilityTarget> newTargets) {
+ fadeIn();
+
+ mTargets.clear();
+ mTargets.addAll(newTargets);
+ mAdapter.notifyDataSetChanged();
+
+ updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
+ setSystemGestureExclusion();
+
+ fadeOut();
+ }
+
+ void setSizeType(@SizeType int newSizeType) {
+ fadeIn();
+
+ mSizeType = newSizeType;
+
+ updateIconSizeWith(newSizeType);
+ updateRadiusWith(newSizeType, mRadiusType, mTargets.size());
+
+ // When the icon sized changed, the menu size and location will be impacted.
+ updateLocationWith(mAlignment, mPercentageY);
+ setSystemGestureExclusion();
+
+ fadeOut();
+ }
+
+ void setShapeType(@ShapeType int newShapeType) {
+ fadeIn();
+
+ mShapeType = newShapeType;
+
+ updateOffsetWith(newShapeType, mAlignment);
+
+ setOnTouchListener(
+ newShapeType == ShapeType.OVAL
+ ? null
+ : (view, event) -> onTouched(event));
+
+ fadeOut();
+ }
+
+ void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
+ mIsFadeEffectEnabled = isFadeEffectEnabled;
+ mFadeOutValue = newOpacityValue;
+
+ mFadeOutAnimator.cancel();
+ mFadeOutAnimator.setFloatValues(1.0f, mFadeOutValue);
+ setAlpha(mIsFadeEffectEnabled ? mFadeOutValue : /* completely opaque */ 1.0f);
+ }
+
+ @VisibleForTesting
+ void fadeIn() {
+ if (!mIsFadeEffectEnabled) {
+ return;
+ }
+
+ mFadeOutAnimator.cancel();
+ mUiHandler.removeCallbacksAndMessages(null);
+ mUiHandler.post(() -> setAlpha(/* completely opaque */ 1.0f));
+ }
+
+ @VisibleForTesting
+ void fadeOut() {
+ if (!mIsFadeEffectEnabled) {
+ return;
+ }
+
+ mUiHandler.postDelayed(() -> mFadeOutAnimator.start(), FADE_EFFECT_DURATION_MS);
+ }
+
+ private boolean onTouched(MotionEvent event) {
+ final int action = event.getAction();
+ final int currentX = (int) event.getX();
+ final int currentY = (int) event.getY();
+
+ final int menuHalfWidth = getLayoutWidth() / 2;
+ final Rect touchDelegateBounds =
+ new Rect(mMargin, mMargin, mMargin + menuHalfWidth, mMargin + getLayoutHeight());
+ if (action == MotionEvent.ACTION_DOWN
+ && touchDelegateBounds.contains(currentX, currentY)) {
+ mIsDownInEnlargedTouchArea = true;
+ }
+
+ if (!mIsDownInEnlargedTouchArea) {
+ return false;
+ }
+
+ if (action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL) {
+ mIsDownInEnlargedTouchArea = false;
+ }
+
+ // In order to correspond to the correct item of list view.
+ event.setLocation(currentX - mMargin, currentY - mMargin);
+ return mListView.dispatchTouchEvent(event);
+ }
+
+ private boolean isMovingTowardsScreenEdge(@Alignment int side, int currentRawX, int downX) {
+ return (side == Alignment.RIGHT && currentRawX > downX)
+ || (side == Alignment.LEFT && downX > currentRawX);
+ }
+
+ private boolean hasExceededTouchSlop(int startX, int startY, int endX, int endY) {
+ return (sq(endX - startX) + sq(endY - startY)) > mSquareScaledTouchSlop;
+ }
+
+ private void setRadius(float radius, @RadiusType int type) {
+ getMenuGradientDrawable().setCornerRadii(createRadii(radius, type));
+ }
+
+ private float[] createRadii(float radius, @RadiusType int type) {
+ if (type == RadiusType.LEFT_HALF_OVAL) {
+ return new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+ }
+
+ if (type == RadiusType.RIGHT_HALF_OVAL) {
+ return new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f};
+ }
+
+ return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
+ }
+
+ private Handler createUiHandler() {
+ final Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalArgumentException("looper must not be null");
+ }
+ return new Handler(looper);
+ }
+
+ private void updateDimensions() {
+ final Resources res = getResources();
+ final DisplayMetrics dm = res.getDisplayMetrics();
+ mScreenWidth = dm.widthPixels;
+ mScreenHeight = dm.heightPixels;
+ mMargin =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
+ mPadding =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding);
+ mInset =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
+
+ mSquareScaledTouchSlop =
+ sq(ViewConfiguration.get(getContext()).getScaledTouchSlop());
+ }
+
+ private void updateIconSizeWith(@SizeType int sizeType) {
+ final Resources res = getResources();
+ final int iconResId =
+ sizeType == SizeType.SMALL
+ ? R.dimen.accessibility_floating_menu_small_width_height
+ : R.dimen.accessibility_floating_menu_large_width_height;
+ mIconWidth = res.getDimensionPixelSize(iconResId);
+ mIconHeight = mIconWidth;
+
+ mAdapter.setIconWidthHeight(mIconWidth);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private void initListView() {
+ final Drawable background =
+ getContext().getDrawable(R.drawable.accessibility_floating_menu_background);
+ final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
+ final LayoutParams layoutParams =
+ new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ mListView.setLayoutParams(layoutParams);
+ final InstantInsetLayerDrawable layerDrawable =
+ new InstantInsetLayerDrawable(new Drawable[]{background});
+ mListView.setBackground(layerDrawable);
+ mListView.setAdapter(mAdapter);
+ mListView.setLayoutManager(layoutManager);
+ mListView.addOnItemTouchListener(this);
+ mListView.animate().setInterpolator(new OvershootInterpolator());
+ updateListView();
+
+ addView(mListView);
+ }
+
+ private void updateListView() {
+ final int elevation =
+ getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
+ mListView.setElevation(elevation);
+
+ updateMarginsWith(mAlignment);
+ }
+
+ private WindowManager.LayoutParams createDefaultLayoutParams() {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ params.windowAnimations = android.R.style.Animation_Translucent;
+ params.gravity = Gravity.START | Gravity.TOP;
+ params.x = getMaxWindowX();
+ params.y = (int) (getMaxWindowY() * mPercentageY);
+
+ return params;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateDimensions();
+ updateListView();
+ updateIconSizeWith(mSizeType);
+ updateColor();
+ updateStrokeWith(newConfig.uiMode, mAlignment);
+ updateLocationWith(mAlignment, mPercentageY);
+ }
+
+ private void snapToLocation(int endX, int endY) {
+ mDragAnimator.cancel();
+ mDragAnimator.removeAllUpdateListeners();
+ mDragAnimator.addUpdateListener(anim -> onDragAnimationUpdate(anim, endX, endY));
+ mDragAnimator.start();
+ }
+
+ private void onDragAnimationUpdate(ValueAnimator animator, int endX, int endY) {
+ float value = (float) animator.getAnimatedValue();
+ final int newX = (int) (((1 - value) * mCurrentLayoutParams.x) + (value * endX));
+ final int newY = (int) (((1 - value) * mCurrentLayoutParams.y) + (value * endY));
+
+ mCurrentLayoutParams.x = newX;
+ mCurrentLayoutParams.y = newY;
+ mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
+ }
+
+ private int getMaxWindowX() {
+ return mScreenWidth - mMargin - getLayoutWidth();
+ }
+
+ private int getMaxWindowY() {
+ return mScreenHeight - getWindowHeight();
+ }
+
+ private InstantInsetLayerDrawable getMenuLayerDrawable() {
+ return (InstantInsetLayerDrawable) mListView.getBackground();
+ }
+
+ private GradientDrawable getMenuGradientDrawable() {
+ return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
+ }
+
+ /**
+ * Updates the floating menu to be fixed at the side of the screen.
+ */
+ private void updateLocationWith(@Alignment int side, float percentageCurrentY) {
+ mCurrentLayoutParams.x = (side == Alignment.RIGHT) ? getMaxWindowX() : MIN_WINDOW_X;
+ mCurrentLayoutParams.y = (int) (percentageCurrentY * getMaxWindowY());
+ mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
+ }
+
+ private void updateOffsetWith(@ShapeType int shapeType, @Alignment int side) {
+ final float halfWidth = getLayoutWidth() / 2.0f;
+ final float offset = (shapeType == ShapeType.OVAL) ? 0 : halfWidth;
+ mListView.animate().translationX(side == Alignment.RIGHT ? offset : -offset);
+ }
+
+ private void updateMarginsWith(@Alignment int side) {
+ final LayoutParams layoutParams = (LayoutParams) mListView.getLayoutParams();
+ final int marginLeft = (side == Alignment.LEFT) ? 0 : mMargin;
+ final int marginRight = (side == Alignment.RIGHT) ? 0 : mMargin;
+
+ if (marginLeft == layoutParams.leftMargin
+ && marginRight == layoutParams.rightMargin) {
+ return;
+ }
+
+ layoutParams.setMargins(marginLeft, mMargin, marginRight, mMargin);
+ mListView.setLayoutParams(layoutParams);
+ }
+
+ private void updateColor() {
+ final int menuColorResId = R.color.accessibility_floating_menu_background;
+ getMenuGradientDrawable().setColor(getResources().getColor(menuColorResId));
+ }
+
+ private void updateStrokeWith(int uiMode, @Alignment int side) {
+ updateInsetWith(uiMode, side);
+
+ final boolean isNightMode =
+ (uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ final Resources res = getResources();
+ final int width =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width);
+ final int strokeWidth = isNightMode ? width : 0;
+ final int strokeColor = res.getColor(R.color.accessibility_floating_menu_stroke_dark);
+ getMenuGradientDrawable().setStroke(strokeWidth, strokeColor);
+ }
+
+ private void updateRadiusWith(@SizeType int sizeType, @RadiusType int radiusType,
+ int itemCount) {
+ mRadius =
+ getResources().getDimensionPixelSize(getRadiusResId(sizeType, itemCount));
+ setRadius(mRadius, radiusType);
+ }
+
+ private void updateInsetWith(int uiMode, @Alignment int side) {
+ final boolean isNightMode =
+ (uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+
+ final int layerInset = isNightMode ? mInset : 0;
+ final int insetLeft = (side == Alignment.LEFT) ? layerInset : 0;
+ final int insetRight = (side == Alignment.RIGHT) ? layerInset : 0;
+ setInset(insetLeft, insetRight);
+ }
+
+ private void setInset(int left, int right) {
+ final LayerDrawable layerDrawable = getMenuLayerDrawable();
+ if (layerDrawable.getLayerInsetLeft(INDEX_MENU_ITEM) == left
+ && layerDrawable.getLayerInsetRight(INDEX_MENU_ITEM) == right) {
+ return;
+ }
+
+ layerDrawable.setLayerInset(INDEX_MENU_ITEM, left, 0, right, 0);
+ }
+
+ @Alignment
+ private int calculateCurrentAlignment() {
+ return mCurrentLayoutParams.x >= ((MIN_WINDOW_X + getMaxWindowX()) / 2)
+ ? Alignment.RIGHT
+ : Alignment.LEFT;
+ }
+
+ private float calculateCurrentPercentageY() {
+ return mCurrentLayoutParams.y / (float) getMaxWindowY();
+ }
+
+ private @DimenRes int getRadiusResId(@SizeType int sizeType, int itemCount) {
+ return sizeType == SizeType.SMALL
+ ? getSmallSizeResIdWith(itemCount)
+ : getLargeSizeResIdWith(itemCount);
+ }
+
+ private int getSmallSizeResIdWith(int itemCount) {
+ return itemCount > 1
+ ? R.dimen.accessibility_floating_menu_small_multiple_radius
+ : R.dimen.accessibility_floating_menu_small_single_radius;
+ }
+
+ private int getLargeSizeResIdWith(int itemCount) {
+ return itemCount > 1
+ ? R.dimen.accessibility_floating_menu_large_multiple_radius
+ : R.dimen.accessibility_floating_menu_large_single_radius;
+ }
+
+ private int getLayoutWidth() {
+ return mPadding * 2 + mIconWidth;
+ }
+
+ private int getLayoutHeight() {
+ return Math.min(mScreenHeight - mMargin * 2,
+ (mPadding + mIconHeight) * mTargets.size() + mPadding);
+ }
+
+ private int getWindowWidth() {
+ return mMargin + getLayoutWidth();
+ }
+
+ private int getWindowHeight() {
+ return Math.min(mScreenHeight, mMargin * 2 + getLayoutHeight());
+ }
+
+ private void setSystemGestureExclusion() {
+ final Rect excludeZone =
+ new Rect(0, 0, getWindowWidth(), getWindowHeight());
+ post(() -> setSystemGestureExclusionRects(
+ mIsShowing
+ ? Collections.singletonList(excludeZone)
+ : Collections.emptyList()));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
new file mode 100644
index 0000000..bb4038e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -0,0 +1,150 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+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;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.R;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * An adapter which shows the set of accessibility targets that can be performed.
+ */
+public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
+ private int mIconWidthHeight;
+ private final List<AccessibilityTarget> mTargets;
+
+ @IntDef({
+ FIRST_ITEM,
+ REGULAR_ITEM,
+ LAST_ITEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ItemType {
+ int FIRST_ITEM = 0;
+ int REGULAR_ITEM = 1;
+ int LAST_ITEM = 2;
+ }
+
+ public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
+ mTargets = targets;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, @ItemType int itemType) {
+ final View root = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.accessibility_floating_menu_item, parent,
+ /* attachToRoot= */ false);
+
+ if (itemType == FIRST_ITEM) {
+ return new TopViewHolder(root);
+ }
+
+ if (itemType == LAST_ITEM) {
+ return new BottomViewHolder(root);
+ }
+
+ return new ViewHolder(root);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.mIconView.setBackground(mTargets.get(position).getIcon());
+ holder.updateIconWidthHeight(mIconWidthHeight);
+ holder.itemView.setOnClickListener((v) -> mTargets.get(position).onSelected());
+ }
+
+ @ItemType
+ @Override
+ public int getItemViewType(int position) {
+ if (position == 0) {
+ return FIRST_ITEM;
+ }
+
+ if (position == (getItemCount() - 1)) {
+ return LAST_ITEM;
+ }
+
+ return REGULAR_ITEM;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mTargets.size();
+ }
+
+ public void setIconWidthHeight(int iconWidthHeight) {
+ mIconWidthHeight = iconWidthHeight;
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ final View mIconView;
+ final View mDivider;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ mIconView = itemView.findViewById(R.id.icon_view);
+ mDivider = itemView.findViewById(R.id.transparent_divider);
+ }
+
+ void updateIconWidthHeight(int newValue) {
+ final ViewGroup.LayoutParams layoutParams = mIconView.getLayoutParams();
+ if (layoutParams.width == newValue) {
+ return;
+ }
+ layoutParams.width = newValue;
+ layoutParams.height = newValue;
+ mIconView.setLayoutParams(layoutParams);
+ }
+ }
+
+ static class TopViewHolder extends ViewHolder {
+ TopViewHolder(View itemView) {
+ super(itemView);
+ final int padding = itemView.getPaddingStart();
+ itemView.setPaddingRelative(padding, padding, padding, 0);
+ }
+ }
+
+ static class BottomViewHolder extends ViewHolder {
+ BottomViewHolder(View itemView) {
+ super(itemView);
+ mDivider.setVisibility(GONE);
+ final int padding = itemView.getPaddingStart();
+ itemView.setPaddingRelative(padding, 0, padding, padding);
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java
similarity index 60%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
copy to packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java
index 54242be..62f02a0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java
@@ -14,12 +14,25 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.systemui.accessibility.floatingmenu;
/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ * Interface for managing the accessibility targets menu component.
*/
-oneway interface ISplitScreenListener {
- void onStagePositionChanged(int stage, int position);
- void onTaskStageChanged(int taskId, int stage, boolean visible);
+public interface IAccessibilityFloatingMenu {
+
+ /**
+ * Checks if the menu was shown.
+ */
+ boolean isShowing();
+
+ /**
+ * Shows the accessibility targets menu.
+ */
+ void show();
+
+ /**
+ * Hides the accessibility targets menu.
+ */
+ void hide();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/InstantInsetLayerDrawable.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/InstantInsetLayerDrawable.java
new file mode 100644
index 0000000..6c021a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/InstantInsetLayerDrawable.java
@@ -0,0 +1,37 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+
+/**
+ * A drawable that forces to update the bounds {@link #onBoundsChange(Rect)} immediately after
+ * {@link #setLayerInset} dynamically.
+ */
+public class InstantInsetLayerDrawable extends LayerDrawable {
+ public InstantInsetLayerDrawable(Drawable[] layers) {
+ super(layers);
+ }
+
+ @Override
+ public void setLayerInset(int index, int l, int t, int r, int b) {
+ super.setLayerInset(index, l, t, r, b);
+ onBoundsChange(getBounds());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 2040347..e53f5c9 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -20,7 +20,6 @@
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
-import android.Manifest;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -43,6 +42,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -370,13 +370,9 @@
}
// TODO ntmyren: remove after teamfood is finished
- private boolean shouldShowAppPredictor(String pkgName) {
- if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled",
- false)) {
- return false;
- }
- return mPackageManager.checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, pkgName)
- == PackageManager.PERMISSION_GRANTED;
+ private boolean showSystemApps() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false);
}
/**
@@ -399,8 +395,8 @@
return true;
}
// TODO ntmyren: Replace this with more robust check if this moves beyond teamfood
- if ((appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName))
- || shouldShowAppPredictor(packageName)
+ if (((showSystemApps() && !packageName.equals("android"))
+ || appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName))
|| isSpeechRecognizerUsage(appOpCode, packageName)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
index 2083f2e..d90d0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics;
-import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.view.Surface;
+import com.android.systemui.biometrics.HbmTypes.HbmType;
+
/**
* Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
* enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
@@ -26,16 +28,20 @@
*/
public interface HbmCallback {
/**
- * UdfpsView will call this to enable the HBM before drawing the illumination dot.
+ * UdfpsView will call this to enable the HBM when the fingerprint illumination is needed.
*
- * @param surface A valid surface for which the HBM should be enabled.
+ * @param hbmType The type of HBM that should be enabled. See {@link HbmTypes}.
+ * @param surface The surface for which the HBM is requested, in case the HBM implementation
+ * needs to set special surface flags to enable the HBM. Can be null.
*/
- void enableHbm(@NonNull Surface surface);
+ void enableHbm(@HbmType int hbmType, @Nullable Surface surface);
/**
* UdfpsView will call this to disable the HBM when the illumination is not longer needed.
*
- * @param surface A valid surface for which the HBM should be disabled.
+ * @param hbmType The type of HBM that should be disabled. See {@link HbmTypes}.
+ * @param surface The surface for which the HBM is requested, in case the HBM implementation
+ * needs to unset special surface flags to disable the HBM. Can be null.
*/
- void disableHbm(@NonNull Surface surface);
+ void disableHbm(@HbmType int hbmType, @Nullable Surface surface);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java b/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java
new file mode 100644
index 0000000..96ee0f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/HbmTypes.java
@@ -0,0 +1,38 @@
+/*
+ * 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.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Different high-brightness mode (HBM) types that are relevant to this package.
+ */
+public final class HbmTypes {
+ /** HBM that applies to the whole screen. */
+ public static final int GLOBAL_HBM = 0;
+
+ /** HBM that only applies to a portion of the screen. */
+ public static final int LOCAL_HBM = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({GLOBAL_HBM, LOCAL_HBM})
+ public @interface HbmType {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index 2036150..60fdbab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -71,8 +71,16 @@
return false;
}
- private void updateAlpha() {
- getDrawable().setAlpha(mPauseAuth ? mAlpha : 255);
+ protected void updateAlpha() {
+ getDrawable().setAlpha(calculateAlpha());
+ }
+
+ protected final int calculateAlpha() {
+ return mPauseAuth ? mAlpha : 255;
+ }
+
+ boolean isPauseAuth() {
+ return mPauseAuth;
}
private int expansionToAlpha(float expansion) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
index b6d80ba..b7726f4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
@@ -24,10 +24,16 @@
import android.graphics.PointF;
import android.graphics.RectF;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.ViewController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* Handles:
* 1. registering for listeners when its view is attached and unregistering on view detached
@@ -39,33 +45,50 @@
* - doze time event
*/
abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
- extends ViewController<T> {
+ extends ViewController<T> implements Dumpable {
@NonNull final StatusBarStateController mStatusBarStateController;
@NonNull final StatusBar mStatusBar;
+ @NonNull final DumpManager mDumpManger;
private boolean mNotificationShadeExpanded;
private int mStatusBarState;
protected UdfpsAnimationViewController(
T view,
- StatusBarStateController statusBarStateController,
- StatusBar statusBar) {
+ @NonNull StatusBarStateController statusBarStateController,
+ @NonNull StatusBar statusBar,
+ @NonNull DumpManager dumpManager) {
super(view);
mStatusBarStateController = statusBarStateController;
mStatusBar = statusBar;
+ mDumpManger = dumpManager;
}
+ abstract @NonNull String getTag();
+
@Override
protected void onViewAttached() {
mStatusBarStateController.addCallback(mStateListener);
mStateListener.onStateChanged(mStatusBarStateController.getState());
mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener);
+
+ mDumpManger.registerDumpable(getTag(), this);
}
@Override
protected void onViewDetached() {
mStatusBarStateController.removeCallback(mStateListener);
mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener);
+
+ mDumpManger.unregisterDumpable(getTag());
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mStatusBarState=" + StatusBarState.toShortString(mStatusBarState));
+ pw.println("mNotificationShadeExpanded=" + mNotificationShadeExpanded);
+ pw.println("shouldPauseAuth()=" + shouldPauseAuth());
+ pw.println("isPauseAuth=" + mView.isPauseAuth());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
index b712c65..93d80e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
@@ -16,6 +16,9 @@
package com.android.systemui.biometrics;
+import android.annotation.NonNull;
+
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -24,9 +27,15 @@
*/
class UdfpsBpViewController extends UdfpsAnimationViewController<UdfpsBpView> {
protected UdfpsBpViewController(
- UdfpsBpView view,
- StatusBarStateController statusBarStateController,
- StatusBar statusBar) {
- super(view, statusBarStateController, statusBar);
+ @NonNull UdfpsBpView view,
+ @NonNull StatusBarStateController statusBarStateController,
+ @NonNull StatusBar statusBar,
+ @NonNull DumpManager dumpManager) {
+ super(view, statusBarStateController, statusBar, dumpManager);
+ }
+
+ @Override
+ @NonNull String getTag() {
+ return "UdfpsBpViewController";
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 94aeb73..b9f9b1bc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -19,6 +19,8 @@
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -42,16 +44,16 @@
import android.view.VelocityTracker;
import android.view.WindowManager;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.biometrics.HbmTypes.HbmType;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
@@ -83,6 +85,8 @@
private final DelayableExecutor mFgExecutor;
@NonNull private final StatusBar mStatusBar;
@NonNull private final StatusBarStateController mStatusBarStateController;
+ @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
+ @NonNull private final DumpManager mDumpManager;
// 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;
@@ -91,7 +95,7 @@
// Tracks the velocity of a touch to help filter out the touches that move too fast.
@Nullable private VelocityTracker mVelocityTracker;
// The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
- private int mActivePointerId;
+ private int mActivePointerId = -1;
// The timestamp of the most recent touch log.
private long mTouchLogTime;
@@ -299,7 +303,9 @@
WindowManager windowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
- @NonNull StatusBar statusBar) {
+ @NonNull StatusBar statusBar,
+ @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ @NonNull DumpManager dumpManager) {
mContext = context;
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -309,6 +315,8 @@
mFgExecutor = fgExecutor;
mStatusBar = statusBar;
mStatusBarStateController = statusBarStateController;
+ mKeyguardViewManager = statusBarKeyguardViewManager;
+ mDumpManager = dumpManager;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -457,7 +465,8 @@
enrollView,
mServerRequest.mEnrollHelper,
mStatusBarStateController,
- mStatusBar
+ mStatusBar,
+ mDumpManager
);
case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD:
UdfpsKeyguardView keyguardView = (UdfpsKeyguardView)
@@ -466,7 +475,9 @@
return new UdfpsKeyguardViewController(
keyguardView,
mStatusBarStateController,
- mStatusBar
+ mStatusBar,
+ mKeyguardViewManager,
+ mDumpManager
);
case IUdfpsOverlayController.REASON_AUTH_BP:
// note: empty controller, currently shows no visual affordance
@@ -475,7 +486,8 @@
return new UdfpsBpViewController(
bpView,
mStatusBarStateController,
- mStatusBar
+ mStatusBar,
+ mDumpManager
);
case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER:
UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView)
@@ -484,7 +496,8 @@
return new UdfpsFpmOtherViewController(
authOtherView,
mStatusBarStateController,
- mStatusBar
+ mStatusBar,
+ mDumpManager
);
default:
Log.d(TAG, "Animation for reason " + reason + " not supported yet");
@@ -567,13 +580,13 @@
}
@Override
- public void enableHbm(@NonNull Surface surface) {
+ public void enableHbm(@HbmType int hbmType, @Nullable Surface surface) {
// Do nothing. This method can be implemented for devices that require the high-brightness
// mode for fingerprint illumination.
}
@Override
- public void disableHbm(@NonNull Surface surface) {
+ public void disableHbm(@HbmType int hbmType, @Nullable Surface surface) {
// Do nothing. This method can be implemented for devices that require the high-brightness
// mode for fingerprint illumination.
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java
index 13d31cb..18f5416 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java
@@ -61,12 +61,14 @@
*/
protected void updateFingerprintIconBounds(@NonNull Rect bounds) {
mFingerprintDrawable.setBounds(bounds);
+ invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
mFingerprintDrawable.setAlpha(mAlpha);
+ invalidateSelf();
}
boolean isIlluminationShowing() {
@@ -74,7 +76,11 @@
}
void setIlluminationShowing(boolean showing) {
+ if (mIlluminationShowing == showing) {
+ return;
+ }
mIlluminationShowing = showing;
+ invalidateSelf();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index d80e085..cd5abd7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -17,7 +17,6 @@
package com.android.systemui.biometrics;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -37,11 +36,10 @@
public class UdfpsEnrollDrawable extends UdfpsDrawable {
private static final String TAG = "UdfpsAnimationEnroll";
- private static final float SHADOW_RADIUS = 5.f;
- static final float PROGRESS_BAR_RADIUS = 140.f;
+ static final float PROGRESS_BAR_RADIUS = 180.f;
@NonNull private final Drawable mMovingTargetFpIcon;
- @NonNull private final Paint mSensorPaint;
+ @NonNull private final Paint mSensorOutlinePaint;
@NonNull private final Paint mBlueFill;
@NonNull private final Paint mBlueStroke;
@@ -51,11 +49,11 @@
UdfpsEnrollDrawable(@NonNull Context context) {
super(context);
- mSensorPaint = new Paint(0 /* flags */);
- mSensorPaint.setAntiAlias(true);
- mSensorPaint.setColor(Color.WHITE);
- mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK);
- mSensorPaint.setStyle(Paint.Style.FILL);
+ mSensorOutlinePaint = new Paint(0 /* flags */);
+ mSensorOutlinePaint.setAntiAlias(true);
+ mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
+ mSensorOutlinePaint.setStyle(Paint.Style.STROKE);
+ mSensorOutlinePaint.setStrokeWidth(2.f);
mBlueFill = new Paint(0 /* flags */);
mBlueFill.setAntiAlias(true);
@@ -98,18 +96,15 @@
return;
}
- final boolean isNightMode = (mContext.getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_YES) != 0;
- if (!isNightMode) {
- if (mSensorRect != null) {
- canvas.drawOval(mSensorRect, mSensorPaint);
- }
+ if (mSensorRect != null) {
+ canvas.drawOval(mSensorRect, mSensorOutlinePaint);
}
mFingerprintDrawable.draw(canvas);
// Draw moving target
if (mEnrollHelper.isCenterEnrollmentComplete()) {
mFingerprintDrawable.setAlpha(mAlpha == 255 ? 64 : mAlpha);
+ mSensorOutlinePaint.setAlpha(mAlpha == 255 ? 64 : mAlpha);
canvas.save();
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
@@ -123,15 +118,17 @@
canvas.restore();
} else {
mFingerprintDrawable.setAlpha(mAlpha);
+ mSensorOutlinePaint.setAlpha(mAlpha);
}
}
@Override
public void setAlpha(int alpha) {
super.setAlpha(alpha);
- mSensorPaint.setAlpha(alpha);
+ mSensorOutlinePaint.setAlpha(alpha);
mBlueFill.setAlpha(alpha);
mBlueStroke.setAlpha(alpha);
+ mMovingTargetFpIcon.setAlpha(alpha);
invalidateSelf();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 62058a9..98a703f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -21,6 +21,9 @@
import android.content.Context;
import android.graphics.PointF;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.TypedValue;
import java.util.ArrayList;
@@ -32,6 +35,10 @@
public class UdfpsEnrollHelper {
private static final String TAG = "UdfpsEnrollHelper";
+ private static final String SCALE_OVERRIDE =
+ "com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
+ private static final float SCALE = 0.5f;
+
// Enroll with two center touches before going to guided enrollment
private static final int NUM_CENTER_TOUCHES = 2;
@@ -39,9 +46,10 @@
void onEnrollmentProgress(int remaining, int totalSteps);
}
+ @NonNull private final Context mContext;
// IUdfpsOverlayController reason
private final int mEnrollReason;
- private final List<PointF> mGuidedEnrollmentPoints;
+ @NonNull private final List<PointF> mGuidedEnrollmentPoints;
private int mTotalSteps = -1;
private int mRemainingSteps = -1;
@@ -53,6 +61,7 @@
@Nullable Listener mListener;
public UdfpsEnrollHelper(@NonNull Context context, int reason) {
+ mContext = context;
mEnrollReason = reason;
mGuidedEnrollmentPoints = new ArrayList<>();
@@ -121,9 +130,15 @@
@NonNull
PointF getNextGuidedEnrollmentPoint() {
+ float scale = SCALE;
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ SCALE_OVERRIDE, SCALE,
+ UserHandle.USER_CURRENT);
+ }
final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES;
final PointF originalPoint = mGuidedEnrollmentPoints
.get(index % mGuidedEnrollmentPoints.size());
- return new PointF(originalPoint.x * 0.5f, originalPoint.y * 0.5f);
+ return new PointF(originalPoint.x * scale, originalPoint.y * scale);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 7985d95..75e8638 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -20,6 +20,7 @@
import android.util.AttributeSet;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.R;
@@ -28,8 +29,9 @@
* View corresponding with udfps_enroll_view.xml
*/
public class UdfpsEnrollView extends UdfpsAnimationView {
- private final UdfpsEnrollDrawable mFingerprintDrawable;
- private ImageView mFingerprintView;
+ @NonNull private final UdfpsEnrollDrawable mFingerprintDrawable;
+ @NonNull private ImageView mFingerprintView;
+ @NonNull private UdfpsProgressBar mProgressBar;
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -37,9 +39,17 @@
}
@Override
+ protected void updateAlpha() {
+ super.updateAlpha();
+ mProgressBar.setAlpha(calculateAlpha());
+ mProgressBar.getProgressDrawable().setAlpha(calculateAlpha());
+ }
+
+ @Override
protected void onFinishInflate() {
mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
mFingerprintView.setImageDrawable(mFingerprintDrawable);
+ mProgressBar = findViewById(R.id.progress_bar);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index da8d712..1ebbfbf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -21,6 +21,7 @@
import android.view.View;
import com.android.systemui.R;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -32,17 +33,23 @@
@NonNull private final UdfpsEnrollHelper mEnrollHelper;
protected UdfpsEnrollViewController(
- UdfpsEnrollView view,
+ @NonNull UdfpsEnrollView view,
@NonNull UdfpsEnrollHelper enrollHelper,
- StatusBarStateController statusBarStateController,
- StatusBar statusBar) {
- super(view, statusBarStateController, statusBar);
+ @NonNull StatusBarStateController statusBarStateController,
+ @NonNull StatusBar statusBar,
+ @NonNull DumpManager dumpManager) {
+ super(view, statusBarStateController, statusBar, dumpManager);
mEnrollHelper = enrollHelper;
mProgressBar = mView.findViewById(R.id.progress_bar);
mView.setEnrollHelper(mEnrollHelper);
}
@Override
+ @NonNull String getTag() {
+ return "UdfpsEnrollViewController";
+ }
+
+ @Override
protected void onViewAttached() {
super.onViewAttached();
if (mEnrollHelper.shouldShowProgressBar()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
index 587501b..6e2e4ba 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
@@ -16,6 +16,9 @@
package com.android.systemui.biometrics;
+import android.annotation.NonNull;
+
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -27,9 +30,15 @@
*/
class UdfpsFpmOtherViewController extends UdfpsAnimationViewController<UdfpsFpmOtherView> {
protected UdfpsFpmOtherViewController(
- UdfpsFpmOtherView view,
- StatusBarStateController statusBarStateController,
- StatusBar statusBar) {
- super(view, statusBarStateController, statusBar);
+ @NonNull UdfpsFpmOtherView view,
+ @NonNull StatusBarStateController statusBarStateController,
+ @NonNull StatusBar statusBar,
+ @NonNull DumpManager dumpManager) {
+ super(view, statusBarStateController, statusBar, dumpManager);
+ }
+
+ @Override
+ @NonNull String getTag() {
+ return "UdfpsFpmOtherViewController";
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
index b0c5da0..12c15a0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
@@ -36,14 +36,14 @@
public class UdfpsKeyguardDrawable extends UdfpsDrawable implements DozeReceiver {
private static final String TAG = "UdfpsAnimationKeyguard";
- private final int mLockScreenColor;
private final int mAmbientDisplayColor;
@NonNull private final Context mContext;
- private final int mMaxBurnInOffsetX;
- private final int mMaxBurnInOffsetY;
+ private int mLockScreenColor;
// AOD anti-burn-in offsets
+ private final int mMaxBurnInOffsetX;
+ private final int mMaxBurnInOffsetY;
private float mInterpolatedDarkAmount;
private float mBurnInOffsetX;
private float mBurnInOffsetY;
@@ -95,4 +95,10 @@
mInterpolatedDarkAmount = eased;
updateAodPositionAndColor();
}
+
+ void setLockScreenColor(int color) {
+ if (mLockScreenColor == color) return;
+ mLockScreenColor = color;
+ updateAodPositionAndColor();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 6a93560..378907c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -16,13 +16,23 @@
package com.android.systemui.biometrics;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.ImageView;
import androidx.annotation.Nullable;
+import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarState;
/**
* View corresponding with udfps_keyguard_view.xml
@@ -30,6 +40,14 @@
public class UdfpsKeyguardView extends UdfpsAnimationView {
private final UdfpsKeyguardDrawable mFingerprintDrawable;
private ImageView mFingerprintView;
+ private int mWallpaperTexColor;
+ private int mStatusBarState;
+
+ // used when highlighting fp icon:
+ private int mTextColorPrimary;
+ private ImageView mBgProtection;
+
+ private AnimatorSet mAnimatorSet;
public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -38,8 +56,15 @@
@Override
protected void onFinishInflate() {
+ super.onFinishInflate();
mFingerprintView = findViewById(R.id.udfps_keyguard_animation_fp_view);
- mFingerprintView.setImageDrawable(mFingerprintDrawable);
+ mFingerprintView.setForeground(mFingerprintDrawable);
+
+ mBgProtection = findViewById(R.id.udfps_keyguard_fp_bg);
+
+ mWallpaperTexColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
+ mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
+ android.R.attr.textColorPrimary);
}
@Override
@@ -48,13 +73,130 @@
}
@Override
+ void onIlluminationStarting() {
+ setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ void onIlluminationStopped() {
+ setVisibility(View.VISIBLE);
+ }
+
+ @Override
public boolean dozeTimeTick() {
// TODO: burnin
mFingerprintDrawable.dozeTimeTick();
return true;
}
+ void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ if (!isShadeLocked()) {
+ mFingerprintView.setAlpha(1f);
+ mFingerprintDrawable.setLockScreenColor(mWallpaperTexColor);
+ }
+ }
+
void onDozeAmountChanged(float linear, float eased) {
mFingerprintDrawable.onDozeAmountChanged(linear, eased);
+ invalidate();
+ }
+
+ /**
+ * Animates in the bg protection circle behind the fp icon to highlight the icon.
+ */
+ void animateHighlightFp() {
+ if (mBgProtection.getVisibility() == View.VISIBLE && mBgProtection.getAlpha() == 1f) {
+ // already fully highlighted, don't re-animate
+ return;
+ }
+
+ if (mAnimatorSet != null) {
+ mAnimatorSet.cancel();
+ }
+ ValueAnimator fpIconAnim;
+ if (isShadeLocked()) {
+ // set color and fade in since we weren't showing before
+ mFingerprintDrawable.setLockScreenColor(mTextColorPrimary);
+ fpIconAnim = ObjectAnimator.ofFloat(mFingerprintView, View.ALPHA, 0f, 1f);
+ } else {
+ // update icon color
+ fpIconAnim = new ValueAnimator();
+ fpIconAnim.setIntValues(mWallpaperTexColor, mTextColorPrimary);
+ fpIconAnim.setEvaluator(new ArgbEvaluator());
+ fpIconAnim.addUpdateListener(valueAnimator -> mFingerprintDrawable.setLockScreenColor(
+ (Integer) valueAnimator.getAnimatedValue()));
+ }
+
+ mAnimatorSet = new AnimatorSet();
+ mAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mAnimatorSet.setDuration(500);
+ mAnimatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBgProtection.setVisibility(View.VISIBLE);
+ }
+ });
+
+ mAnimatorSet.playTogether(
+ ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 0f, 1f),
+ ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 0f, 1f),
+ ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 0f, 1f),
+ fpIconAnim);
+ mAnimatorSet.start();
+ }
+
+ private boolean isShadeLocked() {
+ return mStatusBarState == StatusBarState.SHADE_LOCKED;
+ }
+
+ /**
+ * Animates out the bg protection circle behind the fp icon to unhighlight the icon.
+ */
+ void animateUnhighlightFp(@Nullable Runnable onEndAnimation) {
+ if (mBgProtection.getVisibility() == View.GONE) {
+ // already hidden
+ return;
+ }
+
+ if (mAnimatorSet != null) {
+ mAnimatorSet.cancel();
+ }
+ ValueAnimator fpIconAnim;
+ if (isShadeLocked()) {
+ // fade out
+ fpIconAnim = ObjectAnimator.ofFloat(mFingerprintView, View.ALPHA, 1f, 0f);
+ } else {
+ // update icon color
+ fpIconAnim = new ValueAnimator();
+ fpIconAnim.setIntValues(mTextColorPrimary, mWallpaperTexColor);
+ fpIconAnim.setEvaluator(new ArgbEvaluator());
+ fpIconAnim.addUpdateListener(valueAnimator -> mFingerprintDrawable.setLockScreenColor(
+ (Integer) valueAnimator.getAnimatedValue()));
+ }
+
+ mAnimatorSet = new AnimatorSet();
+ mAnimatorSet.playTogether(
+ ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 1f, 0f),
+ ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 1f, 0f),
+ ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 1f, 0f),
+ fpIconAnim);
+ mAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mAnimatorSet.setDuration(500);
+
+ mAnimatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBgProtection.setVisibility(View.GONE);
+ if (onEndAnimation != null) {
+ onEndAnimation.run();
+ }
+ }
+ });
+ mAnimatorSet.start();
+ }
+
+ boolean isAnimating() {
+ return mAnimatorSet != null && mAnimatorSet.isRunning();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 14bb3fee..08e5d56 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -16,34 +16,62 @@
package com.android.systemui.biometrics;
+import android.annotation.NonNull;
+
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
/**
* Class that coordinates non-HBM animations during keyguard authentication.
*/
public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
+ @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
+
private boolean mForceShow;
protected UdfpsKeyguardViewController(
- UdfpsKeyguardView view,
- StatusBarStateController statusBarStateController,
- StatusBar statusBar) {
- super(view, statusBarStateController, statusBar);
+ @NonNull UdfpsKeyguardView view,
+ @NonNull StatusBarStateController statusBarStateController,
+ @NonNull StatusBar statusBar,
+ @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ @NonNull DumpManager dumpManager) {
+ super(view, statusBarStateController, statusBar, dumpManager);
+ mKeyguardViewManager = statusBarKeyguardViewManager;
+ }
+
+ @Override
+ @NonNull String getTag() {
+ return "UdfpsKeyguardViewController";
}
@Override
protected void onViewAttached() {
super.onViewAttached();
- mStatusBarStateController.addCallback(mStateListener);
+
final float dozeAmount = mStatusBarStateController.getDozeAmount();
+ mStatusBarStateController.addCallback(mStateListener);
mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
+ mStateListener.onStateChanged(mStatusBarStateController.getState());
+ mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
}
@Override
protected void onViewDetached() {
super.onViewDetached();
mStatusBarStateController.removeCallback(mStateListener);
+ mAlternateAuthInterceptor.reset();
+ mKeyguardViewManager.setAlternateAuthInterceptor(null);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("mForceShow=" + mForceShow);
}
/**
@@ -56,7 +84,11 @@
mForceShow = forceShow;
updatePauseAuth();
- // TODO: animate show/hide background protection
+ if (mForceShow) {
+ mView.animateHighlightFp();
+ } else {
+ mView.animateUnhighlightFp(() -> mKeyguardViewManager.cancelPostAuthActions());
+ }
}
/**
@@ -76,6 +108,50 @@
@Override
public void onDozeAmountChanged(float linear, float eased) {
mView.onDozeAmountChanged(linear, eased);
+ if (linear != 0) forceShow(false);
+ }
+
+ @Override
+ public void onStateChanged(int statusBarState) {
+ mView.setStatusBarState(statusBarState);
}
};
+
+ private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
+ new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
+ @Override
+ public boolean showAlternativeAuthMethod() {
+ if (mForceShow) {
+ return false;
+ }
+
+ forceShow(true);
+ return true;
+ }
+
+ @Override
+ public boolean reset() {
+ if (!mForceShow) {
+ return false;
+ }
+
+ forceShow(false);
+ return true;
+ }
+
+ @Override
+ public boolean isShowingAlternativeAuth() {
+ return mForceShow;
+ }
+
+ @Override
+ public boolean isAnimating() {
+ return mView.isAnimating();
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.print(getTag());
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
index 61ec127..4d441bd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
@@ -23,16 +23,25 @@
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
+import com.android.systemui.biometrics.HbmTypes.HbmType;
+
/**
* Under-display fingerprint sensor Surface View. The surface should be used for HBM-specific things
* only. All other animations should be done on the other view.
*/
public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
private static final String TAG = "UdfpsSurfaceView";
+ private static final String SETTING_HBM_TYPE =
+ "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType";
+ private static final @HbmType int DEFAULT_HBM_TYPE = HbmTypes.GLOBAL_HBM;
/**
* This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
@@ -45,6 +54,7 @@
@NonNull private final SurfaceHolder mHolder;
@NonNull private final Paint mSensorPaint;
@NonNull private final SimpleDrawable mIlluminationDotDrawable;
+ private final @HbmType int mHbmType;
@NonNull private RectF mSensorRect;
@Nullable private HbmCallback mHbmCallback;
@@ -70,6 +80,13 @@
mIlluminationDotDrawable = canvas -> {
canvas.drawOval(mSensorRect, mSensorPaint);
};
+
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ mHbmType = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ SETTING_HBM_TYPE, DEFAULT_HBM_TYPE, UserHandle.USER_CURRENT);
+ } else {
+ mHbmType = DEFAULT_HBM_TYPE;
+ }
}
@Override
@@ -79,10 +96,15 @@
@Override
public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
- if (mHbmCallback != null && mHolder.getSurface().isValid()) {
- mHbmCallback.enableHbm(mHolder.getSurface());
+ if (mHbmCallback != null) {
+ mHbmCallback.enableHbm(mHbmType, mHolder.getSurface());
+ } else {
+ Log.e(TAG, "startIllumination | mHbmCallback is null");
}
- drawImmediately(mIlluminationDotDrawable);
+
+ if (mHbmType == HbmTypes.GLOBAL_HBM) {
+ drawImmediately(mIlluminationDotDrawable);
+ }
if (onIlluminatedRunnable != null) {
// No framework API can reliably tell when a frame reaches the panel. A timeout is the
@@ -94,8 +116,10 @@
@Override
public void stopIllumination() {
- if (mHbmCallback != null && mHolder.getSurface().isValid()) {
- mHbmCallback.disableHbm(mHolder.getSurface());
+ if (mHbmCallback != null) {
+ mHbmCallback.disableHbm(mHbmType, mHolder.getSurface());
+ } else {
+ Log.e(TAG, "stopIllumination | mHbmCallback is null");
}
invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index efb7992..7e7cdce 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -28,9 +28,11 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
+import com.android.systemui.classifier.HistoryTracker.BeliefListener;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.sensors.ThresholdSensor;
import java.io.FileDescriptor;
@@ -40,7 +42,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Queue;
import java.util.Set;
import java.util.StringJoiner;
@@ -61,12 +62,14 @@
private static final int RECENT_INFO_LOG_SIZE = 40;
private static final int RECENT_SWIPE_LOG_SIZE = 20;
private static final double TAP_CONFIDENCE_THRESHOLD = 0.7;
+ private static final double FALSE_BELIEF_THRESHOLD = 0.9;
private final FalsingDataProvider mDataProvider;
private final DockManager mDockManager;
private final SingleTapClassifier mSingleTapClassifier;
private final DoubleTapClassifier mDoubleTapClassifier;
private final HistoryTracker mHistoryTracker;
+ private final KeyguardStateController mKeyguardStateController;
private final boolean mTestHarness;
private final MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
@@ -76,6 +79,7 @@
new ArrayDeque<>(RECENT_SWIPE_LOG_SIZE + 1);
private final Collection<FalsingClassifier> mClassifiers;
+ private final List<FalsingBeliefListener> mFalsingBeliefListeners = new ArrayList<>();
private final SessionListener mSessionListener = new SessionListener() {
@Override
@@ -89,23 +93,47 @@
}
};
- private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener =
- new FalsingDataProvider.GestureCompleteListener() {
- @Override
- public void onGestureComplete(long completionTimeMs) {
- if (mPriorResults != null) {
- mHistoryTracker.addResults(mPriorResults, completionTimeMs);
- mPriorResults = null;
- } else {
- // Gestures that were not classified get treated as a false.
- mHistoryTracker.addResults(
- Collections.singleton(
- FalsingClassifier.Result.falsed(.8, "unclassified")),
- completionTimeMs);
+ private final BeliefListener mBeliefListener = new BeliefListener() {
+ @Override
+ public void onBeliefChanged(double belief) {
+ logInfo(String.format(
+ "{belief=%s confidence=%s}",
+ mHistoryTracker.falseBelief(),
+ mHistoryTracker.falseConfidence()));
+ if (belief > FALSE_BELIEF_THRESHOLD) {
+ mFalsingBeliefListeners.forEach(FalsingBeliefListener::onFalse);
+ logInfo("Triggering False Event (Threshold: " + FALSE_BELIEF_THRESHOLD + ")");
}
}
};
+ private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener =
+ new FalsingDataProvider.GestureCompleteListener() {
+ @Override
+ public void onGestureComplete(long completionTimeMs) {
+ if (mPriorResults != null) {
+ mPriorResults.forEach(result -> {
+ if (result.isFalse()) {
+ String reason = result.getReason();
+ if (reason != null) {
+ logInfo(reason);
+ }
+ }
+ });
+
+ mHistoryTracker.addResults(mPriorResults, completionTimeMs);
+ mPriorResults = null;
+ } else {
+ // Gestures that were not classified get treated as a false.
+ mHistoryTracker.addResults(
+ Collections.singleton(
+ FalsingClassifier.Result.falsed(
+ .8, getClass().getSimpleName(), "unclassified")),
+ completionTimeMs);
+ }
+ }
+ };
+
private Collection<FalsingClassifier.Result> mPriorResults;
@Inject
@@ -113,7 +141,8 @@
DockManager dockManager, MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
- HistoryTracker historyTracker, @TestHarness boolean testHarness) {
+ HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
+ @TestHarness boolean testHarness) {
mDataProvider = falsingDataProvider;
mDockManager = dockManager;
mMetricsLogger = metricsLogger;
@@ -121,10 +150,12 @@
mSingleTapClassifier = singleTapClassifier;
mDoubleTapClassifier = doubleTapClassifier;
mHistoryTracker = historyTracker;
+ mKeyguardStateController = keyguardStateController;
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
mDataProvider.addGestureCompleteListener(mGestureCompleteListener);
+ mHistoryTracker.addBeliefListener(mBeliefListener);
}
@Override
@@ -134,32 +165,19 @@
@Override
public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
- boolean result;
+ if (skipFalsing()) {
+ return false;
+ }
- mDataProvider.setInteractionType(interactionType);
+ boolean result;
if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) {
Stream<FalsingClassifier.Result> results =
- mClassifiers.stream().map(falsingClassifier -> {
- FalsingClassifier.Result classifierResult =
- falsingClassifier.classifyGesture(
- mHistoryTracker.falseBelief(),
- mHistoryTracker.falseConfidence());
- if (classifierResult.isFalse()) {
- logInfo(String.format(
- (Locale) null,
- "{classifier=%s, interactionType=%d}",
- falsingClassifier.getClass().getName(),
- mDataProvider.getInteractionType()));
- String reason = classifierResult.getReason();
- if (reason != null) {
- logInfo(reason);
- }
- } else {
- logDebug(falsingClassifier.getClass().getName() + ": false");
- }
- return classifierResult;
- });
+ mClassifiers.stream().map(falsingClassifier ->
+ falsingClassifier.classifyGesture(
+ interactionType,
+ mHistoryTracker.falseBelief(),
+ mHistoryTracker.falseConfidence()));
mPriorResults = new ArrayList<>();
final boolean[] localResult = {false};
results.forEach(classifierResult -> {
@@ -172,13 +190,13 @@
mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1));
}
- logDebug("Is false touch? " + result);
+ logDebug("False Gesture: " + result);
if (Build.IS_ENG || Build.IS_USERDEBUG) {
// Copy motion events, as the passed in list gets emptied out elsewhere in the code.
RECENT_SWIPES.add(new DebugSwipeRecord(
result,
- mDataProvider.getInteractionType(),
+ interactionType,
mDataProvider.getRecentMotionEvents().stream().map(
motionEvent -> new XYDt(
(int) motionEvent.getX(),
@@ -195,57 +213,64 @@
@Override
public boolean isFalseTap(boolean robustCheck, double falsePenalty) {
+ if (skipFalsing()) {
+ return false;
+ }
+
FalsingClassifier.Result singleTapResult =
mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents());
mPriorResults = Collections.singleton(singleTapResult);
- if (singleTapResult.isFalse()) {
- logInfo(String.format(
- (Locale) null, "{classifier=%s}", mSingleTapClassifier.getClass().getName()));
- String reason = singleTapResult.getReason();
- if (reason != null) {
- logInfo(reason);
- }
- return true;
- }
- if (robustCheck) {
+ if (!singleTapResult.isFalse() && robustCheck) {
if (mDataProvider.isJustUnlockedWithFace()) {
// Immediately pass if a face is detected.
mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1));
+ logDebug("False Single Tap: false (face detected)");
return false;
} else if (!isFalseDoubleTap()) {
// We must check double tapping before other heuristics. This is because
// the double tap will fail if there's only been one tap. We don't want that
// failure to be recorded in mPriorResults.
+ logDebug("False Single Tap: false (double tapped)");
return false;
} else if (mHistoryTracker.falseBelief() > TAP_CONFIDENCE_THRESHOLD) {
mPriorResults = Collections.singleton(
- FalsingClassifier.Result.falsed(0, "bad history"));
+ FalsingClassifier.Result.falsed(
+ 0, getClass().getSimpleName(), "bad history"));
+ logDebug("False Single Tap: true (bad history)");
return true;
} else {
mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(0.1));
+ logDebug("False Single Tap: false (default)");
return false;
}
+
+ } else {
+ logDebug("False Single Tap: " + singleTapResult.isFalse() + " (simple)");
+ return singleTapResult.isFalse();
}
- return false;
}
@Override
public boolean isFalseDoubleTap() {
- FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture();
- mPriorResults = Collections.singleton(result);
- if (result.isFalse()) {
- logInfo(String.format(
- (Locale) null, "{classifier=%s}", mDoubleTapClassifier.getClass().getName()));
- String reason = result.getReason();
- if (reason != null) {
- logInfo(reason);
- }
+ if (skipFalsing()) {
+ return false;
}
+
+ FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture(
+ Classifier.GENERIC,
+ mHistoryTracker.falseBelief(),
+ mHistoryTracker.falseConfidence());
+ mPriorResults = Collections.singleton(result);
+ logDebug("False Double Tap: " + result.isFalse());
return result.isFalse();
}
+ private boolean skipFalsing() {
+ return !mKeyguardStateController.isShowing();
+ }
+
@Override
public void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
@@ -282,6 +307,16 @@
}
@Override
+ public void addFalsingBeliefListener(FalsingBeliefListener listener) {
+ mFalsingBeliefListeners.add(listener);
+ }
+
+ @Override
+ public void removeFalsingBeliefListener(FalsingBeliefListener listener) {
+ mFalsingBeliefListeners.remove(listener);
+ }
+
+ @Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("BRIGHTLINE FALSING MANAGER");
@@ -321,6 +356,8 @@
mDataProvider.removeSessionListener(mSessionListener);
mDataProvider.removeGestureCompleteListener(mGestureCompleteListener);
mClassifiers.forEach(FalsingClassifier::cleanup);
+ mFalsingBeliefListeners.clear();
+ mHistoryTracker.removeBeliefListener(mBeliefListener);
}
static void logDebug(String msg) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
index ffcdb93..71edbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
@@ -62,15 +62,16 @@
VERTICAL_ANGLE_RANGE);
}
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
float angle = getAngle();
if (angle == Float.MAX_VALUE) { // Unknown angle
return Result.passed(0);
}
- if (getInteractionType() == LEFT_AFFORDANCE
- || getInteractionType() == RIGHT_AFFORDANCE) {
+ if (interactionType == LEFT_AFFORDANCE || interactionType == RIGHT_AFFORDANCE) {
return Result.passed(0);
}
@@ -86,7 +87,7 @@
|| angleBetween(angle, minAngle - NINETY_DEG, maxAngle - NINETY_DEG)
|| angleBetween(angle, minAngle + ONE_HUNDRED_EIGHTY_DEG,
maxAngle + ONE_HUNDRED_EIGHTY_DEG);
- return falsed ? Result.falsed(0.5f, getReason()) : Result.passed(0.5);
+ return falsed ? falsed(0.5f, getReason()) : Result.passed(0.5);
}
private String getReason() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index 0f121c1..5a9c386 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -136,8 +136,6 @@
float dX = getLastMotionEvent().getX() - getFirstMotionEvent().getX();
float dY = getLastMotionEvent().getY() - getFirstMotionEvent().getY();
- logInfo("dX: " + dX + " dY: " + dY + " xV: " + vX + " yV: " + vY);
-
return new DistanceVectors(dX, dY, vX, vY);
}
@@ -147,9 +145,10 @@
}
@Override
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
- return !getPassedFlingThreshold()
- ? Result.falsed(0.5, getReason()) : Result.passed(0.5);
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
+ return !getPassedFlingThreshold() ? falsed(0.5, getReason()) : Result.passed(0.5);
}
String getReason() {
@@ -172,7 +171,7 @@
Result isLongSwipe() {
boolean longSwipe = getPassedDistanceThreshold();
logDebug("Is longSwipe? " + longSwipe);
- return longSwipe ? Result.passed(0.5) : Result.falsed(0.5, getReason());
+ return longSwipe ? Result.passed(0.5) : falsed(0.5, getReason());
}
private boolean getPassedDistanceThreshold() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java
index baa54a6..e7c9d18 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DoubleTapClassifier.java
@@ -46,18 +46,20 @@
}
@Override
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
List<MotionEvent> secondTapEvents = getRecentMotionEvents();
List<MotionEvent> firstTapEvents = getPriorMotionEvents();
StringBuilder reason = new StringBuilder();
if (firstTapEvents == null) {
- return Result.falsed(0, "Only one gesture recorded");
+ return falsed(0, "Only one gesture recorded");
}
return !isDoubleTap(firstTapEvents, secondTapEvents, reason)
- ? Result.falsed(0.5, reason.toString()) : Result.passed(0.5);
+ ? falsed(0.5, reason.toString()) : Result.passed(0.5);
}
/** Returns true if the two supplied lists of {@link MotionEvent}s look like a double-tap. */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java
index 1af5f7c..81b9f66 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingClassifier.java
@@ -35,6 +35,14 @@
mDataProvider.addMotionEventListener(mMotionEventListener);
}
+ protected String getFalsingContext() {
+ return getClass().getSimpleName();
+ }
+
+ protected Result falsed(double confidence, String reason) {
+ return Result.falsed(confidence, getFalsingContext(), reason);
+ }
+
List<MotionEvent> getRecentMotionEvents() {
return mDataProvider.getRecentMotionEvents();
}
@@ -87,10 +95,6 @@
return mDataProvider.getYdpi();
}
- final @Classifier.InteractionType int getInteractionType() {
- return mDataProvider.getInteractionType();
- }
-
void cleanup() {
mDataProvider.removeMotionEventListener(mMotionEventListener);
}
@@ -101,42 +105,32 @@
* Useful for classifiers that need to see every MotionEvent, but most can probably
* use {@link #getRecentMotionEvents()} instead, which will return a list of MotionEvents.
*/
- void onTouchEvent(MotionEvent motionEvent) {};
+ void onTouchEvent(MotionEvent motionEvent) {}
/**
* Called when a ProximityEvent occurs (change in near/far).
*/
- void onProximityEvent(ProximitySensor.ThresholdSensorEvent proximityEvent) {};
+ void onProximityEvent(ProximitySensor.ThresholdSensorEvent proximityEvent) {}
/**
* The phone screen has turned on and we need to begin falsing detection.
*/
- void onSessionStarted() {};
+ void onSessionStarted() {}
/**
* The phone screen has turned off and falsing data can be discarded.
*/
- void onSessionEnded() {};
+ void onSessionEnded() {}
/**
- * Returns whether a gesture looks like a false touch.
+ * Returns whether a gesture looks like a false touch, taking history into consideration.
*
- * See also {@link #classifyGesture(double, double)}.
+ * See {@link HistoryTracker#falseBelief()} and {@link HistoryTracker#falseConfidence()}.
*/
- Result classifyGesture() {
- return calculateFalsingResult(0.5, 0);
- }
-
- /**
- * Returns whether a gesture looks like a false touch, with the option to consider history.
- *
- * Unlike the parameter-less version of this method, this method allows the classifier to take
- * history into account, penalizing or boosting confidence in a gesture based on recent results.
- *
- * See also {@link #classifyGesture()}.
- */
- Result classifyGesture(double historyBelief, double historyConfidence) {
- return calculateFalsingResult(historyBelief, historyConfidence);
+ Result classifyGesture(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
+ return calculateFalsingResult(interactionType, historyBelief, historyConfidence);
}
/**
@@ -144,7 +138,9 @@
*
* When passed a historyConfidence of 0, the history belief should be wholly ignored.
*/
- abstract Result calculateFalsingResult(double historyBelief, double historyConfidence);
+ abstract Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence);
/** */
public static void logDebug(String msg) {
@@ -167,14 +163,16 @@
public static class Result {
private final boolean mFalsed;
private final double mConfidence;
+ private final String mContext;
private final String mReason;
/**
- * See {@link #falsed(double, String)} abd {@link #passed(double)}.
+ * See {@link #falsed(double, String, String)} abd {@link #passed(double)}.
*/
- private Result(boolean falsed, double confidence, String reason) {
+ private Result(boolean falsed, double confidence, String context, String reason) {
mFalsed = falsed;
mConfidence = confidence;
+ mContext = context;
mReason = reason;
}
@@ -187,21 +185,21 @@
}
public String getReason() {
- return mReason;
+ return String.format("{context=%s reason=%s}", mContext, mReason);
}
/**
* Construct a "falsed" result indicating that a gesture should be treated as accidental.
*/
- public static Result falsed(double confidence, String reason) {
- return new Result(true, confidence, reason);
+ public static Result falsed(double confidence, String context, String reason) {
+ return new Result(true, confidence, context, reason);
}
/**
* Construct a "passed" result indicating that a gesture should be allowed.
*/
public static Result passed(double confidence) {
- return new Result(false, confidence, null);
+ return new Result(false, confidence, null, null);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index e090006..cf61697 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.SystemClock;
@@ -48,6 +49,7 @@
private final HistoryTracker mHistoryTracker;
private final ProximitySensor mProximitySensor;
private final StatusBarStateController mStatusBarStateController;
+ private final KeyguardStateController mKeyguardStateController;
private final SystemClock mSystemClock;
private int mState;
@@ -87,13 +89,14 @@
FalsingCollectorImpl(FalsingDataProvider falsingDataProvider, FalsingManager falsingManager,
KeyguardUpdateMonitor keyguardUpdateMonitor, HistoryTracker historyTracker,
ProximitySensor proximitySensor, StatusBarStateController statusBarStateController,
- SystemClock systemClock) {
+ KeyguardStateController keyguardStateController, SystemClock systemClock) {
mFalsingDataProvider = falsingDataProvider;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mHistoryTracker = historyTracker;
mProximitySensor = proximitySensor;
mStatusBarStateController = statusBarStateController;
+ mKeyguardStateController = keyguardStateController;
mSystemClock = systemClock;
@@ -124,7 +127,6 @@
@Override
public void onNotificationStartDraggingDown() {
- updateInteractionType(Classifier.NOTIFICATION_DRAG_DOWN);
}
@Override
@@ -137,7 +139,6 @@
@Override
public void onQsDown() {
- updateInteractionType(Classifier.QUICK_SETTINGS);
}
@Override
@@ -156,7 +157,6 @@
@Override
public void onTrackingStarted(boolean secure) {
- updateInteractionType(secure ? Classifier.BOUNCER_UNLOCK : Classifier.UNLOCK);
}
@Override
@@ -173,8 +173,6 @@
@Override
public void onAffordanceSwipingStarted(boolean rightCorner) {
- updateInteractionType(
- rightCorner ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
}
@Override
@@ -183,7 +181,6 @@
@Override
public void onStartExpandingFromPulse() {
- updateInteractionType(Classifier.PULSE_EXPAND);
}
@Override
@@ -234,7 +231,6 @@
@Override
public void onNotificationStartDismissing() {
- updateInteractionType(Classifier.NOTIFICATION_DISMISS);
}
@Override
@@ -255,6 +251,10 @@
@Override
public void onTouchEvent(MotionEvent ev) {
+ if (!mKeyguardStateController.isShowing()) {
+ avoidGesture();
+ return;
+ }
// We delay processing down events to see if another component wants to process them.
// If #avoidGesture is called after a MotionEvent.ACTION_DOWN, all following motion events
// will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
@@ -276,8 +276,8 @@
@Override
public void avoidGesture() {
+ mAvoidGesture = true;
if (mPendingDownEvent != null) {
- mAvoidGesture = true;
mPendingDownEvent.recycle();
mPendingDownEvent = null;
}
@@ -295,11 +295,6 @@
mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis());
}
- private void updateInteractionType(@Classifier.InteractionType int type) {
- logDebug("InteractionType: " + type);
- mFalsingDataProvider.setInteractionType(type);
- }
-
private boolean shouldSessionBeActive() {
return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 336f13f..f665a74 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -47,8 +47,6 @@
private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>();
private final List<GestureCompleteListener> mGestureCompleteListeners = new ArrayList<>();
- private @Classifier.InteractionType int mInteractionType;
-
private TimeLimitedMotionEventBuffer mRecentMotionEvents =
new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
private List<MotionEvent> mPriorMotionEvents;
@@ -138,21 +136,6 @@
}
/**
- * interactionType is defined by {@link com.android.systemui.classifier.Classifier}.
- */
- public final void setInteractionType(@Classifier.InteractionType int interactionType) {
- if (mInteractionType != interactionType) {
- mInteractionType = interactionType;
- mDirty = true;
- }
- }
-
- /** Return the interaction type that is being compared against for falsing. */
- public final int getInteractionType() {
- return mInteractionType;
- }
-
- /**
* Get the first recorded {@link MotionEvent} of the most recent gesture.
*
* Note that MotionEvents are not kept forever. As a gesture gets longer in duration, older
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
index aac27cb..d39f124 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -24,6 +24,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* Simple Fake for testing where {@link FalsingManager} is required.
@@ -38,6 +40,8 @@
private boolean mIsReportingEnabled;
private boolean mIsFalseRobustTap;
+ private final List<FalsingBeliefListener> mFalsingBeliefListeners = new ArrayList<>();
+
@Override
public void onSuccessfulUnlock() {
@@ -127,4 +131,14 @@
public void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
}
+
+ @Override
+ public void addFalsingBeliefListener(FalsingBeliefListener listener) {
+ mFalsingBeliefListeners.add(listener);
+ }
+
+ @Override
+ public void removeFalsingBeliefListener(FalsingBeliefListener listener) {
+ mFalsingBeliefListeners.remove(listener);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index e9bb48c..9c29f27 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -161,6 +161,16 @@
}
@Override
+ public void addFalsingBeliefListener(FalsingBeliefListener listener) {
+ mInternalFalsingManager.addFalsingBeliefListener(listener);
+ }
+
+ @Override
+ public void removeFalsingBeliefListener(FalsingBeliefListener listener) {
+ mInternalFalsingManager.removeFalsingBeliefListener(listener);
+ }
+
+ @Override
public void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
mInternalFalsingManager.onProximityEvent(proximityEvent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java
index be48ec4..09bf04c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HistoryTracker.java
@@ -20,7 +20,9 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.util.time.SystemClock;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
@@ -50,6 +52,7 @@
private final SystemClock mSystemClock;
DelayQueue<CombinedResult> mResults = new DelayQueue<>();
+ private final List<BeliefListener> mBeliefListeners = new ArrayList<>();
@Inject
HistoryTracker(SystemClock systemClock) {
@@ -153,8 +156,17 @@
}
mResults.add(new CombinedResult(uptimeMillis, finalScore));
+
+ mBeliefListeners.forEach(beliefListener -> beliefListener.onBeliefChanged(falseBelief()));
}
+ void addBeliefListener(BeliefListener listener) {
+ mBeliefListeners.add(listener);
+ }
+
+ void removeBeliefListener(BeliefListener listener) {
+ mBeliefListeners.remove(listener);
+ }
/**
* Represents a falsing score combing all the classifiers together.
*
@@ -197,4 +209,8 @@
return Long.compare(ourDelay, otherDelay);
}
}
+
+ interface BeliefListener {
+ void onBeliefChanged(double belief);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
index 77d2d42..17942a6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/PointerCountClassifier.java
@@ -56,14 +56,15 @@
}
@Override
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
- int interactionType = getInteractionType();
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
int allowedPointerCount =
(interactionType == QUICK_SETTINGS || interactionType == NOTIFICATION_DRAG_DOWN)
? MAX_ALLOWED_POINTERS_SWIPE_DOWN : MAX_ALLOWED_POINTERS;
return mMaxPointerCount > allowedPointerCount
- ? Result.falsed(1, getReason(allowedPointerCount)) : Result.passed(0);
+ ? falsed(1, getReason(allowedPointerCount)) : Result.passed(0);
}
private String getReason(int allowedPointerCount) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
index 6e97857..ac330f0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java
@@ -112,28 +112,31 @@
}
@Override
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
- if (getInteractionType() == QUICK_SETTINGS) {
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
+ if (interactionType == QUICK_SETTINGS) {
return Result.passed(0);
}
- logInfo("Percent of gesture in proximity: " + mPercentNear);
-
if (mPercentNear > mPercentCoveredThreshold) {
Result longSwipeResult = mDistanceClassifier.isLongSwipe();
return longSwipeResult.isFalse()
- ? Result.falsed(0.5, getReason(longSwipeResult)) : Result.passed(0.5);
+ ? falsed(
+ 0.5, getReason(longSwipeResult, mPercentNear, mPercentCoveredThreshold))
+ : Result.passed(0.5);
}
return Result.passed(0.5);
}
- private String getReason(Result longSwipeResult) {
+ private static String getReason(Result longSwipeResult, float percentNear,
+ float percentCoveredThreshold) {
return String.format(
(Locale) null,
"{percentInProximity=%f, threshold=%f, distanceClassifier=%s}",
- mPercentNear,
- mPercentCoveredThreshold,
+ percentNear,
+ percentCoveredThreshold,
longSwipeResult.getReason());
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
index 4dd20cc..68a9e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
@@ -39,14 +39,16 @@
}
@Override
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
return isTap(getRecentMotionEvents());
}
/** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
public Result isTap(List<MotionEvent> motionEvents) {
if (motionEvents.isEmpty()) {
- return Result.falsed(0, "no motion events");
+ return falsed(0, "no motion events");
}
float downX = motionEvents.get(0).getX();
float downY = motionEvents.get(0).getY();
@@ -58,13 +60,13 @@
+ Math.abs(event.getX() - downX)
+ "vs "
+ mTouchSlop;
- return Result.falsed(0.5, reason);
+ return falsed(0.5, reason);
} else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
reason = "dY too big for a tap: "
+ Math.abs(event.getY() - downY)
+ " vs "
+ mTouchSlop;
- return Result.falsed(0.5, reason);
+ return falsed(0.5, reason);
}
}
return Result.passed(0);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index 4e032ea..427c2ef 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -38,13 +38,15 @@
}
@Override
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
boolean vertical = isVertical();
boolean up = isUp();
boolean right = isRight();
boolean wrongDirection = true;
- switch (getInteractionType()) {
+ switch (interactionType) {
case QUICK_SETTINGS:
case PULSE_EXPAND:
case NOTIFICATION_DRAG_DOWN:
@@ -68,10 +70,11 @@
break;
}
- return wrongDirection ? Result.falsed(1, getReason()) : Result.passed(0.5);
+ return wrongDirection ? falsed(1, getReason(interactionType)) : Result.passed(0.5);
}
- private String getReason() {
- return String.format("{vertical=%s, up=%s, right=%s}", isVertical(), isUp(), isRight());
+ private String getReason(int interactionType) {
+ return String.format("{interaction=%s, vertical=%s, up=%s, right=%s}",
+ interactionType, isVertical(), isUp(), isRight());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index 2058257..1d413af 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -84,7 +84,9 @@
}
@Override
- Result calculateFalsingResult(double historyBelief, double historyConfidence) {
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
List<MotionEvent> motionEvents = getRecentMotionEvents();
// Rotate horizontal gestures to be horizontal between their first and last point.
// Rotate vertical gestures to be vertical between their first and last point.
@@ -156,7 +158,7 @@
logDebug("Straightness Deviance: (" + devianceX + "," + devianceY + ") vs "
+ "(" + maxXDeviance + "," + maxYDeviance + ")");
return devianceX > maxXDeviance || devianceY > maxYDeviance
- ? Result.falsed(0.5, getReason()) : Result.passed(0.5);
+ ? falsed(0.5, getReason()) : Result.passed(0.5);
}
private String getReason() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
new file mode 100644
index 0000000..3bfdcae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.controls
+
+import android.service.controls.DeviceTypes.DeviceType
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.controls.ui.ControlViewHolder
+
+/**
+ * Interface for logging UI events related to controls
+ */
+interface ControlsMetricsLogger {
+
+ /**
+ * Assign a new instance id for this controls session, defined as when the controls area is
+ * made visible to when it is closed.
+ */
+ fun assignInstanceId()
+
+ fun touch(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_TOUCH.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ fun drag(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_DRAG.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ fun longPress(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_LONG_PRESS.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ fun refreshBegin(uid: Int, isLocked: Boolean) {
+ assignInstanceId()
+ log(ControlsEvents.CONTROL_REFRESH_BEGIN.id, 0, uid, isLocked)
+ }
+
+ fun refreshEnd(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_REFRESH_END.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ /**
+ * Logs a controls-related event
+ *
+ * @param eventId Main UIEvent to capture
+ * @param deviceType One of {@link android.service.controls.DeviceTypes}
+ * @param packageName Package name of the service that receives the request
+ * @param isLocked Is the device locked at the start of the action?
+ */
+ fun log(
+ eventId: Int,
+ @DeviceType deviceType: Int,
+ uid: Int,
+ isLocked: Boolean
+ )
+
+ private enum class ControlsEvents(val metricId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "User touched a control")
+ CONTROL_TOUCH(714),
+
+ @UiEvent(doc = "User dragged a control")
+ CONTROL_DRAG(713),
+
+ @UiEvent(doc = "User long-pressed a control")
+ CONTROL_LONG_PRESS(715),
+
+ @UiEvent(doc = "User has opened controls, and a state refresh has begun")
+ CONTROL_REFRESH_BEGIN(716),
+
+ @UiEvent(doc = "User has opened controls, and a state refresh has ended")
+ CONTROL_REFRESH_END(717);
+
+ override fun getId() = metricId
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
new file mode 100644
index 0000000..c165632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
@@ -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 com.android.systemui.controls
+
+import android.service.controls.DeviceTypes.DeviceType
+
+import com.android.internal.logging.InstanceIdSequence
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.SysUiStatsLog
+
+import javax.inject.Inject
+
+/**
+ * Implementation for logging UI events related to controls
+ */
+@SysUISingleton
+class ControlsMetricsLoggerImpl @Inject constructor() : ControlsMetricsLogger {
+
+ companion object {
+ private const val INSTANCE_ID_MAX = 1 shl 13
+ }
+
+ private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+ private var instanceId = 0
+
+ override fun assignInstanceId() {
+ instanceId = instanceIdSequence.newInstanceId().id
+ }
+
+ /**
+ * {@see ControlsMetricsLogger#log}
+ */
+ override fun log(
+ eventId: Int,
+ @DeviceType deviceType: Int,
+ uid: Int,
+ isLocked: Boolean
+ ) {
+ SysUiStatsLog.write(
+ SysUiStatsLog.DEVICE_CONTROL_CHANGED,
+ eventId,
+ instanceId,
+ deviceType,
+ uid,
+ isLocked
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index ed625de..a165bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -18,6 +18,8 @@
import android.app.Activity
import android.content.pm.PackageManager
+import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsMetricsLoggerImpl
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -80,6 +82,9 @@
abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
@Binds
+ abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
+
+ @Binds
abstract fun provideControlActionCoordinator(
coordinator: ControlActionCoordinatorImpl
): ControlActionCoordinator
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 58a5981..477c220 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -18,7 +18,6 @@
import android.annotation.MainThread
import android.app.Dialog
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@@ -33,6 +32,7 @@
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsComponent
@@ -54,13 +54,15 @@
private val globalActionsComponent: GlobalActionsComponent,
private val taskViewFactory: Optional<TaskViewFactory>,
private val broadcastDispatcher: BroadcastDispatcher,
- private val lazyUiController: Lazy<ControlsUiController>
+ private val lazyUiController: Lazy<ControlsUiController>,
+ private val controlsMetricsLogger: ControlsMetricsLogger
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
private var pendingAction: Action? = null
private var actionsInProgress = mutableSetOf<String>()
-
+ private val isLocked: Boolean
+ get() = !keyguardStateController.isUnlocked()
override var activityContext: Context? = null
companion object {
@@ -73,6 +75,7 @@
}
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
+ controlsMetricsLogger.touch(cvh, isLocked)
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
cvh.action(BooleanAction(templateId, !isChecked))
@@ -80,6 +83,7 @@
}
override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
+ controlsMetricsLogger.touch(cvh, isLocked)
val blockable = cvh.usePanel()
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
@@ -100,12 +104,14 @@
}
override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
+ controlsMetricsLogger.drag(cvh, isLocked)
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.action(FloatAction(templateId, newValue))
}, false /* blockable */))
}
override fun longPress(cvh: ControlViewHolder) {
+ controlsMetricsLogger.longPress(cvh, isLocked)
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
// Long press snould only be called when there is valid control state, otherwise ignore
cvh.cws.control?.let {
@@ -116,7 +122,7 @@
}
override fun runPendingAction(controlId: String) {
- if (!keyguardStateController.isUnlocked()) return
+ if (isLocked) return
if (pendingAction?.controlId == controlId) {
pendingAction?.invoke()
pendingAction = null
@@ -141,28 +147,17 @@
@VisibleForTesting
fun bouncerOrRun(action: Action) {
if (keyguardStateController.isShowing()) {
- var closeDialog = !keyguardStateController.isUnlocked()
- if (closeDialog) {
+ if (isLocked) {
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
// pending actions will only run after the control state has been refreshed
pendingAction = action
}
-
+ val wasLocked = isLocked
activityStarter.dismissKeyguardThenExecute({
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
- if (closeDialog) {
- activityContext?.let {
- val i = Intent().apply {
- component = ComponentName(context, ControlsActivity::class.java)
- addFlags(
- Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false)
- }
- it.startActivity(i)
- } ?: run {
- globalActionsComponent.handleShowGlobalActionsMenu()
- }
+ if (wasLocked && activityContext == null) {
+ globalActionsComponent.handleShowGlobalActionsMenu()
} else {
action.invoke()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 9d92a40..3e02890 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -49,6 +49,7 @@
import com.android.internal.graphics.ColorUtils
import com.android.systemui.Interpolators
import com.android.systemui.R
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor
import kotlin.reflect.KClass
@@ -63,7 +64,9 @@
val controlsController: ControlsController,
val uiExecutor: DelayableExecutor,
val bgExecutor: DelayableExecutor,
- val controlActionCoordinator: ControlActionCoordinator
+ val controlActionCoordinator: ControlActionCoordinator,
+ val controlsMetricsLogger: ControlsMetricsLogger,
+ val uid: Int
) {
companion object {
@@ -141,7 +144,7 @@
status.setSelected(true)
}
- fun bindData(cws: ControlWithState) {
+ fun bindData(cws: ControlWithState, isLocked: Boolean) {
// If an interaction is in progress, the update may visually interfere with the action the
// action the user wants to make. Don't apply the update, and instead assume a new update
// will coming from when the user interaction is complete.
@@ -171,10 +174,16 @@
controlActionCoordinator.runPendingAction(cws.ci.controlId)
}
+ val wasLoading = isLoading
isLoading = false
behavior = bindBehavior(behavior,
findBehaviorClass(controlStatus, controlTemplate, deviceType))
updateContentDescription()
+
+ // Only log one event per control, at the moment we have determined that the control
+ // switched from the loading to done state
+ val doneLoading = wasLoading && !isLoading
+ if (doneLoading) controlsMetricsLogger.refreshEnd(this, isLocked)
}
fun actionResponse(@ControlAction.ResponseResult response: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 0f7f48f..d08882b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -43,6 +43,7 @@
import android.widget.Space
import android.widget.TextView
import com.android.systemui.R
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlInfo
@@ -58,6 +59,7 @@
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.ShadeController
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.text.Collator
@@ -77,7 +79,9 @@
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val shadeController: ShadeController,
- private val iconCache: CustomIconCache
+ private val iconCache: CustomIconCache,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val keyguardStateController: KeyguardStateController
) : ControlsUiController {
companion object {
@@ -133,7 +137,8 @@
return object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
val lastItems = serviceInfos.map {
- SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName)
+ val uid = it.serviceInfo.applicationInfo.uid
+ SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
}
uiExecutor.execute {
parent.removeAllViews()
@@ -282,8 +287,19 @@
private fun showControlsView(items: List<SelectionItem>) {
controlViewsById.clear()
- createListView()
- createDropDown(items)
+ val itemsByComponent = items.associateBy { it.componentName }
+ val itemsWithStructure = mutableListOf<SelectionItem>()
+ allStructures.mapNotNullTo(itemsWithStructure) {
+ itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
+ }
+ itemsWithStructure.sortWith(localeComparator)
+
+ val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
+
+ controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
+
+ createListView(selectionItem)
+ createDropDown(itemsWithStructure, selectionItem)
createMenu()
}
@@ -325,22 +341,13 @@
})
}
- private fun createDropDown(items: List<SelectionItem>) {
+ private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
items.forEach {
RenderInfo.registerComponentIcon(it.componentName, it.icon)
}
- val itemsByComponent = items.associateBy { it.componentName }
- val itemsWithStructure = mutableListOf<SelectionItem>()
- allStructures.mapNotNullTo(itemsWithStructure) {
- itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
- }
- itemsWithStructure.sortWith(localeComparator)
-
- val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
-
var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply {
- addAll(itemsWithStructure)
+ addAll(items)
}
/*
@@ -349,13 +356,13 @@
* a similar effect
*/
val spinner = parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply {
- setText(selectionItem.getTitle())
+ setText(selected.getTitle())
// override the default color on the dropdown drawable
(getBackground() as LayerDrawable).getDrawable(0)
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
}
- if (itemsWithStructure.size == 1) {
+ if (items.size == 1) {
spinner.setBackground(null)
return
}
@@ -388,7 +395,7 @@
})
}
- private fun createListView() {
+ private fun createListView(selected: SelectionItem) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.controls_with_favorites, parent, true)
@@ -421,9 +428,11 @@
controlsController.get(),
uiExecutor,
bgExecutor,
- controlActionCoordinator
+ controlActionCoordinator,
+ controlsMetricsLogger,
+ selected.uid
)
- cvh.bindData(it)
+ cvh.bindData(it, false /* isLocked, will be ignored on initial load */)
controlViewsById.put(key, cvh)
}
}
@@ -528,6 +537,7 @@
}
override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+ val isLocked = !keyguardStateController.isUnlocked()
controls.forEach { c ->
controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
@@ -536,8 +546,8 @@
val key = ControlKey(componentName, c.getControlId())
controlsById.put(key, cws)
- uiExecutor.execute {
- controlViewsById.get(key)?.bindData(cws)
+ controlViewsById.get(key)?.let {
+ uiExecutor.execute { it.bindData(cws, isLocked) }
}
}
}
@@ -566,7 +576,8 @@
val appName: CharSequence,
val structure: CharSequence,
val icon: Drawable,
- val componentName: ComponentName
+ val componentName: ComponentName,
+ val uid: Int
) {
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 25d02a6..2a7023a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -19,6 +19,7 @@
import android.content.BroadcastReceiver;
import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
+import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.screenshot.ActionProxyReceiver;
import com.android.systemui.screenshot.DeleteScreenshotReceiver;
import com.android.systemui.screenshot.SmartActionsReceiver;
@@ -69,4 +70,13 @@
public abstract BroadcastReceiver bindMediaOutputDialogReceiver(
MediaOutputDialogReceiver broadcastReceiver);
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(PeopleSpaceWidgetPinnedReceiver.class)
+ public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
+ PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index a2f96bb..1a72929 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -31,6 +31,7 @@
import android.os.Looper;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.LayoutInflater;
@@ -45,8 +46,11 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.accessibility.ModeSwitchesController;
import com.android.systemui.accessibility.SystemActions;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
@@ -212,6 +216,7 @@
MetricsLogger metricsLogger,
OverviewProxyService overviewProxyService,
NavigationModeController navigationModeController,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver,
StatusBarStateController statusBarStateController,
SysUiState sysUiFlagsContainer,
BroadcastDispatcher broadcastDispatcher,
@@ -236,6 +241,7 @@
metricsLogger,
overviewProxyService,
navigationModeController,
+ accessibilityButtonModeObserver,
statusBarStateController,
sysUiFlagsContainer,
broadcastDispatcher,
@@ -256,6 +262,16 @@
/** */
@Provides
@SysUISingleton
+ public AccessibilityFloatingMenuController provideAccessibilityFloatingMenuController(
+ Context context, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
+ return new AccessibilityFloatingMenuController(context, accessibilityButtonTargetsObserver,
+ accessibilityButtonModeObserver);
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
public ConfigurationController provideConfigurationController(Context context) {
return new ConfigurationControllerImpl(context);
}
@@ -324,6 +340,7 @@
/** */
@Provides
+ @SysUISingleton
public AlwaysOnDisplayPolicy provideAlwaysOnDisplayPolicy(Context context) {
return new AlwaysOnDisplayPolicy(context);
}
@@ -349,13 +366,6 @@
/** */
@Provides
@SysUISingleton
- public SystemActions providesSystemActions(Context context) {
- return new SystemActions(context);
- }
-
- /** */
- @Provides
- @SysUISingleton
public Choreographer providesChoreographer() {
return Choreographer.getInstance();
}
@@ -366,4 +376,11 @@
public ModeSwitchesController providesModeSwitchesController(Context context) {
return new ModeSwitchesController(context);
}
+
+ /** */
+ @Provides
+ @SysUISingleton
+ public QuickAccessWalletClient provideQuickAccessWalletClient(Context context) {
+ return QuickAccessWalletClient.create(context);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 8f79de5..ed3d5ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -34,7 +34,7 @@
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.RemoteTransitions;
+import com.android.wm.shell.transition.ShellTransitions;
import java.util.Optional;
@@ -87,7 +87,7 @@
Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
@BindsInstance
- Builder setTransitions(RemoteTransitions t);
+ Builder setTransitions(ShellTransitions t);
@BindsInstance
Builder setStartingSurface(Optional<StartingSurface> s);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 1b77d1c..bbd95b4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -33,7 +33,7 @@
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.RemoteTransitions;
+import com.android.wm.shell.transition.ShellTransitions;
import java.util.Optional;
@@ -98,7 +98,7 @@
Optional<TaskViewFactory> getTaskViewFactory();
@WMSingleton
- RemoteTransitions getTransitions();
+ ShellTransitions getTransitions();
@WMSingleton
Optional<StartingSurface> getStartingSurface();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
index 1a53c28..58c41d5 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
@@ -29,6 +29,7 @@
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenTextures;
import static android.opengl.GLES20.glTexParameteri;
+import static android.opengl.GLES20.glUniform1f;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glVertexAttribPointer;
@@ -52,6 +53,7 @@
private static final String A_POSITION = "aPosition";
private static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
private static final String U_TEXTURE = "uTexture";
+ private static final String U_EXPOSURE = "uExposure";
private static final int POSITION_COMPONENT_COUNT = 2;
private static final int TEXTURE_COMPONENT_COUNT = 2;
private static final int BYTES_PER_FLOAT = 4;
@@ -83,6 +85,7 @@
private int mAttrPosition;
private int mAttrTextureCoordinates;
private int mUniTexture;
+ private int mUniExposure;
private int mTextureId;
ImageGLWallpaper(ImageGLProgram program) {
@@ -125,6 +128,7 @@
private void setupUniforms() {
mUniTexture = mProgram.getUniformHandle(U_TEXTURE);
+ mUniExposure = mProgram.getUniformHandle(U_EXPOSURE);
}
void draw() {
@@ -171,6 +175,10 @@
glUniform1i(mUniTexture, 0);
}
+ void setExposureValue(float exposureValue) {
+ glUniform1f(mUniExposure, exposureValue);
+ }
+
/**
* Called to dump current state.
* @param prefix prefix.
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 01a353c..cdf88f3 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -46,6 +46,7 @@
private final ImageGLWallpaper mWallpaper;
private final Rect mSurfaceSize = new Rect();
private final WallpaperTexture mTexture;
+ private float mExposureValue;
public ImageWallpaperRenderer(Context context) {
final WallpaperManager wpm = context.getSystemService(WallpaperManager.class);
@@ -66,6 +67,13 @@
mTexture.use(c);
}
+ /**
+ * @hide
+ */
+ public void setExposureValue(float exposureValue) {
+ mExposureValue = exposureValue;
+ }
+
@Override
public boolean isWcgContent() {
return mTexture.isWcgContent();
@@ -94,6 +102,7 @@
public void onDrawFrame() {
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height());
+ mWallpaper.setExposureValue(mExposureValue);
mWallpaper.useTexture();
mWallpaper.draw();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 17f7ccf..97803c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -149,9 +149,14 @@
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) {
- // TODO(bc-unlock): Calls KeyguardViewMediator#setOccluded to update the state and
- // run animation.
try {
+ if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) {
+ mBinder.setOccluded(true /* isOccluded */, true /* animate */);
+ } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE) {
+ mBinder.setOccluded(false /* isOccluded */, true /* animate */);
+ }
+ // TODO(bc-unlock): Implement occlude/unocclude animation applied on apps,
+ // wallpapers and nonApps.
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 55c55b9..41c9dae 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -476,7 +476,6 @@
Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf<Int>()
// TODO: b/153736623 look into creating actions when this isn't a media style notification
- val packageContext: Context = sbn.getPackageContext(context)
if (actions != null) {
for ((index, action) in actions.withIndex()) {
if (action.getIcon() == null) {
@@ -499,7 +498,7 @@
null
}
val mediaActionIcon = if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
- Icon.createWithResource(packageContext, action.getIcon()!!.getResId())
+ Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
} else {
action.getIcon()
}.setTint(themeText)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 5536237..9d43e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -23,6 +23,7 @@
import static android.app.StatusBarManager.WindowType;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.containsType;
@@ -35,6 +36,7 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -85,6 +87,7 @@
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.IWindowManager;
import android.view.InsetsState.InternalInsetsType;
import android.view.KeyEvent;
@@ -111,6 +114,7 @@
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistManager;
@@ -156,7 +160,8 @@
* Contains logic for a navigation bar view.
*/
public class NavigationBar implements View.OnAttachStateChangeListener,
- Callbacks, NavigationModeController.ModeChangedListener, DisplayManager.DisplayListener {
+ Callbacks, NavigationModeController.ModeChangedListener,
+ AccessibilityButtonModeObserver.ModeChangedListener, DisplayManager.DisplayListener {
public static final String TAG = "NavigationBar";
private static final boolean DEBUG = false;
@@ -184,6 +189,7 @@
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
private final NavigationModeController mNavigationModeController;
+ private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
private final Optional<Pip> mPipOptional;
@@ -211,7 +217,9 @@
private Locale mLocale;
private int mLayoutDirection;
+ private boolean mAllowForceNavBarHandleOpaque;
private boolean mForceNavBarHandleOpaque;
+ private Optional<Long> mHomeButtonLongPressDurationMs;
private boolean mIsCurrentUserSetup;
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
@@ -222,6 +230,7 @@
private boolean mTransientShown;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
+ private int mA11yBtnMode;
private LightBarController mLightBarController;
private AutoHideController mAutoHideController;
@@ -267,7 +276,7 @@
/** Only for default display */
@Nullable
- private AssistHandleViewController mAssistHandlerViewController;
+ AssistHandleViewController mAssistHandlerViewController;
private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
@@ -333,13 +342,23 @@
// If the current user is not yet setup, then don't update any button alphas
return;
}
+ if (QuickStepContract.isLegacyMode(mNavBarMode)) {
+ // Don't allow the bar buttons to be affected by the alpha
+ return;
+ }
+
ButtonDispatcher buttonDispatcher = null;
boolean forceVisible = false;
- if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
- buttonDispatcher = mNavigationBarView.getBackButton();
- } else if (QuickStepContract.isGesturalMode(mNavBarMode)) {
- forceVisible = mForceNavBarHandleOpaque;
+ if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ // Disallow home handle animations when in gestural
+ animate = false;
+ forceVisible = mAllowForceNavBarHandleOpaque && mForceNavBarHandleOpaque;
buttonDispatcher = mNavigationBarView.getHomeHandle();
+ if (getBarTransitions() != null) {
+ getBarTransitions().setBackgroundOverrideAlpha(alpha);
+ }
+ } else if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
+ buttonDispatcher = mNavigationBarView.getBackButton();
}
if (buttonDispatcher != null) {
buttonDispatcher.setVisibility(
@@ -373,6 +392,14 @@
private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true);
+ private final Runnable mOnVariableDurationHomeLongClick = () -> {
+ if (onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView())) {
+ mNavigationBarView.getHomeButton().getCurrentView().performHapticFeedback(
+ HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
+ };
+
private final ContentObserver mAssistContentObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
@@ -394,6 +421,13 @@
mForceNavBarHandleOpaque = properties.getBoolean(
NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
}
+
+ if (properties.getKeyset().contains(HOME_BUTTON_LONG_PRESS_DURATION_MS)) {
+ mHomeButtonLongPressDurationMs = Optional.of(
+ properties.getLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 0)
+ ).filter(duration -> duration != 0);
+ reconfigureHomeLongClick();
+ }
}
};
@@ -414,6 +448,7 @@
MetricsLogger metricsLogger,
OverviewProxyService overviewProxyService,
NavigationModeController navigationModeController,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver,
StatusBarStateController statusBarStateController,
SysUiState sysUiFlagsContainer,
BroadcastDispatcher broadcastDispatcher,
@@ -441,7 +476,7 @@
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
- mNavBarMode = navigationModeController.addListener(this);
+ mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
mPipOptional = pipOptional;
@@ -451,6 +486,10 @@
mHandler = mainHandler;
mNavbarOverlayController = navbarOverlayController;
mUiEventLogger = uiEventLogger;
+
+ mNavBarMode = mNavigationModeController.addListener(this);
+ mAccessibilityButtonModeObserver.addListener(this);
+ mA11yBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
}
public View getView() {
@@ -506,16 +545,25 @@
// Respect the latest disabled-flags.
mCommandQueue.recomputeDisableFlags(mDisplayId, false);
+ mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean(
+ R.bool.allow_force_nav_bar_handle_opaque);
mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
NAV_BAR_HANDLE_FORCE_OPAQUE,
/* defaultValue = */ true);
+ mHomeButtonLongPressDurationMs = Optional.of(DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ HOME_BUTTON_LONG_PRESS_DURATION_MS,
+ /* defaultValue = */ 0
+ )).filter(duration -> duration != 0);
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
mDeviceProvisionedController.addCallback(mUserSetupListener);
+ setAccessibilityFloatingMenuModeIfNeeded();
+
return barView;
}
@@ -524,6 +572,7 @@
mContext.getSystemService(WindowManager.class).removeViewImmediate(
mNavigationBarView.getRootView());
mNavigationModeController.removeListener(this);
+ mAccessibilityButtonModeObserver.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
@@ -769,6 +818,22 @@
}
}
+ private void reconfigureHomeLongClick() {
+ if (mNavigationBarView == null
+ || mNavigationBarView.getHomeButton().getCurrentView() == null) {
+ return;
+ }
+ if (mHomeButtonLongPressDurationMs.isPresent()) {
+ mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(false);
+ mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
+ mNavigationBarView.getHomeButton().setOnLongClickListener(null);
+ } else {
+ mNavigationBarView.getHomeButton().getCurrentView().setLongClickable(true);
+ mNavigationBarView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(true);
+ mNavigationBarView.getHomeButton().setOnLongClickListener(this::onHomeLongClick);
+ }
+ }
+
private int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
@@ -779,6 +844,7 @@
pw.println("NavigationBar (displayId=" + mDisplayId + "):");
pw.println(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
pw.println(" mCurrentRotation=" + mCurrentRotation);
+ pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
if (mNavigationBarView != null) {
pw.println(" mNavigationBarWindowState="
@@ -1108,7 +1174,8 @@
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
- homeButton.setOnLongClickListener(this::onHomeLongClick);
+
+ reconfigureHomeLongClick();
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
@@ -1118,7 +1185,8 @@
updateScreenPinningGestures();
}
- private boolean onHomeTouch(View v, MotionEvent event) {
+ @VisibleForTesting
+ boolean onHomeTouch(View v, MotionEvent event) {
if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
return true;
}
@@ -1138,9 +1206,13 @@
return true;
}
}
+ mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
+ });
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
mStatusBarLazy.get().awakenDreams();
break;
}
@@ -1322,7 +1394,7 @@
return true;
}
- private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
+ void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
if (mNavigationBarView == null) {
return;
}
@@ -1336,6 +1408,13 @@
updateSystemUiStateFlags(a11yFlags);
}
+ private void setAccessibilityFloatingMenuModeIfNeeded() {
+ if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+ ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+ }
+ }
+
public void updateSystemUiStateFlags(int a11yFlags) {
if (a11yFlags < 0) {
a11yFlags = getA11yButtonState(null);
@@ -1391,6 +1470,12 @@
outFeedbackEnabled[0] = feedbackEnabled;
}
+ // If accessibility button is floating menu mode, click and long click state should be
+ // disabled.
+ if (mA11yBtnMode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
+ return 0;
+ }
+
return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
| (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
}
@@ -1468,13 +1553,26 @@
@Override
public void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
+ if (!QuickStepContract.isGesturalMode(mode)) {
+ // Reset the override alpha
+ if (getBarTransitions() != null) {
+ getBarTransitions().setBackgroundOverrideAlpha(1f);
+ }
+ }
updateScreenPinningGestures();
+ setAccessibilityFloatingMenuModeIfNeeded();
if (!canShowSecondaryHandle()) {
resetSecondaryHandle();
}
}
+ @Override
+ public void onAccessibilityButtonModeChanged(int mode) {
+ mA11yBtnMode = mode;
+ updateAccessibilityServicesState(mAccessibilityManager);
+ }
+
public void disableAnimationsDuringHide(long delay) {
mNavigationBarView.setLayoutTransitionsEnabled(false);
mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
@@ -1497,7 +1595,10 @@
}
public NavigationBarTransitions getBarTransitions() {
- return mNavigationBarView.getBarTransitions();
+ if (mNavigationBarView != null) {
+ return mNavigationBarView.getBarTransitions();
+ }
+ return null;
}
public void finishBarAnimations() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 0bfd065..50efa8d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -43,6 +43,7 @@
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dumpable;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistManager;
@@ -91,6 +92,7 @@
private final MetricsLogger mMetricsLogger;
private final OverviewProxyService mOverviewProxyService;
private final NavigationModeController mNavigationModeController;
+ private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
private final StatusBarStateController mStatusBarStateController;
private final SysUiState mSysUiFlagsContainer;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -127,6 +129,7 @@
MetricsLogger metricsLogger,
OverviewProxyService overviewProxyService,
NavigationModeController navigationModeController,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver,
StatusBarStateController statusBarStateController,
SysUiState sysUiFlagsContainer,
BroadcastDispatcher broadcastDispatcher,
@@ -151,6 +154,7 @@
mMetricsLogger = metricsLogger;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
+ mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mStatusBarStateController = statusBarStateController;
mSysUiFlagsContainer = sysUiFlagsContainer;
mBroadcastDispatcher = broadcastDispatcher;
@@ -289,6 +293,7 @@
mMetricsLogger,
mOverviewProxyService,
mNavigationModeController,
+ mAccessibilityButtonModeObserver,
mStatusBarStateController,
mSysUiFlagsContainer,
mBroadcastDispatcher,
@@ -324,7 +329,7 @@
});
}
- private void removeNavigationBar(int displayId) {
+ void removeNavigationBar(int displayId) {
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
navBar.setAutoHideController(/* autoHideController */ null);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 2707460..7342f91 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -40,9 +40,9 @@
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.buttons.ReverseLinearLayout;
+import com.android.systemui.navigationbar.buttons.ReverseLinearLayout.ReverseRelativeLayout;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.navigationbar.buttons.ReverseLinearLayout.ReverseRelativeLayout;
import java.io.PrintWriter;
import java.util.Objects;
@@ -361,7 +361,7 @@
return v;
}
- private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
+ View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index 61e1d61..fbc7c92 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -36,6 +36,7 @@
import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -135,6 +136,10 @@
mBarBackground.setFrame(frame);
}
+ void setBackgroundOverrideAlpha(float alpha) {
+ mBarBackground.setOverrideAlpha(alpha);
+ }
+
@Override
protected boolean isLightsOut(int mode) {
return super.isLightsOut(mode) || (mAllowAutoDimWallpaperNotVisible && mAutoDim
@@ -230,4 +235,17 @@
public void removeDarkIntensityListener(DarkIntensityListener listener) {
mDarkIntensityListeners.remove(listener);
}
+
+ public void dump(PrintWriter pw) {
+ pw.println("NavigationBarTransitions:");
+ pw.println(" mMode: " + getMode());
+ pw.println(" mAlwaysOpaque: " + isAlwaysOpaque());
+ pw.println(" mAllowAutoDimWallpaperNotVisible: " + mAllowAutoDimWallpaperNotVisible);
+ pw.println(" mWallpaperVisible: " + mWallpaperVisible);
+ pw.println(" mLightsOut: " + mLightsOut);
+ pw.println(" mAutoDim: " + mAutoDim);
+ pw.println(" bg overrideAlpha: " + mBarBackground.getOverrideAlpha());
+ pw.println(" bg color: " + mBarBackground.getColor());
+ pw.println(" bg frame: " + mBarBackground.getFrame());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 148c665..091b17d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1158,6 +1158,8 @@
int frameHeight = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
+ } else {
+ mBarTransitions.setBackgroundFrame(null);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -1331,6 +1333,7 @@
if (mNavigationInflaterView != null) {
mNavigationInflaterView.dump(pw);
}
+ mBarTransitions.dump(pw);
mContextualButtonGroup.dump(pw);
mRecentsOnboarding.dump(pw);
mRegionSamplingHelper.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
index e7458a3..59329d1 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
@@ -16,9 +16,7 @@
package com.android.systemui.people;
-import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
-import android.app.people.PeopleSpaceTile;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -33,13 +31,16 @@
import android.util.Log;
import android.widget.RemoteViews;
+import com.android.systemui.Dependency;
import com.android.systemui.shared.system.PeopleProviderUtils;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
/** API that returns a People Tile preview. */
public class PeopleProvider extends ContentProvider {
LauncherApps mLauncherApps;
IPeopleManager mPeopleManager;
+ NotificationEntryManager mNotificationEntryManager;
private static final String TAG = "PeopleProvider";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
@@ -57,18 +58,6 @@
throw new IllegalArgumentException("Invalid method");
}
- // If services are not set as mocks in tests, fetch them now.
- mPeopleManager = mPeopleManager != null ? mPeopleManager
- : IPeopleManager.Stub.asInterface(
- ServiceManager.getService(Context.PEOPLE_SERVICE));
- mLauncherApps = mLauncherApps != null ? mLauncherApps
- : getContext().getSystemService(LauncherApps.class);
-
- if (mPeopleManager == null || mLauncherApps == null) {
- Log.w(TAG, "Null system managers");
- return null;
- }
-
if (extras == null) {
Log.w(TAG, "Extras can't be null");
throw new IllegalArgumentException("Extras can't be null");
@@ -94,23 +83,21 @@
throw new IllegalArgumentException("Null user handle");
}
- ConversationChannel channel;
- try {
- channel = mPeopleManager.getConversation(
- packageName, userHandle.getIdentifier(), shortcutId);
- } catch (Exception e) {
- Log.w(TAG, "Exception getting tiles: " + e);
+ // If services are not set as mocks in tests, fetch them now.
+ mPeopleManager = mPeopleManager != null ? mPeopleManager
+ : IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mLauncherApps = mLauncherApps != null ? mLauncherApps
+ : getContext().getSystemService(LauncherApps.class);
+ mNotificationEntryManager = mNotificationEntryManager != null ? mNotificationEntryManager
+ : Dependency.get(NotificationEntryManager.class);
+
+ RemoteViews view = PeopleSpaceUtils.getPreview(getContext(), mPeopleManager, mLauncherApps,
+ mNotificationEntryManager, shortcutId, userHandle, packageName);
+ if (view == null) {
+ if (DEBUG) Log.d(TAG, "No preview available for shortcutId: " + shortcutId);
return null;
}
- PeopleSpaceTile tile = PeopleSpaceUtils.getTile(channel, mLauncherApps);
-
- if (tile == null) {
- if (DEBUG) Log.i(TAG, "No tile was returned");
- return null;
- }
-
- if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
- RemoteViews view = PeopleSpaceUtils.createRemoteViews(getContext(), tile, 0);
final Bundle bundle = new Bundle();
bundle.putParcelable(PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS, view);
return bundle;
@@ -118,8 +105,8 @@
private boolean doesCallerHavePermission() {
return getContext().checkPermission(
- PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
- Binder.getCallingPid(), Binder.getCallingUid())
+ PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
+ Binder.getCallingPid(), Binder.getCallingUid())
== PackageManager.PERMISSION_GRANTED;
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 378e49d..09461c3 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -35,7 +35,6 @@
import com.android.systemui.R;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
-import com.android.systemui.people.widget.PeopleTileKey;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import java.util.List;
@@ -62,9 +61,12 @@
private boolean mShowSingleConversation;
@Inject
- public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager) {
+ public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager,
+ PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
super();
mNotificationEntryManager = notificationEntryManager;
+ mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
+
}
@Override
@@ -78,7 +80,6 @@
mPackageManager = getPackageManager();
mPeopleManager = IPeopleManager.Stub.asInterface(
ServiceManager.getService(Context.PEOPLE_SERVICE));
- mPeopleSpaceWidgetManager = new PeopleSpaceWidgetManager(mContext);
mLauncherApps = mContext.getSystemService(LauncherApps.class);
setTileViewsWithPriorityConversations();
mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID,
@@ -139,9 +140,7 @@
+ mAppWidgetId);
}
}
- PeopleTileKey key = new PeopleTileKey(
- tile.getId(), tile.getUserHandle().getIdentifier(), tile.getPackageName());
- mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, key);
+ mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, tile);
finishActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index aa45178..5163879 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -26,6 +26,10 @@
import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
import static android.app.people.ConversationStatus.ACTIVITY_UPCOMING_BIRTHDAY;
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;
@@ -41,6 +45,7 @@
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;
@@ -52,13 +57,18 @@
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcelable;
+import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.ContactsContract;
import android.provider.Settings;
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;
@@ -84,7 +94,6 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -110,11 +119,17 @@
public static final String SHORTCUT_ID = "shortcut_id";
public static final String EMPTY_STRING = "";
- public static final int INVALID_WIDGET_ID = -1;
public static final int INVALID_USER_ID = -1;
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("[?][?]+");
@@ -193,91 +208,6 @@
return tiles;
}
- /**
- * Updates {@code appWidgetIds} with their associated conversation stored, handling a
- * notification being posted or removed.
- */
- public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds,
- AppWidgetManager appWidgetManager, IPeopleManager peopleManager) {
- Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>();
- for (int appWidgetId : appWidgetIds) {
- PeopleSpaceTile tile = getPeopleSpaceTile(
- context, appWidgetId, appWidgetManager, peopleManager);
- if (tile == null) {
- if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID");
- //TODO: Delete app widget id when crash is fixed (b/172932636)
- continue;
- }
- updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, tile);
- widgetIdToTile.put(appWidgetId, tile);
- }
- getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds);
- }
-
- /**
- * Returns a {@link PeopleSpaceTile} based on the {@code appWidgetId}. If the PeopleSpaceTile
- * isn't cached, store it in AppWidgetOptions.
- */
- @Nullable
- public static PeopleSpaceTile getPeopleSpaceTile(Context context, int appWidgetId,
- AppWidgetManager appWidgetManager, IPeopleManager peopleManager) {
- // First, check if tile is cached in AppWidgetOptions.
- PeopleSpaceTile tile = AppWidgetOptionsHelper.getPeopleTile(appWidgetManager, appWidgetId);
- if (tile != null) {
- if (DEBUG) Log.d(TAG, "People Tile is cached for widget: " + appWidgetId);
- return tile;
- }
-
- // If not, we get the PeopleTileKey from SharedPreferences, retrieve the Conversation from
- // persisted storage, and cache it in AppWidgetOptions.
- SharedPreferences widgetSp = context.getSharedPreferences(
- String.valueOf(appWidgetId),
- Context.MODE_PRIVATE);
- PeopleTileKey sharedPreferencesKey = new PeopleTileKey(
- widgetSp.getString(SHORTCUT_ID, EMPTY_STRING),
- widgetSp.getInt(USER_ID, INVALID_USER_ID),
- widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
-
- if (!sharedPreferencesKey.isValid()) {
- Log.e(TAG, "Cannot find shortcut info for widgetId: " + appWidgetId);
- return null;
- }
-
- if (DEBUG) Log.d(TAG, "PeopleTile key is present in sharedPreferences: " + appWidgetId);
- // If tile is null, we need to retrieve from persisted storage.
- return getPeopleTileFromPersistentStorage(context, sharedPreferencesKey, peopleManager);
- }
-
- /**
- * Returns a {@link PeopleSpaceTile} based on {@link ConversationChannel} returned by
- * {@link IPeopleManager}.
- */
- public static PeopleSpaceTile getPeopleTileFromPersistentStorage(Context context,
- PeopleTileKey peopleTileKey, IPeopleManager peopleManager) {
- try {
- if (DEBUG) Log.d(TAG, "Retrieving Tile from storage: " + peopleTileKey.toString());
- LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
- if (launcherApps == null) {
- Log.d(TAG, "LauncherApps is null");
- return null;
- }
-
- ConversationChannel channel = peopleManager.getConversation(
- peopleTileKey.getPackageName(),
- peopleTileKey.getUserId(),
- peopleTileKey.getShortcutId());
- if (channel == null) {
- Log.d(TAG, "Could not retrieve conversation from storage");
- return null;
- }
-
- return new PeopleSpaceTile.Builder(channel, launcherApps).build();
- } catch (Exception e) {
- Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
- return null;
- }
- }
-
/** Returns stored widgets for the conversation specified. */
public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) {
if (!key.isValid()) {
@@ -332,6 +262,14 @@
widgetEditor.apply();
}
+ /** Augments a single {@link PeopleSpaceTile} with notification content, if one is present. */
+ public static PeopleSpaceTile augmentSingleTileFromVisibleNotifications(Context context,
+ PeopleSpaceTile tile, NotificationEntryManager notificationEntryManager) {
+ List<PeopleSpaceTile> augmentedTile = augmentTilesFromVisibleNotifications(
+ context, Arrays.asList(tile), notificationEntryManager);
+ return augmentedTile.get(0);
+ }
+
static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(Context context,
List<PeopleSpaceTile> tiles, NotificationEntryManager notificationEntryManager) {
if (notificationEntryManager == null) {
@@ -343,7 +281,10 @@
.stream()
.filter(entry -> entry.getRanking() != null
&& entry.getRanking().getConversationShortcutInfo() != null)
- .collect(Collectors.toMap(PeopleTileKey::new, e -> e));
+ .collect(Collectors.toMap(PeopleTileKey::new, e -> e,
+ // Handle duplicate keys to avoid crashes.
+ (e1, e2) -> e1.getSbn().getNotification().when
+ > e2.getSbn().getNotification().when ? e1 : e2));
if (DEBUG) {
Log.d(TAG, "Number of visible notifications:" + visibleNotifications.size());
}
@@ -401,9 +342,11 @@
/** Creates a {@link RemoteViews} for {@code tile}. */
public static RemoteViews createRemoteViews(Context context,
- PeopleSpaceTile tile, int appWidgetId) {
- RemoteViews viewsForTile = getViewForTile(context, tile);
- RemoteViews views = setCommonRemoteViewsFields(context, viewsForTile, tile);
+ 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);
}
@@ -411,15 +354,16 @@
* 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.
*/
- private static RemoteViews getViewForTile(Context context, PeopleSpaceTile tile) {
+ 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);
+ return createMissedCallRemoteViews(context, tile, layoutSize);
}
if (tile.getNotificationKey() != null) {
if (DEBUG) Log.d(TAG, "Create notification view");
- return createNotificationRemoteViews(context, tile);
+ return createNotificationRemoteViews(context, tile, layoutSize);
}
// TODO: Add sorting when we expose timestamp of statuses.
@@ -429,7 +373,7 @@
ConversationStatus birthdayStatus = getBirthdayStatus(tile, statusesForEntireView);
if (birthdayStatus != null) {
if (DEBUG) Log.d(TAG, "Create birthday view");
- return createStatusRemoteViews(context, birthdayStatus);
+ return createStatusRemoteViews(context, birthdayStatus, layoutSize);
}
if (!statusesForEntireView.isEmpty()) {
@@ -437,10 +381,48 @@
Log.d(TAG,
"Create status view for: " + statusesForEntireView.get(0).getActivity());
}
- return createStatusRemoteViews(context, statusesForEntireView.get(0));
+ return createStatusRemoteViews(context, statusesForEntireView.get(0), layoutSize);
}
- return createLastInteractionRemoteViews(context, tile);
+ 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
@@ -478,14 +460,22 @@
}
}
- private static RemoteViews createStatusRemoteViews(Context context, ConversationStatus status) {
- RemoteViews views = new RemoteViews(
- context.getPackageName(), R.layout.people_space_small_avatar_tile);
+ 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.setTextViewText(R.id.status, statusText);
+ 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);
@@ -494,6 +484,8 @@
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;
}
@@ -519,7 +511,7 @@
}
private static RemoteViews setCommonRemoteViewsFields(Context context, RemoteViews views,
- PeopleSpaceTile tile) {
+ PeopleSpaceTile tile, int maxAvatarSize) {
try {
boolean isAvailable =
tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
@@ -532,27 +524,20 @@
boolean hasNewStory =
tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
c -> c.getActivity() == ACTIVITY_NEW_STORY);
- if (hasNewStory) {
- views.setViewVisibility(R.id.person_icon_with_story, View.VISIBLE);
- views.setViewVisibility(R.id.person_icon_only, View.GONE);
- views.setImageViewIcon(R.id.person_icon_inside_ring, tile.getUserIcon());
- } else {
- views.setViewVisibility(R.id.person_icon_with_story, View.GONE);
- views.setViewVisibility(R.id.person_icon_only, View.VISIBLE);
- views.setImageViewIcon(R.id.person_icon_only, tile.getUserIcon());
- }
-
views.setTextViewText(R.id.name, tile.getUserName().toString());
- views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
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);
- views.setImageViewBitmap(
- R.id.package_icon,
- PeopleSpaceUtils.convertDrawableToBitmap(
- context.getPackageManager().getApplicationIcon(
- tile.getPackageName())
- )
- );
return views;
} catch (Exception e) {
Log.e(TAG, "Failed to set common fields: " + e);
@@ -585,47 +570,77 @@
} catch (Exception e) {
Log.e(TAG, "Failed to add launch intents: " + e);
}
+
return views;
}
private static RemoteViews createMissedCallRemoteViews(Context context,
- PeopleSpaceTile tile) {
- RemoteViews views = new RemoteViews(
- context.getPackageName(), R.layout.people_space_small_avatar_tile);
- views.setTextViewText(R.id.status, tile.getNotificationContent());
- views.setImageViewResource(R.id.status_defined_icon, R.drawable.ic_phone_missed);
+ 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) {
- RemoteViews views = new RemoteViews(
- context.getPackageName(), R.layout.people_space_notification_content_tile);
+ 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.content, View.GONE);
+ 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.content, content);
- views.setViewVisibility(R.id.content, View.VISIBLE);
+ 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.setTextViewText(R.id.subtext, PeopleSpaceUtils.getLastInteractionString(
- context, tile.getLastInteractionTimestamp()));
+ views.setViewVisibility(R.id.subtext, View.GONE);
return views;
}
private static RemoteViews createLastInteractionRemoteViews(Context context,
- PeopleSpaceTile tile) {
- RemoteViews views = new RemoteViews(
- context.getPackageName(), R.layout.people_space_large_avatar_tile);
+ 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);
@@ -822,7 +837,7 @@
}
/** Calls to retrieve birthdays on a background thread. */
- private static void getBirthdaysOnBackgroundThread(Context context,
+ public static void getBirthdaysOnBackgroundThread(Context context,
AppWidgetManager appWidgetManager,
Map<Integer, PeopleSpaceTile> peopleSpaceTiles, int[] appWidgetIds) {
ThreadUtils.postOnBackgroundThread(
@@ -900,18 +915,23 @@
removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
}
- /** Updates tile in app widget options and the current view. */
- public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
- Context context, int appWidgetId, PeopleSpaceTile tile) {
- AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile);
-
+ /** Updates the current widget view with provided {@link PeopleSpaceTile}. */
+ 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);
+ RemoteViews views = createRemoteViews(context, tile, appWidgetId, options);
// Tell the AppWidgetManager to perform an update on the current app widget.
appWidgetManager.updateAppWidget(appWidgetId, views);
}
+ /** Updates tile in app widget options and the current view. */
+ public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
+ Context context, int appWidgetId, PeopleSpaceTile tile) {
+ Bundle options = AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile);
+ updateAppWidgetViews(appWidgetManager, context, appWidgetId, tile, options);
+ }
+
/**
* Returns lookup keys for all contacts with a birthday today.
*
@@ -953,6 +973,43 @@
return lookupKeysWithBirthdaysToday;
}
+ /**
+ * Returns a {@link RemoteViews} preview of a Conversation's People Tile. Returns null if one
+ * is not available.
+ */
+ public static RemoteViews getPreview(Context context, IPeopleManager peopleManager,
+ LauncherApps launcherApps, NotificationEntryManager notificationEntryManager,
+ String shortcutId, UserHandle userHandle, String packageName) {
+ peopleManager = (peopleManager != null) ? peopleManager : IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ launcherApps = (launcherApps != null) ? launcherApps
+ : context.getSystemService(LauncherApps.class);
+ if (peopleManager == null || launcherApps == null) {
+ return null;
+ }
+
+ ConversationChannel channel;
+ try {
+ channel = peopleManager.getConversation(
+ packageName, userHandle.getIdentifier(), shortcutId);
+ } catch (Exception e) {
+ Log.w(TAG, "Exception getting tiles: " + e);
+ return null;
+ }
+ PeopleSpaceTile tile = PeopleSpaceUtils.getTile(channel, launcherApps);
+
+ if (tile == null) {
+ if (DEBUG) Log.i(TAG, "No tile was returned");
+ return null;
+ }
+ PeopleSpaceTile augmentedTile = augmentSingleTileFromVisibleNotifications(
+ context, tile, notificationEntryManager);
+
+ if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
+ Bundle bundle = new Bundle();
+ return PeopleSpaceUtils.createRemoteViews(context, augmentedTile, 0, bundle);
+ }
+
/** Returns the userId associated with a {@link PeopleSpaceTile} */
public static int getUserId(PeopleSpaceTile tile) {
return tile.getUserHandle().getIdentifier();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
new file mode 100644
index 0000000..145fee5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
@@ -0,0 +1,215 @@
+/*
+ * 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 android.annotation.ColorInt;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+import android.util.TypedValue;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.systemui.R;
+
+class PeopleStoryIconFactory extends BaseIconFactory {
+
+ private static final int PADDING = 2;
+ private static final int RING_WIDTH = 2;
+ private static final int MAX_BADGE_SIZE = 36;
+
+ final PackageManager mPackageManager;
+ final IconDrawableFactory mIconDrawableFactory;
+ private int mImportantConversationColor;
+ private int mAccentColor;
+ private float mDensity;
+ private float mIconSize;
+
+ PeopleStoryIconFactory(Context context, PackageManager pm,
+ IconDrawableFactory iconDrawableFactory, int iconSizeDp) {
+ super(context, context.getResources().getConfiguration().densityDpi,
+ (int) (iconSizeDp * context.getResources().getDisplayMetrics().density));
+ mDensity = context.getResources().getDisplayMetrics().density;
+ mIconSize = mDensity * iconSizeDp;
+ mPackageManager = pm;
+ mIconDrawableFactory = iconDrawableFactory;
+ mImportantConversationColor = context.getColor(R.color.important_conversation);
+ TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ mAccentColor = context.getColor(typedValue.resourceId);
+ }
+
+
+ /**
+ * Gets the {@link Drawable} that represents the app icon, badged with the work profile icon
+ * if appropriate.
+ */
+ private Drawable getAppBadge(String packageName, int userId) {
+ Drawable badge = null;
+ try {
+ final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId);
+ badge = mIconDrawableFactory.getBadgedIcon(appInfo, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ badge = mPackageManager.getDefaultActivityIcon();
+ }
+ return badge;
+ }
+
+ /**
+ * Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged
+ * with the launcher icon of the app specified by packageName.
+ */
+ public Drawable getPeopleTileDrawable(Drawable headDrawable, String packageName, int userId,
+ boolean important, boolean newStory) {
+ return new PeopleStoryIconDrawable(headDrawable, getAppBadge(packageName, userId),
+ mIconBitmapSize, mImportantConversationColor, important, mIconSize, mDensity,
+ mAccentColor, newStory);
+ }
+
+ /**
+ * Custom drawable which overlays a badge drawable on a head icon (conversation/person avatar),
+ * with decorations indicating Important conversations and having a New Story.
+ */
+ public static class PeopleStoryIconDrawable extends Drawable {
+ private float mFullIconSize;
+ private Drawable mAvatar;
+ private Drawable mBadgeIcon;
+ private int mIconSize;
+ private Paint mPriorityRingPaint;
+ private boolean mShowImportantRing;
+ private boolean mShowStoryRing;
+ private Paint mStoryPaint;
+ private float mDensity;
+
+ PeopleStoryIconDrawable(Drawable avatar,
+ Drawable badgeIcon,
+ int iconSize,
+ @ColorInt int ringColor,
+ boolean showImportantRing, float fullIconSize, float density,
+ @ColorInt int accentColor, boolean showStoryRing) {
+ mAvatar = avatar;
+ mBadgeIcon = badgeIcon;
+ mIconSize = iconSize;
+ mShowImportantRing = showImportantRing;
+ mPriorityRingPaint = new Paint();
+ mPriorityRingPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mPriorityRingPaint.setColor(ringColor);
+ mShowStoryRing = showStoryRing;
+ mStoryPaint = new Paint();
+ mStoryPaint.setStyle(Paint.Style.STROKE);
+ mStoryPaint.setColor(accentColor);
+ mFullIconSize = fullIconSize;
+ mDensity = density;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIconSize;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIconSize;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+ final int minBound = Math.min(bounds.height(), bounds.width());
+ // Scale head icon and app icon to our canvas.
+ float scale = minBound / mFullIconSize;
+
+ int paddingInDp = (int) (PADDING * mDensity);
+ int ringStrokeWidth = (int) (RING_WIDTH * mDensity);
+ mPriorityRingPaint.setStrokeWidth(ringStrokeWidth);
+ mStoryPaint.setStrokeWidth(ringStrokeWidth);
+
+ int scaledFullIconSize = (int) (mFullIconSize * scale);
+ int avatarSize = scaledFullIconSize - (paddingInDp * 2);
+ if (mAvatar != null) {
+ int leftAndTopPadding = paddingInDp;
+ int rightAndBottomPadding = avatarSize + paddingInDp;
+ if (mShowStoryRing) {
+ int headCenter = scaledFullIconSize / 2;
+ canvas.drawCircle(headCenter, headCenter,
+ getRadius(avatarSize, ringStrokeWidth),
+ mStoryPaint);
+ leftAndTopPadding += (ringStrokeWidth + paddingInDp);
+ rightAndBottomPadding -= (ringStrokeWidth + paddingInDp);
+ }
+ mAvatar.setBounds(leftAndTopPadding,
+ leftAndTopPadding,
+ rightAndBottomPadding,
+ rightAndBottomPadding);
+ mAvatar.draw(canvas);
+ } else {
+ Log.w("PeopleStoryIconFactory", "Null avatar icon");
+ }
+
+ // Determine badge size from either the size relative to the head icon, or max size.
+ int maxBadgeSize = (int) (MAX_BADGE_SIZE * mDensity);
+ int badgeSizeRelativeToHead = (int) (avatarSize / 2.4);
+ int badgeSize = Math.min(maxBadgeSize, badgeSizeRelativeToHead);
+ if (mBadgeIcon != null) {
+ int leftAndTopPadding = scaledFullIconSize - badgeSize;
+ int rightAndBottomPadding = scaledFullIconSize;
+ if (mShowImportantRing) {
+ int badgeCenter = leftAndTopPadding + (badgeSize / 2);
+ canvas.drawCircle(badgeCenter, badgeCenter,
+ getRadius(badgeSize, ringStrokeWidth),
+ mPriorityRingPaint);
+ leftAndTopPadding += ringStrokeWidth;
+ rightAndBottomPadding -= ringStrokeWidth;
+ }
+ mBadgeIcon.setBounds(
+ leftAndTopPadding,
+ leftAndTopPadding,
+ rightAndBottomPadding,
+ rightAndBottomPadding);
+ mBadgeIcon.draw(canvas);
+ } else {
+ Log.w("PeopleStoryIconFactory", "Null badge icon");
+ }
+ }
+
+ private int getRadius(int circleWidth, int circleStrokeWidth) {
+ return (circleWidth - circleStrokeWidth) / 2;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // unimplemented
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ // unimplemented
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
index df08ee4..7254eec 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/AppWidgetOptionsHelper.java
@@ -37,15 +37,16 @@
public static final String OPTIONS_PEOPLE_TILE = "options_people_tile";
/** Sets {@link PeopleSpaceTile} in AppWidgetOptions. */
- public static void setPeopleTile(AppWidgetManager appWidgetManager, int appWidgetId,
+ public static Bundle setPeopleTile(AppWidgetManager appWidgetManager, int appWidgetId,
PeopleSpaceTile tile) {
+ Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
if (tile == null) {
if (DEBUG) Log.d(TAG, "Requested to store null tile");
- return;
+ return options;
}
- Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
options.putParcelable(OPTIONS_PEOPLE_TILE, tile);
appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
+ return options;
}
/** Gets {@link PeopleSpaceTile} from AppWidgetOptions. */
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index 48f6184..c01a52d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -27,6 +27,7 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.statusbar.IStatusBarService;
@@ -34,6 +35,9 @@
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.wmshell.BubblesManager;
+
+import java.util.Optional;
import javax.inject.Inject;
@@ -43,16 +47,23 @@
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private NotificationEntryManager mNotificationEntryManager;
+ private final Optional<BubblesManager> mBubblesManagerOptional;
+ private boolean mIsForTesting;
+ private IStatusBarService mIStatusBarService;
@Inject
- public LaunchConversationActivity(NotificationEntryManager notificationEntryManager) {
+ public LaunchConversationActivity(NotificationEntryManager notificationEntryManager,
+ Optional<BubblesManager> bubblesManagerOptional) {
super();
mNotificationEntryManager = notificationEntryManager;
+ mBubblesManagerOptional = bubblesManagerOptional;
}
@Override
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ if (!mIsForTesting) {
+ super.onCreate(savedInstanceState);
+ }
if (DEBUG) Log.d(TAG, "onCreate called");
Intent intent = getIntent();
@@ -63,21 +74,30 @@
String notificationKey =
intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY);
- if (tileId != null && !tileId.isEmpty()) {
+ if (!TextUtils.isEmpty(tileId)) {
if (DEBUG) {
Log.d(TAG, "Launching conversation with shortcutInfo id " + tileId);
}
mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_CLICKED);
try {
+ NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ notificationKey);
+ if (entry != null && entry.canBubble() && mBubblesManagerOptional.isPresent()) {
+ if (DEBUG) Log.d(TAG, "Open bubble for conversation");
+ mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
+ // Just opt-out and don't cancel the notification for bubbles.
+ return;
+ }
+
+ if (mIStatusBarService == null) {
+ mIStatusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+ clearNotificationIfPresent(notificationKey, packageName, userHandle);
LauncherApps launcherApps =
getApplicationContext().getSystemService(LauncherApps.class);
launcherApps.startShortcut(
packageName, tileId, null, null, userHandle);
-
- IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- clearNotificationIfPresent(
- statusBarService, notificationKey, packageName, userHandle);
} catch (Exception e) {
Log.e(TAG, "Exception:" + e);
}
@@ -87,15 +107,14 @@
finish();
}
- void clearNotificationIfPresent(IStatusBarService statusBarService,
- String notifKey, String packageName, UserHandle userHandle) {
+ void clearNotificationIfPresent(String notifKey, String packageName, UserHandle userHandle) {
if (TextUtils.isEmpty(notifKey)) {
if (DEBUG) Log.d(TAG, "Skipping clear notification: notification key is empty");
return;
}
try {
- if (statusBarService == null || mNotificationEntryManager == null) {
+ if (mIStatusBarService == null || mNotificationEntryManager == null) {
if (DEBUG) {
Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
}
@@ -117,7 +136,7 @@
rank, count, true);
if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
- statusBarService.onNotificationClear(
+ mIStatusBarService.onNotificationClear(
packageName, userHandle.getIdentifier(), notifKey,
NotificationStats.DISMISSAL_OTHER,
NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
@@ -125,4 +144,10 @@
Log.e(TAG, "Exception cancelling notification:" + e);
}
}
+
+ @VisibleForTesting
+ void setIsForTesting(boolean isForTesting, IStatusBarService statusBarService) {
+ mIsForTesting = isForTesting;
+ mIStatusBarService = statusBarService;
+ }
}
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 7da9a80..fa7b7b3 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -22,11 +22,13 @@
import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotification;
-import static com.android.systemui.people.PeopleSpaceUtils.getPeopleSpaceTile;
import static com.android.systemui.people.PeopleSpaceUtils.getStoredWidgetIds;
import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView;
+import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews;
+import android.annotation.Nullable;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
import android.app.Person;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
@@ -47,14 +49,17 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import android.widget.RemoteViews;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.systemui.Dependency;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import java.util.Collections;
import java.util.HashMap;
@@ -78,6 +83,7 @@
private IPeopleManager mIPeopleManager;
private SharedPreferences mSharedPrefs;
private PeopleManager mPeopleManager;
+ private NotificationEntryManager mNotificationEntryManager;
public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
@GuardedBy("mLock")
public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener>
@@ -93,6 +99,7 @@
mLauncherApps = context.getSystemService(LauncherApps.class);
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPeopleManager = mContext.getSystemService(PeopleManager.class);
+ mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
}
/**
@@ -101,11 +108,13 @@
@VisibleForTesting
protected void setAppWidgetManager(
AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
- PeopleManager peopleManager, LauncherApps launcherApps) {
+ PeopleManager peopleManager, LauncherApps launcherApps,
+ NotificationEntryManager notificationEntryManager) {
mAppWidgetManager = appWidgetManager;
mIPeopleManager = iPeopleManager;
mPeopleManager = peopleManager;
mLauncherApps = launcherApps;
+ mNotificationEntryManager = notificationEntryManager;
}
/**
@@ -124,8 +133,7 @@
Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0;
if (showSingleConversation) {
synchronized (mLock) {
- PeopleSpaceUtils.updateSingleConversationWidgets(
- mContext, widgetIds, mAppWidgetManager, mIPeopleManager);
+ updateSingleConversationWidgets(widgetIds);
}
}
} catch (Exception e) {
@@ -134,6 +142,85 @@
}
/**
+ * Updates {@code appWidgetIds} with their associated conversation stored, handling a
+ * notification being posted or removed.
+ */
+ public void updateSingleConversationWidgets(int[] appWidgetIds) {
+ Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>();
+ for (int appWidgetId : appWidgetIds) {
+ PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
+ if (tile == null) {
+ if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID");
+ //TODO: Delete app widget id when crash is fixed (b/172932636)
+ continue;
+ }
+ Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
+ updateAppWidgetViews(mAppWidgetManager, mContext, appWidgetId, tile, options);
+ widgetIdToTile.put(appWidgetId, tile);
+ }
+ PeopleSpaceUtils.getBirthdaysOnBackgroundThread(
+ mContext, mAppWidgetManager, widgetIdToTile, appWidgetIds);
+ }
+
+ /**
+ * Returns a {@link PeopleSpaceTile} based on the {@code appWidgetId}.
+ * Widget already exists, so fetch {@link PeopleTileKey} from {@link SharedPreferences}.
+ */
+ @Nullable
+ public PeopleSpaceTile getTileForExistingWidget(int appWidgetId) {
+ // First, check if tile is cached in AppWidgetOptions.
+ PeopleSpaceTile tile = AppWidgetOptionsHelper.getPeopleTile(mAppWidgetManager, appWidgetId);
+ if (tile != null) {
+ if (DEBUG) Log.d(TAG, "People Tile is cached for widget: " + appWidgetId);
+ return tile;
+ }
+
+ // If tile is null, we need to retrieve from persistent storage.
+ if (DEBUG) Log.d(TAG, "Fetching key from sharedPreferences: " + appWidgetId);
+ SharedPreferences widgetSp = mContext.getSharedPreferences(
+ String.valueOf(appWidgetId),
+ Context.MODE_PRIVATE);
+ PeopleTileKey key = new PeopleTileKey(
+ widgetSp.getString(SHORTCUT_ID, EMPTY_STRING),
+ widgetSp.getInt(USER_ID, INVALID_USER_ID),
+ widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
+
+ return getTileFromPersistentStorage(key);
+ }
+
+ /**
+ * Returns a {@link PeopleSpaceTile} based on the {@code appWidgetId}.
+ * If a {@link PeopleTileKey} is not provided, fetch one from {@link SharedPreferences}.
+ */
+ @Nullable
+ public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key) {
+ if (!key.isValid()) {
+ Log.e(TAG, "PeopleTileKey invalid: " + key);
+ return null;
+ }
+
+ if (mIPeopleManager == null || mLauncherApps == null) {
+ Log.d(TAG, "System services are null");
+ return null;
+ }
+
+ try {
+ if (DEBUG) Log.d(TAG, "Retrieving Tile from storage: " + key.toString());
+ ConversationChannel channel = mIPeopleManager.getConversation(
+ key.getPackageName(), key.getUserId(), key.getShortcutId());
+ if (channel == null) {
+ Log.d(TAG, "Could not retrieve conversation from storage");
+ return null;
+ }
+
+ return new PeopleSpaceTile.Builder(channel, mLauncherApps).build();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
+ return null;
+ }
+ }
+
+ /**
* Check if any existing People tiles match the incoming notification change, and store the
* change in the tile if so.
*/
@@ -201,8 +288,7 @@
*/
private void updateStorageAndViewWithConversationData(ConversationChannel conversation,
int appWidgetId) {
- PeopleSpaceTile storedTile = getPeopleSpaceTile(
- mContext, appWidgetId, mAppWidgetManager, mIPeopleManager);
+ PeopleSpaceTile storedTile = getTileForExistingWidget(appWidgetId);
if (storedTile == null) {
if (DEBUG) Log.d(TAG, "Could not find stored tile to add conversation to");
return;
@@ -217,8 +303,7 @@
.setUserName(info.getLabel())
.setUserIcon(
PeopleSpaceTile.convertDrawableToIcon(mLauncherApps.getShortcutIconDrawable(
- info, 0))
- )
+ info, 0)))
.setContactUri(uri)
.setStatuses(conversation.getStatuses())
.setLastInteractionTimestamp(conversation.getLastEventTimestamp())
@@ -235,8 +320,7 @@
StatusBarNotification sbn,
PeopleSpaceUtils.NotificationAction notificationAction,
int appWidgetId) {
- PeopleSpaceTile storedTile = getPeopleSpaceTile(
- mContext, appWidgetId, mAppWidgetManager, mIPeopleManager);
+ PeopleSpaceTile storedTile = getTileForExistingWidget(appWidgetId);
if (storedTile == null) {
if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to");
return;
@@ -250,9 +334,12 @@
}
storedTile = storedTile
.toBuilder()
+ // Reset notification content.
.setNotificationKey(null)
.setNotificationContent(null)
.setNotificationDataUri(null)
+ // Reset missed calls category.
+ .setNotificationCategory(null)
.build();
}
updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId, storedTile);
@@ -330,26 +417,51 @@
Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: "
+ optionsKey.getShortcutId());
}
- addNewWidget(appWidgetId, optionsKey);
AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManager, appWidgetId);
+ addNewWidget(appWidgetId, optionsKey);
+ }
+ // Update views for new widget dimensions.
+ updateWidgets(new int[]{appWidgetId});
+ }
+
+ /** Adds a widget based on {@code key} mapped to {@code appWidgetId}. */
+ public void addNewWidget(int appWidgetId, PeopleTileKey key) {
+ if (DEBUG) Log.d(TAG, "addNewWidget called with key for appWidgetId: " + appWidgetId);
+ PeopleSpaceTile tile = getTileFromPersistentStorage(key);
+ tile = PeopleSpaceUtils.augmentSingleTileFromVisibleNotifications(
+ mContext, tile, mNotificationEntryManager);
+ if (tile != null) {
+ addNewWidget(appWidgetId, tile);
}
}
- /** Adds{@code tile} mapped to {@code appWidgetId}. */
- public void addNewWidget(int appWidgetId, PeopleTileKey key) {
+ /**
+ * Adds a widget based on {@code tile} mapped to {@code appWidgetId}.
+ * The tile provided should already be augmented.
+ */
+ public void addNewWidget(int appWidgetId, PeopleSpaceTile tile) {
+ if (DEBUG) Log.d(TAG, "addNewWidget called for appWidgetId: " + appWidgetId);
+ if (tile == null) {
+ return;
+ }
+
mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED);
synchronized (mLock) {
- if (DEBUG) Log.d(TAG, "Add storage for : " + key.getShortcutId());
+ if (DEBUG) Log.d(TAG, "Add storage for : " + tile.getId());
+ PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId);
}
try {
- if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + key.getShortcutId());
- mLauncherApps.cacheShortcuts(key.getPackageName(),
- Collections.singletonList(key.getShortcutId()),
- UserHandle.of(key.getUserId()), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+ if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
+ mLauncherApps.cacheShortcuts(tile.getPackageName(),
+ Collections.singletonList(tile.getId()),
+ tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
} catch (Exception e) {
Log.w(TAG, "Exception caching shortcut:" + e);
}
+
+ PeopleSpaceUtils.updateAppWidgetOptionsAndView(
+ mAppWidgetManager, mContext, appWidgetId, tile);
PeopleSpaceWidgetProvider provider = new PeopleSpaceWidgetProvider();
provider.onUpdate(mContext, mAppWidgetManager, new int[]{appWidgetId});
}
@@ -448,4 +560,28 @@
Log.d(TAG, "Exception uncaching shortcut:" + e);
}
}
+
+ /**
+ * Builds a request to pin a People Tile app widget, with a preview and storing necessary
+ * information as the callback.
+ */
+ public boolean requestPinAppWidget(ShortcutInfo shortcutInfo) {
+ if (DEBUG) Log.d(TAG, "Requesting pin widget, shortcutId: " + shortcutInfo.getId());
+
+ RemoteViews widgetPreview = PeopleSpaceUtils.getPreview(mContext, mIPeopleManager,
+ mLauncherApps, mNotificationEntryManager, shortcutInfo.getId(),
+ shortcutInfo.getUserHandle(), shortcutInfo.getPackage());
+ if (widgetPreview == null) {
+ Log.w(TAG, "Skipping pinning widget: no tile for shortcutId: " + shortcutInfo.getId());
+ return false;
+ }
+ Bundle extras = new Bundle();
+ extras.putParcelable(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW, widgetPreview);
+
+ PendingIntent successCallback =
+ PeopleSpaceWidgetPinnedReceiver.getPendingIntent(mContext, shortcutInfo);
+
+ ComponentName componentName = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
+ return mAppWidgetManager.requestPinAppWidget(componentName, extras, successCallback);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
new file mode 100644
index 0000000..a28da43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
@@ -0,0 +1,86 @@
+/*
+ * 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.widget;
+
+import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.util.Log;
+
+import com.android.systemui.people.PeopleSpaceUtils;
+
+import javax.inject.Inject;
+
+/** Called when a People Tile widget is added via {@link AppWidgetManager.requestPinAppWidget()}. */
+public class PeopleSpaceWidgetPinnedReceiver extends BroadcastReceiver {
+ private static final String TAG = "PeopleSpaceWgtPinReceiver";
+ private static final int BROADCAST_ID = 0;
+ private static final int INVALID_WIDGET_ID = -1;
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+
+ private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
+
+ @Inject
+ public PeopleSpaceWidgetPinnedReceiver(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
+ mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
+ }
+
+ /** Creates a {@link PendingIntent} that is passed onto this receiver when a widget is added. */
+ public static PendingIntent getPendingIntent(Context context, ShortcutInfo shortcutInfo) {
+ Intent intent = new Intent(context, PeopleSpaceWidgetPinnedReceiver.class)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
+ intent.putExtra(Intent.EXTRA_USER_ID, shortcutInfo.getUserId());
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, shortcutInfo.getPackage());
+
+ // Intent needs to be mutable because App Widget framework populates it with app widget id.
+ return PendingIntent.getBroadcast(context, BROADCAST_ID, intent,
+ PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "Add widget broadcast received");
+ if (context == null || intent == null) {
+ if (DEBUG) Log.w(TAG, "Skipping: context or intent are null");
+ return;
+ }
+
+ int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, INVALID_WIDGET_ID);
+ if (widgetId == INVALID_WIDGET_ID) {
+ if (DEBUG) Log.w(TAG, "Skipping: invalid widgetId");
+ return;
+ }
+
+ String shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID);
+ String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, INVALID_USER_ID);
+ PeopleTileKey key = new PeopleTileKey(shortcutId, userId, packageName);
+ if (!key.isValid()) {
+ if (DEBUG) Log.w(TAG, "Skipping: key is not valid: " + key.toString());
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Adding widget: " + widgetId + ", key:" + key.toString());
+ mPeopleSpaceWidgetManager.addNewWidget(widgetId, key);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
index ac42cb0..319df85 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
@@ -19,6 +19,7 @@
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+import android.app.people.PeopleSpaceTile;
import android.text.TextUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -37,6 +38,12 @@
mPackageName = packageName;
}
+ public PeopleTileKey(PeopleSpaceTile tile) {
+ mShortcutId = tile.getId();
+ mUserId = tile.getUserHandle().getIdentifier();
+ mPackageName = tile.getPackageName();
+ }
+
public PeopleTileKey(NotificationEntry entry) {
mShortcutId = entry.getRanking() != null
&& entry.getRanking().getConversationShortcutInfo() != null
@@ -81,7 +88,7 @@
*/
@Override
public String toString() {
- if (!isValid()) return null;
+ if (!isValid()) return EMPTY_STRING;
return mShortcutId + "/" + mUserId + "/" + mPackageName;
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index 03c1843..f87ea7c 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -214,9 +214,7 @@
private fun filterType(type: PrivacyType?): PrivacyType? {
return type?.let {
- if (privacyItemController.allIndicatorsAvailable) {
- it
- } else if ((it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE) &&
+ if ((it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE) &&
privacyItemController.micCameraAvailable) {
it
} else if (it == PrivacyType.TYPE_LOCATION && privacyItemController.locationAvailable) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 1e04516..03d6154 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -68,8 +68,6 @@
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
}
const val TAG = "PrivacyItemController"
- private const val ALL_INDICATORS =
- SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
private const val DEFAULT_ALL_INDICATORS = false
@@ -83,11 +81,6 @@
@Synchronized get() = field.toList() // Returns a shallow copy of the list
@Synchronized set
- private fun isAllIndicatorsEnabled(): Boolean {
- return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- ALL_INDICATORS, DEFAULT_ALL_INDICATORS)
- }
-
private fun isMicCameraEnabled(): Boolean {
return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
MIC_CAMERA, DEFAULT_MIC_CAMERA)
@@ -120,34 +113,29 @@
uiExecutor.execute(notifyChanges)
}
- var allIndicatorsAvailable = isAllIndicatorsEnabled()
- private set
var micCameraAvailable = isMicCameraEnabled()
private set
var locationAvailable = isLocationEnabled()
+ var allIndicatorsAvailable = micCameraAvailable && locationAvailable
+
private val devicePropertiesChangedListener =
object : DeviceConfig.OnPropertiesChangedListener {
override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) &&
- (properties.keyset.contains(ALL_INDICATORS) ||
- properties.keyset.contains(MIC_CAMERA) ||
+ (properties.keyset.contains(MIC_CAMERA) ||
properties.keyset.contains(LOCATION))) {
// Running on the ui executor so can iterate on callbacks
- if (properties.keyset.contains(ALL_INDICATORS)) {
- allIndicatorsAvailable = properties.getBoolean(ALL_INDICATORS,
- DEFAULT_ALL_INDICATORS)
- callbacks.forEach { it.get()?.onFlagAllChanged(allIndicatorsAvailable) }
- }
-
if (properties.keyset.contains(MIC_CAMERA)) {
micCameraAvailable = properties.getBoolean(MIC_CAMERA, DEFAULT_MIC_CAMERA)
+ allIndicatorsAvailable = micCameraAvailable && locationAvailable
callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) }
}
if (properties.keyset.contains(LOCATION)) {
locationAvailable = properties.getBoolean(LOCATION, DEFAULT_LOCATION)
+ allIndicatorsAvailable = micCameraAvailable && locationAvailable
callbacks.forEach { it.get()?.onFlagLocationChanged(locationAvailable) }
}
internalUiExecutor.updateListeningState()
@@ -163,8 +151,7 @@
active: Boolean
) {
// Check if we care about this code right now
- if (!allIndicatorsAvailable &&
- (code in OPS_LOCATION && !locationAvailable)) {
+ if (code in OPS_LOCATION && !locationAvailable) {
return
}
val userId = UserHandle.getUserId(uid)
@@ -231,7 +218,7 @@
*/
private fun setListeningState() {
val listen = !callbacks.isEmpty() and
- (allIndicatorsAvailable || micCameraAvailable || locationAvailable)
+ (micCameraAvailable || locationAvailable)
if (listening == listen) return
listening = listen
if (listening) {
@@ -338,7 +325,7 @@
AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
else -> return null
}
- if (type == PrivacyType.TYPE_LOCATION && (!allIndicatorsAvailable && !locationAvailable)) {
+ if (type == PrivacyType.TYPE_LOCATION && !locationAvailable) {
return null
}
val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
index 0fa7b59..5ab7bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
+++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
@@ -88,7 +88,6 @@
private boolean mViewAndWindowAdded;
private ObjectAnimator mAnimator;
- private boolean mAllIndicatorsFlagEnabled;
private boolean mMicCameraIndicatorFlagEnabled;
private boolean mLocationIndicatorEnabled;
private List<PrivacyItem> mPrivacyItems;
@@ -111,12 +110,10 @@
mIconMarginStart = Math.round(res.getDimension(R.dimen.privacy_chip_icon_margin));
mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
- mAllIndicatorsFlagEnabled = privacyItemController.getAllIndicatorsAvailable();
mMicCameraIndicatorFlagEnabled = privacyItemController.getMicCameraAvailable();
mLocationIndicatorEnabled = privacyItemController.getLocationAvailable();
if (DEBUG) {
- Log.d(TAG, "allIndicators: " + mAllIndicatorsFlagEnabled);
Log.d(TAG, "micCameraIndicators: " + mMicCameraIndicatorFlagEnabled);
Log.d(TAG, "locationIndicators: " + mLocationIndicatorEnabled);
}
@@ -135,12 +132,6 @@
}
@Override
- public void onFlagAllChanged(boolean flag) {
- if (DEBUG) Log.d(TAG, "all indicators enabled: " + flag);
- mAllIndicatorsFlagEnabled = flag;
- }
-
- @Override
public void onFlagMicCameraChanged(boolean flag) {
if (DEBUG) Log.d(TAG, "mic/camera indicators enabled: " + flag);
mMicCameraIndicatorFlagEnabled = flag;
@@ -155,8 +146,8 @@
private void updateUI() {
if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items");
- if ((mMicCameraIndicatorFlagEnabled || mAllIndicatorsFlagEnabled
- || mLocationIndicatorEnabled) && !mPrivacyItems.isEmpty()) {
+ if ((mMicCameraIndicatorFlagEnabled || mLocationIndicatorEnabled)
+ && !mPrivacyItems.isEmpty()) {
if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) {
showIndicator();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 8ab1743..6386365 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -67,6 +67,7 @@
private final DumpManager mDumpManager;
private final FeatureFlags mFeatureFlags;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
+ private boolean mShouldUseSplitNotificationShade;
private int mLastOrientation;
private String mCachedSpecs = "";
@@ -81,6 +82,8 @@
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
+ mShouldUseSplitNotificationShade =
+ Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
if (newConfig.orientation != mLastOrientation) {
mLastOrientation = newConfig.orientation;
switchTileLayout(false);
@@ -119,6 +122,8 @@
mDumpManager = dumpManager;
mFeatureFlags = featureFlags;
mQSLabelFlag = featureFlags.isQSLabelsEnabled();
+ mShouldUseSplitNotificationShade =
+ Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
}
@Override
@@ -345,7 +350,7 @@
}
boolean shouldUseHorizontalLayout() {
- if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources())) {
+ if (mShouldUseSplitNotificationShade) {
return false;
}
return mUsingMediaPlayer && mMediaHost.getVisible()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 1411fa1..5e13fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.qs;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_SECURITY_FOOTER_VIEW;
import android.app.AlertDialog;
@@ -244,8 +246,14 @@
if (organizationName == null) {
return mContext.getString(R.string.quick_settings_disclosure_management);
}
- return mContext.getString(R.string.quick_settings_disclosure_named_management,
- organizationName);
+ if (isFinancedDevice()) {
+ return mContext.getString(
+ R.string.quick_settings_financed_disclosure_named_management,
+ organizationName);
+ } else {
+ return mContext.getString(R.string.quick_settings_disclosure_named_management,
+ organizationName);
+ }
} // end if(isDeviceManaged)
if (hasCACertsInWorkProfile) {
if (workProfileOrganizationName == null) {
@@ -355,6 +363,10 @@
.inflate(R.layout.quick_settings_footer_dialog, null, false);
// device management section
+ TextView deviceManagementSubtitle =
+ dialogView.findViewById(R.id.device_management_subtitle);
+ deviceManagementSubtitle.setText(getManagementTitle(deviceOwnerOrganization));
+
CharSequence managementMessage = getManagementMessage(isDeviceManaged,
deviceOwnerOrganization, isProfileOwnerOfOrganizationOwnedDevice,
workProfileOrganizationName);
@@ -468,7 +480,8 @@
}
}
- private String getSettingsButton() {
+ @VisibleForTesting
+ String getSettingsButton() {
return mContext.getString(R.string.monitoring_button_view_policies);
}
@@ -490,8 +503,13 @@
return null;
}
if (isDeviceManaged && organizationName != null) {
- return mContext.getString(
- R.string.monitoring_description_named_management, organizationName);
+ if (isFinancedDevice()) {
+ return mContext.getString(R.string.monitoring_financed_description_named_management,
+ organizationName, organizationName);
+ } else {
+ return mContext.getString(
+ R.string.monitoring_description_named_management, organizationName);
+ }
} else if (isProfileOwnerOfOrganizationOwnedDevice && workProfileOrganizationName != null) {
return mContext.getString(
R.string.monitoring_description_named_management, workProfileOrganizationName);
@@ -557,14 +575,23 @@
return message;
}
- private int getTitle(String deviceOwner) {
- if (deviceOwner != null) {
- return R.string.monitoring_title_device_owned;
+ @VisibleForTesting
+ CharSequence getManagementTitle(CharSequence deviceOwnerOrganization) {
+ if (deviceOwnerOrganization != null && isFinancedDevice()) {
+ return mContext.getString(R.string.monitoring_title_financed_device,
+ deviceOwnerOrganization);
} else {
- return R.string.monitoring_title;
+ return mContext.getString(R.string.monitoring_title_device_owned);
}
}
+ private boolean isFinancedDevice() {
+ return mSecurityController.isDeviceManaged()
+ && mSecurityController.getDeviceOwnerType(
+ mSecurityController.getDeviceOwnerComponentOnAnyUser())
+ == DEVICE_OWNER_TYPE_FINANCED;
+ }
+
private final Runnable mUpdateIcon = new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 2cd367d..3e3451e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -445,11 +445,6 @@
final ArrayList<String> tiles = new ArrayList<String>();
boolean addedDefault = false;
Set<String> addedSpecs = new ArraySet<>();
- // TODO(b/174753536): Move it into the config file.
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- tiles.add("internet");
- addedSpecs.add("internet");
- }
for (String tile : tileList.split(",")) {
tile = tile.trim();
if (tile.isEmpty()) continue;
@@ -457,17 +452,6 @@
if (!addedDefault) {
List<String> defaultSpecs = getDefaultSpecs(context);
for (String spec : defaultSpecs) {
- // TODO(b/174753536): Move it into the config file.
- if (FeatureFlagUtils.isEnabled(
- context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- if (spec.equals("wifi") || spec.equals("cell")) {
- continue;
- }
- } else {
- if (spec.equals("internet")) {
- continue;
- }
- }
if (!addedSpecs.contains(spec)) {
tiles.add(spec);
addedSpecs.add(spec);
@@ -476,18 +460,40 @@
addedDefault = true;
}
} else {
- // TODO(b/174753536): Move it into the config file.
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- if (tile.equals("wifi") || tile.equals("cell")) {
- continue;
- }
- }
if (!addedSpecs.contains(tile)) {
tiles.add(tile);
addedSpecs.add(tile);
}
}
}
+ // 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 (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")) {
+ 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.add("cell");
+ }
+ }
+ }
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 8f99a9e..e7828c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -355,7 +355,6 @@
setClipToPadding(false);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.CENTER_HORIZONTAL;
setLayoutParams(lp);
setMaxColumns(4);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index e3c39aa..eedcdab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -97,7 +97,6 @@
private boolean mListening;
private AlarmClockInfo mNextAlarm;
- private boolean mAllIndicatorsEnabled;
private boolean mMicCameraIndicatorsEnabled;
private boolean mLocationIndicatorsEnabled;
private boolean mPrivacyChipLogged;
@@ -151,14 +150,6 @@
}
@Override
- public void onFlagAllChanged(boolean flag) {
- if (mAllIndicatorsEnabled != flag) {
- mAllIndicatorsEnabled = flag;
- update();
- }
- }
-
- @Override
public void onFlagMicCameraChanged(boolean flag) {
if (mMicCameraIndicatorsEnabled != flag) {
mMicCameraIndicatorsEnabled = flag;
@@ -270,7 +261,6 @@
mRingerContainer.setOnClickListener(mOnClickListener);
mPrivacyChip.setOnClickListener(mOnClickListener);
- mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
@@ -321,7 +311,6 @@
mNextAlarmController.addCallback(mNextAlarmChangeCallback);
mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
// Get the most up to date info
- mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
mPrivacyItemController.addCallback(mPICCallback);
@@ -353,13 +342,13 @@
private List<String> getIgnoredIconSlots() {
ArrayList<String> ignored = new ArrayList<>();
if (getChipEnabled()) {
- if (mAllIndicatorsEnabled || mMicCameraIndicatorsEnabled) {
+ if (mMicCameraIndicatorsEnabled) {
ignored.add(mView.getResources().getString(
com.android.internal.R.string.status_bar_camera));
ignored.add(mView.getResources().getString(
com.android.internal.R.string.status_bar_microphone));
}
- if (mAllIndicatorsEnabled || mLocationIndicatorsEnabled) {
+ if (mLocationIndicatorsEnabled) {
ignored.add(mView.getResources().getString(
com.android.internal.R.string.status_bar_location));
}
@@ -368,7 +357,7 @@
}
private boolean getChipEnabled() {
- return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled || mAllIndicatorsEnabled;
+ return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled;
}
private boolean isZenOverridingRinger() {
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 7eeb4bd..32b41ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -115,9 +115,32 @@
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
// TODO(b/174753536): Move it into the config file.
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- if (!possibleTiles.contains("internet")) {
- possibleTiles.add("internet");
+ // 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");
+ }
}
}
for (String spec : possibleTiles) {
@@ -125,15 +148,6 @@
// Do not include CustomTile. Those will be created by `addPackageTiles`.
if (spec.startsWith(CustomTile.PREFIX)) continue;
// TODO(b/174753536): Move it into the config file.
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- if (spec.equals("wifi") || spec.equals("cell")) {
- continue;
- }
- } else {
- if (spec.equals("internet")) {
- continue;
- }
- }
final QSTile tile = host.createTile(spec);
if (tile == null) {
continue;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 29b9e64..ba349c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -47,6 +47,7 @@
import com.android.systemui.qs.tiles.MicrophoneToggleTile;
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
+import com.android.systemui.qs.tiles.QuickAccessWalletTile;
import com.android.systemui.qs.tiles.ReduceBrightColorsTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.ScreenRecordTile;
@@ -93,6 +94,7 @@
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
private final Provider<AlarmTile> mAlarmTileProvider;
+ private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider;
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -129,7 +131,8 @@
Provider<CameraToggleTile> cameraToggleTileProvider,
Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
Provider<DeviceControlsTile> deviceControlsTileProvider,
- Provider<AlarmTile> alarmTileProvider) {
+ Provider<AlarmTile> alarmTileProvider,
+ Provider<QuickAccessWalletTile> quickAccessWalletTileProvider) {
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
@@ -161,6 +164,7 @@
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
mDeviceControlsTileProvider = deviceControlsTileProvider;
mAlarmTileProvider = alarmTileProvider;
+ mQuickAccessWalletTileProvider = quickAccessWalletTileProvider;
}
public QSTile createTile(String tileSpec) {
@@ -224,6 +228,8 @@
return mDeviceControlsTileProvider.get();
case "alarm":
return mAlarmTileProvider.get();
+ case "wallet":
+ return mQuickAccessWalletTileProvider.get();
}
// Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 14a3fc0..1a17e61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -416,7 +416,6 @@
}
} else {
state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
- state.label = r.getString(R.string.quick_settings_airplane_safe_label);
}
} else if (cb.mNoDefaultNetwork && cb.mNoNetworksAvailable) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
@@ -480,9 +479,6 @@
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
} else {
- if (cb.mAirplaneModeEnabled) {
- state.label = r.getString(R.string.quick_settings_airplane_safe_label);
- }
state.icon = new SignalIcon(cb.mMobileSignalIconId);
state.secondaryLabel = appendMobileDataType(cb.mDataSubscriptionName,
getMobileDataContentName(cb));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
new file mode 100644
index 0000000..60c5d1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.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.systemui.qs.tiles;
+
+import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
+import android.service.quicksettings.Tile;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.SecureSettings;
+
+import javax.inject.Inject;
+
+/** Quick settings tile: Quick access wallet **/
+public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
+
+ private static final String FEATURE_CHROME_OS = "org.chromium.arc";
+ private final CharSequence mLabel = mContext.getString(R.string.wallet_title);
+ // TODO(b/180959290): Re-create the QAW Client when the default NFC payment app changes.
+ private final QuickAccessWalletClient mQuickAccessWalletClient;
+ private final KeyguardStateController mKeyguardStateController;
+ private final PackageManager mPackageManager;
+ private final SecureSettings mSecureSettings;
+ private final FeatureFlags mFeatureFlags;
+
+ @Inject
+ public QuickAccessWalletTile(
+ QSHost host,
+ @Background Looper backgroundLooper,
+ @Main Handler mainHandler,
+ FalsingManager falsingManager,
+ MetricsLogger metricsLogger,
+ StatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter,
+ QSLogger qsLogger,
+ QuickAccessWalletClient quickAccessWalletClient,
+ KeyguardStateController keyguardStateController,
+ PackageManager packageManager,
+ SecureSettings secureSettings,
+ FeatureFlags featureFlags) {
+ super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ statusBarStateController, activityStarter, qsLogger);
+ mQuickAccessWalletClient = quickAccessWalletClient;
+ mKeyguardStateController = keyguardStateController;
+ mPackageManager = packageManager;
+ mSecureSettings = secureSettings;
+ mFeatureFlags = featureFlags;
+ }
+
+
+ @Override
+ public State newTileState() {
+ State state = new State();
+ state.handlesLongClick = false;
+ return state;
+ }
+
+ @Override
+ protected void handleClick() {
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ mQuickAccessWalletClient.createWalletIntent(), /* delay= */ 0);
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel();
+ state.label = qawLabel == null ? mLabel : qawLabel;
+ state.contentDescription = state.label;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_wallet);
+ boolean isDeviceLocked = !mKeyguardStateController.isUnlocked();
+ if (mQuickAccessWalletClient.isWalletFeatureAvailable()) {
+ state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
+ state.secondaryLabel = isDeviceLocked
+ ? null
+ : mContext.getString(R.string.wallet_secondary_label);
+ state.stateDescription = state.secondaryLabel;
+ } else {
+ state.state = Tile.STATE_UNAVAILABLE;
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 0;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mFeatureFlags.isQuickAccessWalletEnabled()
+ && mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
+ && !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS)
+ && mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT) != null;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel();
+ return qawLabel == null ? mLabel : qawLabel;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index a87bfd8..b0a3f43 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,6 +25,11 @@
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
@@ -35,15 +40,12 @@
import android.annotation.FloatRange;
import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.app.PictureInPictureParams;
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.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -57,14 +59,12 @@
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.ArraySet;
import android.util.Log;
import android.view.InputMonitor;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.accessibility.AccessibilityManager;
-import android.window.IRemoteTransition;
import androidx.annotation.NonNull;
@@ -83,15 +83,11 @@
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-import com.android.systemui.shared.recents.ISplitScreenListener;
-import com.android.systemui.shared.recents.IStartingWindowListener;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.RemoteTransitionCompat;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -103,7 +99,7 @@
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.RemoteTransitions;
+import com.android.wm.shell.transition.ShellTransitions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -111,7 +107,6 @@
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
-import java.util.function.Consumer;
import javax.inject.Inject;
@@ -151,12 +146,11 @@
private final ScreenshotHelper mScreenshotHelper;
private final Optional<OneHanded> mOneHandedOptional;
private final CommandQueue mCommandQueue;
- private final RemoteTransitions mShellTransitions;
+ private final ShellTransitions mShellTransitions;
private final Optional<StartingSurface> mStartingSurface;
private Region mActiveNavBarRegion;
- private IPinnedStackAnimationListener mIPinnedStackAnimationListener;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
private boolean mBound;
@@ -169,8 +163,6 @@
private float mWindowCornerRadius;
private boolean mSupportsRoundedCornersOnWindows;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
- private final ArraySet<IRemoteTransition> mRemoteTransitions = new ArraySet<>();
- private IStartingWindowListener mIStartingWindowListener;
@VisibleForTesting
public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -294,11 +286,6 @@
}
@Override
- public void setBackButtonAlpha(float alpha, boolean animate) {
- setNavBarButtonAlpha(alpha, animate);
- }
-
- @Override
public void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
if (!verifyCaller("onAssistantProgress")) {
return;
@@ -388,20 +375,6 @@
}
@Override
- public void setShelfHeight(boolean visible, int shelfHeight) {
- if (!verifyCaller("setShelfHeight")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mPipOptional.ifPresent(
- pip -> pip.setShelfHeight(visible, shelfHeight));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
Insets visibleInsets, int taskId) {
// Deprecated
@@ -429,36 +402,6 @@
}
@Override
- public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- if (!verifyCaller("setPinnedStackAnimationListener")) {
- return;
- }
- mIPinnedStackAnimationListener = listener;
- final long token = Binder.clearCallingIdentity();
- try {
- mPipOptional.ifPresent(
- pip -> pip.setPinnedStackAnimationListener(mPinnedStackAnimationCallback));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setStartingWindowListener(IStartingWindowListener listener) {
- if (!verifyCaller("setStartingWindowListener")) {
- return;
- }
- mIStartingWindowListener = listener;
- final long token = Binder.clearCallingIdentity();
- try {
- mStartingSurface.ifPresent(s ->
- s.setStartingWindowListener(mStartingWindowListener));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
if (!verifyCaller("onQuickSwitchToNewTask")) {
return;
@@ -472,32 +415,6 @@
}
@Override
- public void startOneHandedMode() {
- if (!verifyCaller("startOneHandedMode")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mOneHandedOptional.ifPresent(oneHanded -> oneHanded.startOneHanded());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void stopOneHandedMode() {
- if (!verifyCaller("stopOneHandedMode")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mOneHandedOptional.ifPresent(oneHanded -> oneHanded.stopOneHanded());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
Insets visibleInsets, Task.TaskKey task) {
mScreenshotHelper.provideScreenshot(
@@ -525,190 +442,6 @@
}
}
- @Override
- public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
- PictureInPictureParams pictureInPictureParams,
- int launcherRotation, int shelfHeight) {
- if (!verifyCaller("startSwipePipToHome")) {
- return null;
- }
- final long binderToken = Binder.clearCallingIdentity();
- try {
- return mPipOptional.map(pip ->
- pip.startSwipePipToHome(componentName, activityInfo,
- pictureInPictureParams, launcherRotation, shelfHeight))
- .orElse(null);
- } finally {
- Binder.restoreCallingIdentity(binderToken);
- }
- }
-
- @Override
- public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- if (!verifyCaller("stopSwipePipToHome")) {
- return;
- }
- final long binderToken = Binder.clearCallingIdentity();
- try {
- mPipOptional.ifPresent(pip -> pip.stopSwipePipToHome(
- componentName, destinationBounds));
- } finally {
- Binder.restoreCallingIdentity(binderToken);
- }
- }
-
- @Override
- public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) {
- if (!verifyCaller("registerRemoteTransition")) return;
- final long binderToken = Binder.clearCallingIdentity();
- try {
- mRemoteTransitions.add(remoteTransition.getTransition());
- mShellTransitions.registerRemote(
- remoteTransition.getFilter(), remoteTransition.getTransition());
- } finally {
- Binder.restoreCallingIdentity(binderToken);
- }
- }
-
- @Override
- public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
- if (!verifyCaller("registerRemoteTransition")) return;
- final long binderToken = Binder.clearCallingIdentity();
- try {
- mRemoteTransitions.remove(remoteTransition.getTransition());
- mShellTransitions.unregisterRemote(remoteTransition.getTransition());
- } finally {
- Binder.restoreCallingIdentity(binderToken);
- }
- }
-
- @Override
- public void registerSplitScreenListener(ISplitScreenListener listener) {
- if (!verifyCaller("registerSplitScreenListener")) {
- return;
- }
- mISplitScreenListener = listener;
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(
- s -> s.registerSplitScreenListener(mSplitScreenListener));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void unregisterSplitScreenListener(ISplitScreenListener listener) {
- if (!verifyCaller("unregisterSplitScreenListener")) {
- return;
- }
- mISplitScreenListener = null;
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(
- s -> s.unregisterSplitScreenListener(mSplitScreenListener));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void setSideStageVisibility(boolean visible) {
- if (!verifyCaller("setSideStageVisibility")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(s -> s.setSideStageVisibility(visible));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void exitSplitScreen() {
- if (!verifyCaller("exitSplitScreen")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(s -> s.exitSplitScreen());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- if (!verifyCaller("exitSplitScreenOnHide")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(s -> s.exitSplitScreenOnHide(exitSplitScreenOnHide));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void startTask(int taskId, int stage, int position, Bundle options) {
- if (!verifyCaller("startTask")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(
- s -> s.startTask(taskId, stage, position, options));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void startShortcut(String packageName, String shortcutId, int stage, int position,
- Bundle options, UserHandle user) {
- if (!verifyCaller("startShortcut")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(s ->
- s.startShortcut(packageName, shortcutId, stage, position, options, user));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void startIntent(PendingIntent intent, Intent fillInIntent,
- int stage, int position, Bundle options) {
- if (!verifyCaller("startIntent")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(s ->
- s.startIntent(intent, mContext, fillInIntent, stage, position, options));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void removeFromSideStage(int taskId) {
- if (!verifyCaller("removeFromSideStage")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(
- s -> s.removeFromSideStage(taskId));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -762,6 +495,22 @@
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
+
+ mPipOptional.ifPresent((pip) -> params.putBinder(
+ KEY_EXTRA_SHELL_PIP,
+ pip.createExternalInterface().asBinder()));
+ mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
+ KEY_EXTRA_SHELL_SPLIT_SCREEN,
+ splitscreen.createExternalInterface().asBinder()));
+ mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
+ KEY_EXTRA_SHELL_ONE_HANDED,
+ onehanded.createExternalInterface().asBinder()));
+ params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ mShellTransitions.createExternalInterface().asBinder());
+ mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
+ KEY_EXTRA_SHELL_STARTING_WINDOW,
+ startingwindow.createExternalInterface().asBinder()));
+
try {
mOverviewProxy.onInitialize(params);
} catch (RemoteException e) {
@@ -801,42 +550,11 @@
private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
this::notifySplitScreenBoundsChanged;
- private final Consumer<Boolean> mPinnedStackAnimationCallback =
- this::notifyPinnedStackAnimationStarted;
-
- private final BiConsumer<Integer, Integer> mStartingWindowListener =
- this::notifyTaskLaunching;
// This is the death handler for the binder from the launcher service
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
= this::cleanupAfterDeath;
- private ISplitScreenListener mISplitScreenListener;
- private final SplitScreen.SplitScreenListener mSplitScreenListener =
- new SplitScreen.SplitScreenListener() {
- @Override
- public void onStagePositionChanged(int stage, int position) {
- try {
- if (mISplitScreenListener != null) {
- mISplitScreenListener.onStagePositionChanged(stage, position);
- }
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "onStagePositionChanged", e);
- }
- }
-
- @Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
- try {
- if (mISplitScreenListener != null) {
- mISplitScreenListener.onTaskStageChanged(taskId, stage, visible);
- }
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "onTaskStageChanged", e);
- }
- }
- };
-
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyService(Context context, CommandQueue commandQueue,
@@ -849,7 +567,7 @@
Optional<Lazy<StatusBar>> statusBarOptionalLazy,
Optional<OneHanded> oneHandedOptional,
BroadcastDispatcher broadcastDispatcher,
- RemoteTransitions shellTransitions,
+ ShellTransitions shellTransitions,
Optional<StartingSurface> startingSurface) {
super(broadcastDispatcher);
mContext = context;
@@ -966,29 +684,6 @@
}
}
- private void notifyPinnedStackAnimationStarted(Boolean isAnimationStarted) {
- if (mIPinnedStackAnimationListener == null) {
- return;
- }
- try {
- mIPinnedStackAnimationListener.onPinnedStackAnimationStarted();
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call onPinnedStackAnimationStarted()", e);
- }
- }
-
- private void notifyTaskLaunching(int taskId, int supportedType) {
- if (mIStartingWindowListener == null) {
- return;
- }
-
- try {
- mIStartingWindowListener.onTaskLaunching(taskId, supportedType);
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call notifyTaskLaunching()", e);
- }
- }
-
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
boolean bouncerShowing) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
@@ -1032,12 +727,6 @@
// Clean up the minimized state if launcher dies
mLegacySplitScreenOptional.ifPresent(
splitScreen -> splitScreen.setMinimized(false));
-
- // Clean up any registered remote transitions
- for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
- mShellTransitions.unregisterRemote(mRemoteTransitions.valueAt(i));
- }
- mRemoteTransitions.clear();
}
public void startConnectionToCurrentUser() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index bb8c367..1ec175c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -26,6 +26,8 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.IntArray;
import android.util.Log;
@@ -94,6 +96,25 @@
}
@Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+ ss.mTopBoundary = getTopBoundary();
+ ss.mBottomBoundary = getBottomBoundary();
+ return ss;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ setBoundaryTo(CropBoundary.TOP, ss.mTopBoundary);
+ setBoundaryTo(CropBoundary.BOTTOM, ss.mBottomBoundary);
+ }
+
+ @Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
float top = mTopCrop + mTopDelta;
@@ -380,6 +401,44 @@
*/
void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition,
int boundaryPositionPx);
+ }
+ static class SavedState extends BaseSavedState {
+ float mTopBoundary;
+ float mBottomBoundary;
+
+ /**
+ * Constructor called from {@link CropView#onSaveInstanceState()}
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ mTopBoundary = in.readFloat();
+ mBottomBoundary = in.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeFloat(mTopBoundary);
+ out.writeFloat(mBottomBoundary);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index bc8adc9..4aead817f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -213,6 +213,20 @@
CompressFormat format;
boolean published;
boolean deleted;
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Result{");
+ sb.append("uri=").append(uri);
+ sb.append(", requestId=").append(requestId);
+ sb.append(", fileName='").append(fileName).append('\'');
+ sb.append(", timestamp=").append(timestamp);
+ sb.append(", format=").append(format);
+ sb.append(", published=").append(published);
+ sb.append(", deleted=").append(deleted);
+ sb.append('}');
+ return sb.toString();
+ }
}
private static class Task {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java
index 988b93c..7ee7c31 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java
@@ -21,7 +21,6 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
-import android.os.ParcelFileDescriptor;
import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -43,6 +42,16 @@
@Nullable Uri uri;
@Nullable File fileName;
@Nullable Bitmap bitmap;
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Result{");
+ sb.append("uri=").append(uri);
+ sb.append(", fileName=").append(fileName);
+ sb.append(", bitmap=").append(bitmap);
+ sb.append('}');
+ return sb.toString();
+ }
}
@Inject
@@ -54,7 +63,7 @@
* Loads an image via URI from ContentResolver.
*
* @param uri the identifier of the image to load
- * @return a listenable future result
+ * @return a listenable future result containing a bitmap
*/
ListenableFuture<Result> load(Uri uri) {
return CallbackToFutureAdapter.getFuture(completer -> {
@@ -76,7 +85,7 @@
* permissions to read this file/path.
*
* @param file the system file path of the image to load
- * @return a listenable future result
+ * @return a listenable future result containing a bitmap
*/
ListenableFuture<Result> load(File file) {
return CallbackToFutureAdapter.getFuture(completer -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 6743afa..730702e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -31,8 +31,11 @@
import com.android.internal.util.CallbackRegistry.NotifierCallback;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Owns a series of partial screen captures (tiles).
* <p>
@@ -46,6 +49,7 @@
private CallbackRegistry<OnBoundsChangedListener, ImageTileSet, Rect> mOnBoundsListeners;
private CallbackRegistry<OnContentChangedListener, ImageTileSet, Rect> mContentListeners;
+ @Inject
ImageTileSet(@UiThread Handler handler) {
mHandler = handler;
}
@@ -69,9 +73,6 @@
private final Rect mBounds = new Rect();
private final Handler mHandler;
- private OnContentChangedListener mOnContentChangedListener;
- private OnBoundsChangedListener mOnBoundsChangedListener;
-
void addOnBoundsChangedListener(OnBoundsChangedListener listener) {
if (mOnBoundsListeners == null) {
mOnBoundsListeners = new CallbackRegistry<>(
@@ -204,18 +205,17 @@
return mBounds.height();
}
- @AnyThread
void clear() {
- if (!mHandler.getLooper().isCurrentThread()) {
- mHandler.post(this::clear);
- return;
- }
if (mTiles.isEmpty()) {
return;
}
mBounds.setEmpty();
- mTiles.forEach(ImageTile::close);
- mTiles.clear();
+ Iterator<ImageTile> i = mTiles.iterator();
+ while (i.hasNext()) {
+ ImageTile next = i.next();
+ next.close();
+ i.remove();
+ }
notifyBoundsChanged(mBounds);
notifyContentChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index db99705..31cdada 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.HardwareRenderer;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
@@ -30,10 +29,11 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import android.view.IWindowManager;
+import android.view.ScrollCaptureResponse;
import android.view.View;
import android.widget.ImageView;
@@ -41,14 +41,14 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
import com.google.common.util.concurrent.ListenableFuture;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.UUID;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -61,18 +61,15 @@
public class LongScreenshotActivity extends Activity {
private static final String TAG = "LongScreenshotActivity";
- private static final String IMAGE_PATH_KEY = "saved-image";
- private static final String TOP_BOUNDARY_KEY = "top-boundary";
- private static final String BOTTOM_BOUNDARY_KEY = "bottom-boundary";
+ public static final String EXTRA_CAPTURE_RESPONSE = "capture-response";
+ private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path";
private final UiEventLogger mUiEventLogger;
private final ScrollCaptureController mScrollCaptureController;
- private final ScrollCaptureClient.Connection mConnection;
private final Executor mUiExecutor;
private final Executor mBackgroundExecutor;
private final ImageExporter mImageExporter;
- private String mSavedImagePath;
// If true, the activity is re-loading an image from storage, which should either succeed and
// populate the UI or fail and finish the activity.
private boolean mRestoringInstance;
@@ -84,6 +81,14 @@
private View mShare;
private CropView mCropView;
private MagnifierView mMagnifierView;
+ private ScrollCaptureResponse mScrollCaptureResponse;
+ private File mSavedImagePath;
+
+ private ListenableFuture<File> mCacheSaveFuture;
+ private ListenableFuture<ImageLoader.Result> mCacheLoadFuture;
+
+ private ListenableFuture<LongScreenshot> mLongScreenshotFuture;
+ private LongScreenshot mLongScreenshot;
private enum PendingAction {
SHARE,
@@ -92,35 +97,31 @@
}
@Inject
- public LongScreenshotActivity(UiEventLogger uiEventLogger,
- ImageExporter imageExporter,
- @Main Executor mainExecutor,
- @Background Executor bgExecutor,
- Context context) {
+ public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
+ @Main Executor mainExecutor, @Background Executor bgExecutor, IWindowManager wms,
+ Context context, ScrollCaptureController scrollCaptureController) {
mUiEventLogger = uiEventLogger;
mUiExecutor = mainExecutor;
mBackgroundExecutor = bgExecutor;
mImageExporter = imageExporter;
-
- mScrollCaptureController = new ScrollCaptureController(context, mainExecutor, bgExecutor,
- imageExporter);
-
- mConnection = ScreenshotController.takeScrollCaptureConnection();
+ mScrollCaptureController = scrollCaptureController;
}
+
@Override
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ Log.d(TAG, "onCreate(savedInstanceState = " + savedInstanceState + ")");
+ super.onCreate(savedInstanceState);
setContentView(R.layout.long_screenshot);
- mPreview = findViewById(R.id.preview);
- mSave = findViewById(R.id.save);
- mCancel = findViewById(R.id.cancel);
- mEdit = findViewById(R.id.edit);
- mShare = findViewById(R.id.share);
- mCropView = findViewById(R.id.crop_view);
- mMagnifierView = findViewById(R.id.magnifier);
+ mPreview = requireViewById(R.id.preview);
+ mSave = requireViewById(R.id.save);
+ mCancel = requireViewById(R.id.cancel);
+ mEdit = requireViewById(R.id.edit);
+ mShare = requireViewById(R.id.share);
+ mCropView = requireViewById(R.id.crop_view);
+ mMagnifierView = requireViewById(R.id.magnifier);
mCropView.setCropInteractionListener(mMagnifierView);
mSave.setOnClickListener(this::onClicked);
@@ -128,69 +129,186 @@
mEdit.setOnClickListener(this::onClicked);
mShare.setOnClickListener(this::onClicked);
- if (savedInstanceState != null) {
- String imagePath = savedInstanceState.getString(IMAGE_PATH_KEY);
- if (!TextUtils.isEmpty(imagePath)) {
- mRestoringInstance = true;
- mBackgroundExecutor.execute(() -> {
- Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
- if (bitmap == null) {
- Log.e(TAG, "Failed to read bitmap from " + imagePath);
- finishAndRemoveTask();
- } else {
- runOnUiThread(() -> {
- BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
- mPreview.setImageDrawable(drawable);
- mMagnifierView.setDrawable(drawable, bitmap.getWidth(),
- bitmap.getHeight());
-
- mCropView.setBoundaryTo(CropView.CropBoundary.TOP,
- savedInstanceState.getFloat(TOP_BOUNDARY_KEY, 0f));
- mCropView.setBoundaryTo(CropView.CropBoundary.BOTTOM,
- savedInstanceState.getFloat(BOTTOM_BOUNDARY_KEY, 1f));
- mRestoringInstance = false;
- // Reuse the same path for subsequent restoration.
- mSavedImagePath = imagePath;
- Log.d(TAG, "Loaded bitmap from " + imagePath);
- });
- }
- });
- }
- }
mPreview.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
updateCropLocation());
+
+ Intent intent = getIntent();
+ mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
+
+ if (savedInstanceState != null) {
+ String savedImagePath = savedInstanceState.getString(KEY_SAVED_IMAGE_PATH);
+ if (savedImagePath == null) {
+ Log.e(TAG, "Missing saved state entry with key '" + KEY_SAVED_IMAGE_PATH + "'!");
+ finishAndRemoveTask();
+ return;
+ }
+ mSavedImagePath = new File(savedImagePath);
+ ImageLoader imageLoader = new ImageLoader(getContentResolver());
+ mCacheLoadFuture = imageLoader.load(mSavedImagePath);
+ }
}
@Override
public void onStart() {
+ Log.d(TAG, "onStart");
super.onStart();
- if (mPreview.getDrawable() == null && !mRestoringInstance) {
- if (mConnection == null) {
- Log.e(TAG, "Failed to get scroll capture connection, bailing out");
+
+ if (mCacheLoadFuture != null) {
+ Log.d(TAG, "mRestoringInstance = true");
+ final ListenableFuture<ImageLoader.Result> future = mCacheLoadFuture;
+ mCacheLoadFuture.addListener(() -> {
+ Log.d(TAG, "cached bitmap load complete");
+ try {
+ onCachedImageLoaded(future.get());
+ } catch (CancellationException | ExecutionException | InterruptedException e) {
+ Log.e(TAG, "Failed to load cached image", e);
+ if (mSavedImagePath != null) {
+ //noinspection ResultOfMethodCallIgnored
+ mSavedImagePath.delete();
+ mSavedImagePath = null;
+ }
+ finishAndRemoveTask();
+ }
+ }, mUiExecutor);
+ mCacheLoadFuture = null;
+ return;
+ }
+
+ if (mLongScreenshotFuture == null) {
+ Log.d(TAG, "mLongScreenshotFuture == null");
+ // First run through, ensure we have a connection to use (see #onCreate)
+ if (mScrollCaptureResponse == null || !mScrollCaptureResponse.isConnected()) {
+ Log.e(TAG, "Did not receive a live scroll capture connection, bailing out!");
finishAndRemoveTask();
return;
}
- doCapture();
+ mLongScreenshotFuture = mScrollCaptureController.run(mScrollCaptureResponse);
+ mLongScreenshotFuture.addListener(() -> {
+ LongScreenshot longScreenshot;
+ try {
+ longScreenshot = mLongScreenshotFuture.get();
+ } catch (CancellationException | InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error capturing long screenshot!", e);
+ finishAndRemoveTask();
+ return;
+ }
+ if (longScreenshot.getHeight() == 0) {
+ Log.e(TAG, "Got a zero height result");
+ finishAndRemoveTask();
+ return;
+ }
+ onCaptureCompleted(longScreenshot);
+ }, mUiExecutor);
+ } else {
+ Log.d(TAG, "mLongScreenshotFuture != null");
}
}
+ private void onCaptureCompleted(LongScreenshot longScreenshot) {
+ Log.d(TAG, "onCaptureCompleted(longScreenshot=" + longScreenshot + ")");
+ mLongScreenshot = longScreenshot;
+ mPreview.setImageDrawable(mLongScreenshot.getDrawable());
+ updateCropLocation();
+ mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
+ mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
+ // Original boundaries go from the image tile set's y=0 to y=pageSize, so
+ // we animate to that as a starting crop position.
+ float topFraction = Math.max(0,
+ -mLongScreenshot.getTop() / (float) mLongScreenshot.getHeight());
+ float bottomFraction = Math.min(1f,
+ 1 - (mLongScreenshot.getBottom() - mLongScreenshot.getPageHeight())
+ / (float) mLongScreenshot.getHeight());
+ mCropView.animateBoundaryTo(CropView.CropBoundary.TOP, topFraction);
+ mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, bottomFraction);
+ setButtonsEnabled(true);
+
+ // Immediately export to temp image file for saved state
+ mCacheSaveFuture = mImageExporter.exportAsTempFile(mBackgroundExecutor,
+ mLongScreenshot.toBitmap());
+ mCacheSaveFuture.addListener(() -> {
+ try {
+ // Get the temp file path to persist, used in onSavedInstanceState
+ mSavedImagePath = mCacheSaveFuture.get();
+ } catch (CancellationException | InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error saving temp image file", e);
+ finishAndRemoveTask();
+ }
+ }, mUiExecutor);
+ }
+
+ private void onCachedImageLoaded(ImageLoader.Result imageResult) {
+ Log.d(TAG, "onCachedImageLoaded(imageResult=" + imageResult + ")");
+ BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
+ mPreview.setImageDrawable(drawable);
+ mMagnifierView.setDrawable(drawable, imageResult.bitmap.getWidth(),
+ imageResult.bitmap.getHeight());
+ mSavedImagePath = imageResult.fileName;
+
+ setButtonsEnabled(true);
+ }
+
+ private static Bitmap renderBitmap(Drawable drawable, Rect bounds) {
+ final RenderNode output = new RenderNode("Bitmap Export");
+ output.setPosition(0, 0, bounds.width(), bounds.height());
+ RecordingCanvas canvas = output.beginRecording();
+ canvas.translate(-bounds.left, -bounds.top);
+ canvas.clipRect(bounds);
+ drawable.draw(canvas);
+ output.endRecording();
+ return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
+ }
+
@Override
protected void onSaveInstanceState(Bundle outState) {
+ Log.d(TAG, "onSaveInstanceState");
super.onSaveInstanceState(outState);
- outState.putString(IMAGE_PATH_KEY, mSavedImagePath);
- outState.putFloat(TOP_BOUNDARY_KEY, mCropView.getTopBoundary());
- outState.putFloat(BOTTOM_BOUNDARY_KEY, mCropView.getBottomBoundary());
+ if (mSavedImagePath != null) {
+ outState.putString(KEY_SAVED_IMAGE_PATH, mSavedImagePath.getPath());
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ Log.d(TAG, "onPause finishing=" + isFinishing());
+ super.onPause();
+ if (isFinishing()) {
+ if (mScrollCaptureResponse != null) {
+ mScrollCaptureResponse.close();
+ }
+ cleanupCache();
+
+ if (mLongScreenshotFuture != null) {
+ mLongScreenshotFuture.cancel(true);
+ }
+ if (mLongScreenshot != null) {
+ mLongScreenshot.release();
+ }
+ }
+ }
+
+ void cleanupCache() {
+ if (mCacheSaveFuture != null) {
+ mCacheSaveFuture.cancel(true);
+ }
+ if (mSavedImagePath != null) {
+ Log.d(TAG, "Deleting " + mSavedImagePath);
+ //noinspection ResultOfMethodCallIgnored
+ mSavedImagePath.delete();
+ mSavedImagePath = null;
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ Log.d(TAG, "onStop");
+ super.onStop();
}
@Override
protected void onDestroy() {
+ Log.d(TAG, "onDestroy");
super.onDestroy();
- if (isFinishing() && !TextUtils.isEmpty(mSavedImagePath)) {
- Log.d(TAG, "Deleting " + mSavedImagePath);
- File file = new File(mSavedImagePath);
- file.delete();
- }
}
private void setButtonsEnabled(boolean enabled) {
@@ -244,67 +362,51 @@
}
private void startExport(PendingAction action) {
+ Log.d(TAG, "startExport(action = " + action + ")");
Drawable drawable = mPreview.getDrawable();
+ if (drawable == null) {
+ Log.e(TAG, "No drawable, skipping export!");
+ return;
+ }
- Rect croppedPortion = new Rect(
- 0,
- (int) (drawable.getIntrinsicHeight() * mCropView.getTopBoundary()),
- drawable.getIntrinsicWidth(),
- (int) (drawable.getIntrinsicHeight() * mCropView.getBottomBoundary()));
+ 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());
+
+ if (bounds.isEmpty()) {
+ Log.w(TAG, "Crop bounds empty, skipping export.");
+ return;
+ }
+
+ Bitmap output = renderBitmap(mPreview.getDrawable(), bounds);
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
- mBackgroundExecutor, UUID.randomUUID(), getBitmap(croppedPortion, drawable),
- ZonedDateTime.now());
- exportFuture.addListener(() -> {
- try {
- ImageExporter.Result result = exportFuture.get();
- setButtonsEnabled(true);
- switch (action) {
- case EDIT:
- doEdit(result.uri);
- break;
- case SHARE:
- doShare(result.uri);
- break;
- case SAVE:
- // Nothing more to do
- finishAndRemoveTask();
- break;
- }
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "failed to export", e);
- setButtonsEnabled(true);
- }
- }, mUiExecutor);
+ mBackgroundExecutor, UUID.randomUUID(), output, ZonedDateTime.now());
+ exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
}
- private Bitmap getBitmap(Rect bounds, Drawable drawable) {
- final RenderNode output = new RenderNode("Bitmap Export");
- output.setPosition(0, 0, bounds.width(), bounds.height());
- RecordingCanvas canvas = output.beginRecording();
- // Translating the canvas instead of setting drawable bounds since the drawable is still
- // used in the preview.
- canvas.translate(0, -bounds.top);
- drawable.draw(canvas);
- output.endRecording();
- return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
- }
-
- private void saveCacheBitmap(ImageTileSet tileSet) {
- long startTime = SystemClock.uptimeMillis();
- Bitmap bitmap = tileSet.toBitmap();
- // TODO(b/181562529) Remove this
- mPreview.setImageDrawable(tileSet.getDrawable());
+ private void onExportCompleted(PendingAction action,
+ ListenableFuture<ImageExporter.Result> exportFuture) {
+ setButtonsEnabled(true);
+ ImageExporter.Result result;
try {
- File file = File.createTempFile("long_screenshot", ".png", null);
- FileOutputStream stream = new FileOutputStream(file);
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
- stream.flush();
- stream.close();
- mSavedImagePath = file.getAbsolutePath();
- Log.d(TAG, "Saved to " + file.getAbsolutePath() + " in "
- + (SystemClock.uptimeMillis() - startTime) + "ms");
- } catch (IOException e) {
- Log.e(TAG, "Failed to save bitmap", e);
+ result = exportFuture.get();
+ } catch (CancellationException | InterruptedException | ExecutionException e) {
+ Log.e(TAG, "failed to export", e);
+ return;
+ }
+
+ switch (action) {
+ case EDIT:
+ doEdit(result.uri);
+ break;
+ case SHARE:
+ doShare(result.uri);
+ break;
+ case SAVE:
+ // Nothing more to do
+ finishAndRemoveTask();
+ break;
}
}
@@ -313,8 +415,8 @@
if (drawable == null) {
return;
}
-
- float imageRatio = drawable.getBounds().width() / (float) drawable.getBounds().height();
+ Rect bounds = drawable.getBounds();
+ float imageRatio = bounds.width() / (float) bounds.height();
float viewRatio = mPreview.getWidth() / (float) mPreview.getHeight();
if (imageRatio > viewRatio) {
@@ -328,35 +430,4 @@
mCropView.setExtraPadding(0, 0);
}
}
-
- private void doCapture() {
- mScrollCaptureController.start(mConnection,
- new ScrollCaptureController.ScrollCaptureCallback() {
- @Override
- public void onError() {
- Log.e(TAG, "Error capturing long screenshot!");
- finishAndRemoveTask();
- }
-
- @Override
- public void onComplete(ImageTileSet imageTileSet, int pageSize) {
- Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x "
- + imageTileSet.getHeight());
- mPreview.setImageDrawable(imageTileSet.getDrawable());
- updateCropLocation();
- mMagnifierView.setDrawable(imageTileSet.getDrawable(),
- imageTileSet.getWidth(), imageTileSet.getHeight());
- // Original boundaries go from the image tile set's y=0 to y=pageSize, so
- // we animate to that as a starting crop position.
- float topFraction = Math.max(0,
- -imageTileSet.getTop() / (float) imageTileSet.getHeight());
- float bottomFraction = Math.min(1f,
- 1 - (imageTileSet.getBottom() - pageSize)
- / (float) imageTileSet.getHeight());
- mCropView.animateBoundaryTo(CropView.CropBoundary.TOP, topFraction);
- mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, bottomFraction);
- mBackgroundExecutor.execute(() -> saveCacheBitmap(imageTileSet));
- }
- });
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 3d6dea3..c1ae292 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -25,6 +25,7 @@
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;
@@ -65,6 +66,7 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.ScrollCaptureResponse;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -86,10 +88,15 @@
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
import com.android.systemui.util.DeviceConfigProxy;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -101,7 +108,8 @@
public class ScreenshotController {
private static final String TAG = logTag(ScreenshotController.class);
- private static ScrollCaptureClient.Connection sScrollConnection;
+ private ScrollCaptureResponse mLastScrollCaptureResponse;
+ private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
/**
* POD used in the AsyncTask which saves an image in the background.
@@ -222,12 +230,6 @@
| ActivityInfo.CONFIG_SCREEN_LAYOUT
| ActivityInfo.CONFIG_ASSETS_PATHS);
- public static @Nullable ScrollCaptureClient.Connection takeScrollCaptureConnection() {
- ScrollCaptureClient.Connection connection = sScrollConnection;
- sScrollConnection = null;
- return connection;
- }
-
@Inject
ScreenshotController(
Context context,
@@ -352,6 +354,11 @@
} else {
mScreenshotView.animateDismissal();
}
+
+ if (mLastScrollCaptureResponse != null) {
+ mLastScrollCaptureResponse.close();
+ mLastScrollCaptureResponse = null;
+ }
}
boolean isPendingSharedTransition() {
@@ -526,16 +533,15 @@
// the reference used to locate the target window (below).
withWindowAttached(() -> {
mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken());
- mScrollCaptureClient.request(DEFAULT_DISPLAY,
- /* onConnection */
- (connection) -> mScreenshotHandler.post(() ->
- mScreenshotView.showScrollChip(() ->
- /* onClick */
- runScrollCapture(connection))));
+ if (mLastScrollCaptureRequest != null) {
+ mLastScrollCaptureRequest.cancel(true);
+ }
+ mLastScrollCaptureRequest = mScrollCaptureClient.request(DEFAULT_DISPLAY);
+ mLastScrollCaptureRequest.addListener(() ->
+ onScrollCaptureResponseReady(mLastScrollCaptureRequest), mMainExecutor);
});
}
-
attachWindow();
mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@@ -560,6 +566,38 @@
cancelTimeout(); // restarted after animation
}
+ private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {
+ try {
+ if (mLastScrollCaptureResponse != null) {
+ mLastScrollCaptureResponse.close();
+ }
+ mLastScrollCaptureResponse = responseFuture.get();
+ if (!mLastScrollCaptureResponse.isConnected()) {
+ // No connection means that the target window wasn't found
+ // or that it cannot support scroll capture.
+ Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
+ + mLastScrollCaptureResponse.getWindowTitle() + "]");
+ return;
+ }
+ Log.d(TAG, "ScrollCapture: connected to window ["
+ + mLastScrollCaptureResponse.getWindowTitle() + "]");
+ final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(LongScreenshotActivity.EXTRA_CAPTURE_RESPONSE,
+ mLastScrollCaptureResponse);
+ mScreenshotView.showScrollChip(/* onClick */ () -> {
+ // Clear the reference to prevent close() in dismissScreenshot
+ mLastScrollCaptureResponse = null;
+ mContext.startActivity(intent);
+ dismissScreenshot(false);
+ });
+ } catch (CancellationException e) {
+ // Ignore
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "requestScrollCapture failed", e);
+ }
+ }
+
private void withWindowAttached(Runnable action) {
View decorView = mWindow.getDecorView();
if (decorView.isAttachedToWindow()) {
@@ -606,15 +644,6 @@
}
}
- private void runScrollCapture(ScrollCaptureClient.Connection connection) {
- sScrollConnection = connection; // For LongScreenshotActivity to pick up.
-
- Intent intent = new Intent(mContext, LongScreenshotActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- dismissScreenshot(false);
- }
-
/**
* Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
* failure).
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index 54b99bb..926d5c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,18 +30,23 @@
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
+import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.util.Log;
import android.view.IScrollCaptureCallbacks;
import android.view.IScrollCaptureConnection;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindowManager;
import android.view.ScrollCaptureResponse;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
+
import com.android.internal.annotations.VisibleForTesting;
-import java.util.function.Consumer;
+import com.google.common.util.concurrent.ListenableFuture;
import javax.inject.Inject;
@@ -57,61 +62,6 @@
private static final String TAG = LogConfig.logTag(ScrollCaptureClient.class);
-
- /**
- * A connection to a remote window. Starts a capture session.
- */
- public interface Connection {
- /**
- * Start a session.
-
- * @param sessionConsumer listener to receive the session once active
- * @param maxPages the capture buffer size expressed as a multiple of the content height
- */
- // TODO ListenableFuture
- void start(Consumer<Session> sessionConsumer, float maxPages);
-
- /**
- * Close the connection. Must end capture if started to avoid potential unwanted visual
- * artifacts.
- *
- * @see Session#end(Runnable)
- */
- void close();
- }
-
- static class CaptureResult {
- public final Image image;
- /**
- * The area requested, in content rect space, relative to scroll-bounds.
- */
- public final Rect requested;
- /**
- * The actual area captured, in content rect space, relative to scroll-bounds. This may be
- * cropped or empty depending on available content.
- */
- public final Rect captured;
-
- // Error?
-
- private CaptureResult(Image image, Rect request, Rect captured) {
- this.image = image;
- this.requested = request;
- this.captured = captured;
- }
-
- @Override
- public String toString() {
- return "CaptureResult{"
- + "requested=" + requested
- + " (" + requested.width() + "x" + requested.height() + ")"
- + ", captured=" + captured
- + " (" + captured.width() + "x" + captured.height() + ")"
- + ", image=" + image
- + '}';
- }
- }
-
/**
* Represents the connection to a target window and provides a mechanism for requesting tiles.
*/
@@ -121,10 +71,8 @@
* and from left 0, to {@link #getPageWidth()}
*
* @param top the top (y) position of the tile to capture, in content rect space
- * @param consumer listener to be informed of the result
*/
- // TODO ListenableFuture
- void requestTile(int top, Consumer<CaptureResult> consumer);
+ ListenableFuture<CaptureResult> requestTile(int top);
/**
* Returns the maximum number of tiles which may be requested and retained without
@@ -139,6 +87,7 @@
*/
int getTileHeight();
+
/**
* @return the height of scrollable content being captured
*/
@@ -155,11 +104,42 @@
Rect getWindowBounds();
/**
- * End the capture session, return the target app to original state. The listener
- * will be called when the target app is ready to before visible and interactive.
+ * End the capture session, return the target app to original state. The returned Future
+ * will complete once the target app is ready to become visible and interactive.
*/
- // TODO ListenableFuture
- void end(Runnable listener);
+ ListenableFuture<Void> end();
+
+ void release();
+ }
+
+ static class CaptureResult {
+ public final Image image;
+ /**
+ * The area requested, in content rect space, relative to scroll-bounds.
+ */
+ public final Rect requested;
+ /**
+ * The actual area captured, in content rect space, relative to scroll-bounds. This may be
+ * cropped or empty depending on available content.
+ */
+ public final Rect captured;
+
+ CaptureResult(Image image, Rect request, Rect captured) {
+ this.image = image;
+ this.requested = request;
+ this.captured = captured;
+ }
+
+ @Override
+ public String toString() {
+ return "CaptureResult{"
+ + "requested=" + requested
+ + " (" + requested.width() + "x" + requested.height() + ")"
+ + ", captured=" + captured
+ + " (" + captured.width() + "x" + captured.height() + ")"
+ + ", image=" + image
+ + '}';
+ }
}
private final IWindowManager mWindowManagerService;
@@ -185,10 +165,9 @@
* Check for scroll capture support.
*
* @param displayId id for the display containing the target window
- * @param consumer receives a connection when available
*/
- public void request(int displayId, Consumer<Connection> consumer) {
- request(displayId, MATCH_ANY_TASK, consumer);
+ public ListenableFuture<ScrollCaptureResponse> request(int displayId) {
+ return request(displayId, MATCH_ANY_TASK);
}
/**
@@ -196,195 +175,210 @@
*
* @param displayId id for the display containing the target window
* @param taskId id for the task containing the target window or {@link #MATCH_ANY_TASK}.
- * @param consumer receives a connection when available
+ * @return a listenable future providing the response
*/
- public void request(int displayId, int taskId, Consumer<Connection> consumer) {
- try {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "requestScrollCapture(displayId=" + displayId + ", " + mHostWindowToken
- + ", taskId=" + taskId + ", consumer=" + consumer + ")");
+ public ListenableFuture<ScrollCaptureResponse> request(int displayId, int taskId) {
+ return CallbackToFutureAdapter.getFuture((completer) -> {
+ try {
+ mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId,
+ new IScrollCaptureResponseListener.Stub() {
+ @Override
+ public void onScrollCaptureResponse(ScrollCaptureResponse response) {
+ completer.set(response);
+ }
+ });
+
+ } catch (RemoteException e) {
+ completer.setException(e);
}
- mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId,
- new ClientCallbacks(consumer));
- } catch (RemoteException e) {
- Log.e(TAG, "Ignored remote exception", e);
- }
+ return "ScrollCaptureClient#request"
+ + "(displayId=" + displayId + ", taskId=" + taskId + ")";
+ });
}
- private static class ClientCallbacks extends IScrollCaptureCallbacks.Stub implements
- Connection, Session, IBinder.DeathRecipient {
+ /**
+ * Start a scroll capture session.
+ *
+ * @param response a response provided from a request containing a connection
+ * @param maxPages the capture buffer size expressed as a multiple of the content height
+ * @return a listenable future providing the session
+ */
+ public ListenableFuture<Session> start(ScrollCaptureResponse response, float maxPages) {
+ IScrollCaptureConnection connection = response.getConnection();
+ return CallbackToFutureAdapter.getFuture((completer) -> {
+ if (connection == null || !connection.asBinder().isBinderAlive()) {
+ completer.setException(new DeadObjectException("No active connection!"));
+ return "";
+ }
+ SessionWrapper session = new SessionWrapper(connection, response.getWindowBounds(),
+ response.getBoundsInWindow(), maxPages);
+ session.start(completer);
+ return "IScrollCaptureCallbacks#onCaptureStarted";
+ });
+ }
+
+ private static class SessionWrapper extends IScrollCaptureCallbacks.Stub implements Session,
+ IBinder.DeathRecipient {
private IScrollCaptureConnection mConnection;
- private Consumer<Connection> mConnectionConsumer;
- private Consumer<Session> mSessionConsumer;
- private Consumer<CaptureResult> mResultConsumer;
- private Runnable mShutdownListener;
private ImageReader mReader;
- private Rect mScrollBounds;
- private int mTileHeight;
- private int mTileWidth;
+ private final int mTileHeight;
+ private final int mTileWidth;
private Rect mRequestRect;
private boolean mStarted;
private ICancellationSignal mCancellationSignal;
- private Rect mWindowBounds;
- private Rect mBoundsInWindow;
- private int mMaxTiles;
+ private final Rect mWindowBounds;
+ private final Rect mBoundsInWindow;
+ private final int mMaxTiles;
- private ClientCallbacks(Consumer<Connection> connectionConsumer) {
- mConnectionConsumer = connectionConsumer;
- }
+ private Completer<Session> mStartCompleter;
+ private Completer<CaptureResult> mTileRequestCompleter;
+ private Completer<Void> mEndCompleter;
- @BinderThread
- @Override
- public void onScrollCaptureResponse(ScrollCaptureResponse response) throws RemoteException {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "onScrollCaptureResponse(response=" + response + ")");
- }
- if (response.isConnected()) {
- mConnection = response.getConnection();
- mConnection.asBinder().linkToDeath(this, 0);
- mWindowBounds = response.getWindowBounds();
- mBoundsInWindow = response.getBoundsInWindow();
+ private SessionWrapper(IScrollCaptureConnection connection, Rect windowBounds,
+ Rect boundsInWindow, float maxPages) throws RemoteException {
+ mConnection = requireNonNull(connection);
+ mConnection.asBinder().linkToDeath(SessionWrapper.this, 0);
+ mWindowBounds = requireNonNull(windowBounds);
+ mBoundsInWindow = requireNonNull(boundsInWindow);
- int pxPerPage = mBoundsInWindow.width() * mBoundsInWindow.height();
- int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE));
- mTileWidth = mBoundsInWindow.width();
- mTileHeight = pxPerTile / mBoundsInWindow.width();
- if (DEBUG_SCROLL) {
- Log.d(TAG, "boundsInWindow: " + mBoundsInWindow);
- Log.d(TAG, "tile size: " + mTileWidth + "x" + mTileHeight);
- Log.d(TAG, "maxHeight: " + (mMaxTiles * mTileHeight) + "px");
- }
- mConnectionConsumer.accept(this);
- }
- mConnectionConsumer = null;
- }
+ int pxPerPage = mBoundsInWindow.width() * mBoundsInWindow.height();
+ int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE));
- @Override
- public void start(Consumer<Session> sessionConsumer, float maxPages) {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ","
- + " maxPages=" + maxPages + ")");
- }
+ mTileWidth = mBoundsInWindow.width();
+ mTileHeight = pxPerTile / mBoundsInWindow.width();
mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE);
+
+ if (DEBUG_SCROLL) {
+ Log.d(TAG, "boundsInWindow: " + mBoundsInWindow);
+ Log.d(TAG, "tile size: " + mTileWidth + "x" + mTileHeight);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, "binderDied! The target process just crashed :-(");
+ // Clean up
+ mConnection = null;
+
+ // Pass along the bad news.
+ if (mStartCompleter != null) {
+ mStartCompleter.setException(new DeadObjectException("The remote process died"));
+ }
+ if (mTileRequestCompleter != null) {
+ mTileRequestCompleter.setException(
+ new DeadObjectException("The remote process died"));
+ }
+ if (mEndCompleter != null) {
+ mEndCompleter.setException(new DeadObjectException("The remote process died"));
+ }
+ }
+
+ private void start(Completer<Session> completer) {
mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
- mSessionConsumer = sessionConsumer;
-
+ mStartCompleter = completer;
try {
- mCancellationSignal = mConnection.startCapture(mReader.getSurface());
+ mCancellationSignal = mConnection.startCapture(mReader.getSurface(), this);
+ completer.addCancellationListener(() -> {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }, Runnable::run);
mStarted = true;
} catch (RemoteException e) {
- Log.w(TAG, "Failed to start", e);
mReader.close();
+ completer.setException(e);
}
}
@BinderThread
@Override
public void onCaptureStarted() {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "onCaptureStarted()");
- }
- mSessionConsumer.accept(this);
- mSessionConsumer = null;
+ Log.d(TAG, "onCaptureStarted");
+ mStartCompleter.set(this);
}
@Override
- public void requestTile(int top, Consumer<CaptureResult> consumer) {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")");
- }
- cancelPendingRequest();
+ public ListenableFuture<CaptureResult> requestTile(int top) {
mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight);
- mResultConsumer = consumer;
- try {
- mCancellationSignal = mConnection.requestImage(mRequestRect);
- } catch (RemoteException e) {
- Log.e(TAG, "Caught remote exception from requestImage", e);
- }
+ return CallbackToFutureAdapter.getFuture((completer -> {
+ if (mConnection == null || !mConnection.asBinder().isBinderAlive()) {
+ completer.setException(new DeadObjectException("Connection is closed!"));
+ return "";
+ }
+ try {
+ mTileRequestCompleter = completer;
+ mCancellationSignal = mConnection.requestImage(mRequestRect);
+ completer.addCancellationListener(() -> {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }, Runnable::run);
+ } catch (RemoteException e) {
+ completer.setException(e);
+ }
+ return "IScrollCaptureCallbacks#onImageRequestCompleted";
+ }));
}
+ @BinderThread
@Override
public void onImageRequestCompleted(int flags, Rect contentArea) {
Image image = mReader.acquireLatestImage();
- if (DEBUG_SCROLL) {
- Log.d(TAG, "onCaptureBufferSent(flags=" + flags
- + ", contentArea=" + contentArea + ") image=" + image);
- }
- // Save and clear first, since the consumer will likely request the next
- // tile, otherwise the new consumer will be wiped out.
- Consumer<CaptureResult> consumer = mResultConsumer;
- mResultConsumer = null;
- consumer.accept(new CaptureResult(image, mRequestRect, contentArea));
+ mTileRequestCompleter.set(new CaptureResult(image, mRequestRect, contentArea));
}
@Override
- public void end(Runnable listener) {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "end(listener=" + listener + ")");
- }
- if (mStarted) {
- mShutdownListener = listener;
- mReader.close();
+ public ListenableFuture<Void> end() {
+ Log.d(TAG, "end()");
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ if (!mStarted) {
+ try {
+ mConnection.asBinder().unlinkToDeath(SessionWrapper.this, 0);
+ mConnection.close();
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ mConnection = null;
+ completer.set(null);
+ return "";
+ }
+
+ mEndCompleter = completer;
try {
- // listener called from onConnectionClosed callback
mConnection.endCapture();
} catch (RemoteException e) {
- Log.d(TAG, "Ignored exception from endCapture()", e);
- disconnect();
- listener.run();
+ completer.setException(e);
}
- } else {
- disconnect();
- listener.run();
- }
+ return "IScrollCaptureCallbacks#onCaptureEnded";
+ });
+ }
+
+ public void release() {
+ mReader.close();
}
@BinderThread
@Override
public void onCaptureEnded() {
- close();
- if (mShutdownListener != null) {
- mShutdownListener.run();
- mShutdownListener = null;
+ try {
+ mConnection.close();
+ } catch (RemoteException e) {
+ /* ignore */
}
- }
-
- @Override
- public void close() {
- if (mConnection != null) {
- try {
- mConnection.close();
- } catch (RemoteException e) {
- /* ignore */
- }
- disconnect();
- }
+ mConnection = null;
+ mEndCompleter.set(null);
}
// Misc
- private void disconnect() {
- if (mConnection != null) {
- mConnection.asBinder().unlinkToDeath(this, 0);
- }
- mConnection = null;
- }
-
- /**
- * The process hosting the window went away abruptly!
- */
- @Override
- public void binderDied() {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "binderDied()");
- }
- disconnect();
- }
-
@Override
public int getPageHeight() {
return mBoundsInWindow.height();
@@ -408,16 +402,5 @@
public int getMaxTiles() {
return mMaxTiles;
}
-
- private void cancelPendingRequest() {
- if (mCancellationSignal != null) {
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException e) {
- /* ignore */
- }
- mCancellationSignal = null;
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 34094cd..d3dd048 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,20 +16,28 @@
package com.android.systemui.screenshot;
-import android.annotation.UiThread;
import android.content.Context;
-import android.net.Uri;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.util.Log;
+import android.view.ScrollCaptureResponse;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
+
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
-import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
-import java.time.ZonedDateTime;
-import java.util.UUID;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
/**
* Interaction controller between the UI and ScrollCaptureClient.
*/
@@ -47,44 +55,138 @@
// or if the desired bitmap size is reached.
private boolean mFinishOnBoundary;
- private Session mSession;
-
public static final int MAX_HEIGHT = 12000;
private final Context mContext;
-
- private final Executor mUiExecutor;
private final Executor mBgExecutor;
- private final ImageExporter mImageExporter;
private final ImageTileSet mImageTileSet;
+ private final ScrollCaptureClient mClient;
- private ZonedDateTime mCaptureTime;
- private UUID mRequestId;
- private ScrollCaptureCallback mCaptureCallback;
+ private Completer<LongScreenshot> mCaptureCompleter;
- public ScrollCaptureController(Context context, Executor uiExecutor, Executor bgExecutor,
- ImageExporter exporter) {
+ private ListenableFuture<Session> mSessionFuture;
+ private Session mSession;
+ private ListenableFuture<CaptureResult> mTileFuture;
+ private ListenableFuture<Void> mEndFuture;
+
+ static class LongScreenshot {
+ private final ImageTileSet mImageTileSet;
+ private final Session mSession;
+
+ LongScreenshot(Session session, ImageTileSet imageTileSet) {
+ mSession = session;
+ mImageTileSet = imageTileSet;
+ }
+
+ /** Returns a bitmap containing the combinded result. */
+ public Bitmap toBitmap() {
+ return mImageTileSet.toBitmap();
+ }
+
+ public Bitmap toBitmap(Rect bounds) {
+ return mImageTileSet.toBitmap(bounds);
+ }
+
+ /** Releases image resources from the screenshot. */
+ public void release() {
+ Log.d(TAG, "LongScreenshot :: release()");
+ mImageTileSet.clear();
+ mSession.release();
+ }
+
+ public int getLeft() {
+ return mImageTileSet.getLeft();
+ }
+
+ public int getTop() {
+ return mImageTileSet.getTop();
+ }
+
+ public int getBottom() {
+ return mImageTileSet.getBottom();
+ }
+
+ public int getWidth() {
+ return mImageTileSet.getWidth();
+ }
+
+ public int getHeight() {
+ return mImageTileSet.getHeight();
+ }
+
+ /** @return the height of the visible area of the scrolling page, in pixels */
+ public int getPageHeight() {
+ return mSession.getPageHeight();
+ }
+
+ @Override
+ public String toString() {
+ return "LongScreenshot{w=" + mImageTileSet.getWidth()
+ + ", h=" + mImageTileSet.getHeight() + "}";
+ }
+
+ public Drawable getDrawable() {
+ return mImageTileSet.getDrawable();
+ }
+ }
+
+ @Inject
+ ScrollCaptureController(Context context, @Background Executor bgExecutor,
+ ScrollCaptureClient client, ImageTileSet imageTileSet) {
mContext = context;
- mUiExecutor = uiExecutor;
mBgExecutor = bgExecutor;
- mImageExporter = exporter;
- mImageTileSet = new ImageTileSet(context.getMainThreadHandler());
+ mClient = client;
+ mImageTileSet = imageTileSet;
}
/**
- * Run scroll capture!
+ * Run scroll capture. Performs a batch capture, collecting image tiles.
*
- * @param connection connection to the remote window to be used
- * @param callback request callback to report back to the service
+ * @param response a scroll capture response from a previous request which is
+ * {@link ScrollCaptureResponse#isConnected() connected}.
+ * @return a future ImageTile set containing the result
*/
- public void start(Connection connection, ScrollCaptureCallback callback) {
- mCaptureTime = ZonedDateTime.now();
- mRequestId = UUID.randomUUID();
- mCaptureCallback = callback;
+ ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
+ Log.d(TAG, "run: " + response);
+ return CallbackToFutureAdapter.getFuture(completer -> {
+ Log.d(TAG, "getFuture(ImageTileSet) ");
+ mCaptureCompleter = completer;
+ mBgExecutor.execute(() -> {
+ Log.d(TAG, "bgExecutor.execute");
+ float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
+ SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
+ Log.d(TAG, "client start, maxPages=" + maxPages);
+ mSessionFuture = mClient.start(response, maxPages);
+ mSessionFuture.addListener(this::onStartComplete, mContext.getMainExecutor());
+ });
+ return "<batch scroll capture>";
+ });
+ }
- float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
- SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
- connection.start(this::startCapture, maxPages);
+ private void onStartComplete() {
+ try {
+ mSession = mSessionFuture.get();
+ Log.d(TAG, "got session " + mSession);
+ requestNextTile(0);
+ } catch (InterruptedException | ExecutionException e) {
+ // Failure to start, propagate to caller
+ Log.d(TAG, "session start failed!");
+ mCaptureCompleter.setException(e);
+ }
+ }
+
+ private void requestNextTile(int topPx) {
+ Log.d(TAG, "requestNextTile: " + topPx);
+ mTileFuture = mSession.requestTile(topPx);
+ mTileFuture.addListener(() -> {
+ try {
+ Log.d(TAG, "onCaptureResult");
+ onCaptureResult(mTileFuture.get());
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "requestTile failed!", e);
+ mCaptureCompleter.setException(e);
+ }
+ }, mContext.getMainExecutor());
}
private void onCaptureResult(CaptureResult result) {
@@ -146,49 +248,26 @@
}
if (finish) {
- Session session = mSession;
- mSession = null;
Log.d(TAG, "Stop.");
- mUiExecutor.execute(() -> afterCaptureComplete(session));
+ finishCapture();
return;
}
- int nextTop = (mScrollingUp)
- ? result.captured.top - mSession.getTileHeight() : result.captured.bottom;
- Log.d(TAG, "requestTile: " + nextTop);
- mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult);
+ // Partial or empty results caused the direction the flip, so we can reliably use the
+ // requested edges to determine the next top.
+ int nextTop = (mScrollingUp) ? result.requested.top - mSession.getTileHeight()
+ : result.requested.bottom;
+ requestNextTile(nextTop);
}
- private void startCapture(Session session) {
- mSession = session;
- session.requestTile(0, this::onCaptureResult);
+ private void finishCapture() {
+ Log.d(TAG, "finishCapture()");
+ mEndFuture = mSession.end();
+ mEndFuture.addListener(() -> {
+ Log.d(TAG, "endCapture completed");
+ // Provide result to caller and complete the top-level future
+ // Caller is responsible for releasing this resource (ImageReader/HardwareBuffers)
+ mCaptureCompleter.set(new LongScreenshot(mSession, mImageTileSet));
+ }, mContext.getMainExecutor());
}
-
- @UiThread
- void afterCaptureComplete(Session session) {
- Log.d(TAG, "afterCaptureComplete");
-
- if (mImageTileSet.isEmpty()) {
- mCaptureCallback.onError();
- } else {
- mCaptureCallback.onComplete(mImageTileSet, session.getPageHeight());
- }
- }
-
- /**
- * Callback for image capture completion or error.
- */
- public interface ScrollCaptureCallback {
- void onComplete(ImageTileSet imageTileSet, int pageHeight);
- void onError();
- }
-
- /**
- * Callback for image export completion or error.
- */
- public interface ExportCallback {
- void onExportComplete(Uri outputUri);
- void onError();
- }
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 0599039..1ff30a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -37,6 +37,7 @@
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
+import android.hardware.fingerprint.IUdfpsHbmListener;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
import android.os.Bundle;
import android.os.Handler;
@@ -142,6 +143,7 @@
//TODO(b/169175022) Update name and when feature name is locked.
private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT;
private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
+ private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -286,21 +288,38 @@
IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed,
boolean requireConfirmation, int userId, String opPackageName,
- long operationId) { }
- default void onBiometricAuthenticated() { }
- default void onBiometricHelp(String message) { }
- default void onBiometricError(int modality, int error, int vendorCode) { }
- default void hideAuthenticationDialog() { }
+ long operationId) {
+ }
+
+ default void onBiometricAuthenticated() {
+ }
+
+ default void onBiometricHelp(String message) {
+ }
+
+ default void onBiometricError(int modality, int error, int vendorCode) {
+ }
+
+ default void hideAuthenticationDialog() {
+ }
+
+ /**
+ * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+ */
+ default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ }
/**
* @see IStatusBar#onDisplayReady(int)
*/
- default void onDisplayReady(int displayId) { }
+ default void onDisplayReady(int displayId) {
+ }
/**
* @see DisplayManager.DisplayListener#onDisplayRemoved(int)
*/
- default void onDisplayRemoved(int displayId) { }
+ default void onDisplayRemoved(int displayId) {
+ }
/**
* @see IStatusBar#onRecentsAnimationStateChanged(boolean)
@@ -893,6 +912,13 @@
}
@Override
+ public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
+ }
+ }
+
+ @Override
public void onDisplayReady(int displayId) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_DISPLAY_READY, displayId, 0).sendToTarget();
@@ -1286,7 +1312,7 @@
mCallbacks.get(i).onBiometricHelp((String) msg.obj);
}
break;
- case MSG_BIOMETRIC_ERROR:
+ case MSG_BIOMETRIC_ERROR: {
SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onBiometricError(
@@ -1297,11 +1323,17 @@
}
someArgs.recycle();
break;
+ }
case MSG_BIOMETRIC_HIDE:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).hideAuthenticationDialog();
}
break;
+ case MSG_SET_UDFPS_HBM_LISTENER:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
+ }
+ break;
case MSG_SHOW_CHARGING_ANIMATION:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showWirelessChargingAnimation(msg.arg1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 5de2aab..708bdfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -83,6 +83,10 @@
return mFlagReader.isEnabled(R.bool.flag_monet);
}
+ public boolean isQuickAccessWalletEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_wallet);
+ }
+
public boolean isNavigationBarOverlayEnabled() {
return mFlagReader.isEnabled(R.bool.flag_navigation_bar_overlay);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 34b29ca..2856ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -31,6 +31,7 @@
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Handler;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -69,8 +70,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Stream;
import dagger.Lazy;
@@ -630,24 +633,17 @@
Notification.Builder b = Notification.Builder
.recoverBuilder(mContext, sbn.getNotification().clone());
if (remoteInputText != null || uri != null) {
- RemoteInputHistoryItem[] oldHistoryItems = (RemoteInputHistoryItem[])
- sbn.getNotification().extras.getParcelableArray(
- Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
- RemoteInputHistoryItem[] newHistoryItems;
-
- if (oldHistoryItems == null) {
- newHistoryItems = new RemoteInputHistoryItem[1];
- } else {
- newHistoryItems = new RemoteInputHistoryItem[oldHistoryItems.length + 1];
- System.arraycopy(oldHistoryItems, 0, newHistoryItems, 1, oldHistoryItems.length);
- }
- RemoteInputHistoryItem newItem;
- if (uri != null) {
- newItem = new RemoteInputHistoryItem(mimeType, uri, remoteInputText);
- } else {
- newItem = new RemoteInputHistoryItem(remoteInputText);
- }
- newHistoryItems[0] = newItem;
+ RemoteInputHistoryItem newItem = uri != null
+ ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
+ : new RemoteInputHistoryItem(remoteInputText);
+ Parcelable[] oldHistoryItems = sbn.getNotification().extras
+ .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
+ ? Stream.concat(
+ Stream.of(newItem),
+ Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
+ .toArray(RemoteInputHistoryItem[]::new)
+ : new RemoteInputHistoryItem[] { newItem };
b.setRemoteInputHistory(newHistoryItems);
}
b.setShowRemoteInputSpinner(showSpinner);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 6ba5215..5219ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,16 +16,23 @@
package com.android.systemui.statusbar;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.text.format.DateFormat;
import android.util.FloatProperty;
import android.util.Log;
+import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
@@ -81,6 +88,8 @@
// Record the HISTORY_SIZE most recent states
private int mHistoryIndex = 0;
private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE];
+ // This is used by InteractionJankMonitor to get callback from HWUI.
+ private View mView;
/**
* If any of the system bars is hidden.
@@ -236,6 +245,11 @@
@Override
public void setDozeAmount(float dozeAmount, boolean animated) {
+ setAndInstrumentDozeAmount(null, dozeAmount, animated);
+ }
+
+ @Override
+ public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
if (animated && mDozeAmountTarget == dozeAmount) {
return;
@@ -244,6 +258,11 @@
}
}
+ // We don't need a new attached view if we already have one.
+ if ((mView == null || !mView.isAttachedToWindow())
+ && (view != null && view.isAttachedToWindow())) {
+ mView = view;
+ }
mDozeAmountTarget = dozeAmount;
if (animated) {
startDozeAnimation();
@@ -261,6 +280,22 @@
mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget);
mDarkAnimator.setInterpolator(Interpolators.LINEAR);
mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
+ mDarkAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelInteractionJankMonitor();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endInteractionJankMonitor();
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ beginInteractionJankMonitor();
+ }
+ });
mDarkAnimator.start();
}
@@ -277,6 +312,24 @@
}
}
+ private void beginInteractionJankMonitor() {
+ if (mView != null && mView.isAttachedToWindow()) {
+ InteractionJankMonitor.getInstance().begin(mView, getCujType());
+ }
+ }
+
+ private void endInteractionJankMonitor() {
+ InteractionJankMonitor.getInstance().end(getCujType());
+ }
+
+ private void cancelInteractionJankMonitor() {
+ InteractionJankMonitor.getInstance().cancel(getCujType());
+ }
+
+ private int getCujType() {
+ return mIsDozing ? CUJ_LOCKSCREEN_TRANSITION_TO_AOD : CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
+ }
+
@Override
public boolean goingToFullShade() {
return mState == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index a2e07b2..b6d6ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,6 +19,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.view.View;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -75,6 +76,15 @@
*/
void setDozeAmount(float dozeAmount, boolean animated);
+ /**
+ * Changes the current doze amount, also starts the
+ * {@link com.android.internal.jank.InteractionJankMonitor InteractionJankMonitor} as possible.
+ *
+ * @param view An attached view, which will be used by InteractionJankMonitor.
+ * @param dozeAmount New doze/dark amount.
+ * @param animated If change should be animated or not. This will cancel current animations.
+ */
+ void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated);
/**
* Update the expanded state from {@link StatusBar}'s perspective
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
index ce0a08c..1da42a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
@@ -192,9 +192,9 @@
val pref = args[0]
when (pref) {
- Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> {
+ Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S -> {
val value = Integer.parseInt(args[1])
- Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0)
+ Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, value != 0)
}
else -> {
pw.println("Cannot set pref ($pref)")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index dbd8580..5f93f480 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -41,11 +41,11 @@
import android.app.NotificationManager.Policy;
import android.app.Person;
import android.app.RemoteInput;
-import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Parcelable;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
@@ -534,8 +534,8 @@
return false;
}
Bundle extras = mSbn.getNotification().extras;
- RemoteInputHistoryItem[] replyTexts = (RemoteInputHistoryItem[]) extras.getParcelableArray(
- Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ Parcelable[] replyTexts =
+ extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
if (!ArrayUtils.isEmpty(replyTexts)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 0957f78..617dadb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -31,6 +31,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.FeatureFlags;
@@ -71,6 +72,7 @@
import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
@@ -133,6 +135,8 @@
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
+ NotificationEntryManager notificationEntryManager,
+ PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
ShortcutManager shortcutManager,
ChannelEditorDialogController channelEditorDialogController,
@@ -141,7 +145,8 @@
AssistantFeedbackController assistantFeedbackController,
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
- OnUserInteractionCallback onUserInteractionCallback) {
+ OnUserInteractionCallback onUserInteractionCallback,
+ ShadeController shadeController) {
return new NotificationGutsManager(
context,
statusBarLazy,
@@ -150,6 +155,8 @@
accessibilityManager,
highPriorityProvider,
notificationManager,
+ notificationEntryManager,
+ peopleSpaceWidgetManager,
launcherApps,
shortcutManager,
channelEditorDialogController,
@@ -158,7 +165,8 @@
assistantFeedbackController,
bubblesManagerOptional,
uiEventLogger,
- onUserInteractionCallback);
+ onUserInteractionCallback,
+ shadeController);
}
/** Provides an instance of {@link VisualStabilityManager} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 50bbc38..3f7b8af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -104,7 +104,7 @@
@Override
public boolean onTouch(View v, MotionEvent ev) {
- boolean result;
+ boolean result = false;
if (mBlockNextTouch) {
mBlockNextTouch = false;
return true;
@@ -112,16 +112,20 @@
if (ev.getAction() == MotionEvent.ACTION_UP) {
mView.setLastActionUpTime(SystemClock.uptimeMillis());
}
- if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled()
- && mView.isInteractive()) {
+ // With a11y, just do nothing.
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
+ return false;
+ }
+ if (mNeedsDimming && mView.isInteractive()) {
if (mNeedsDimming && !mView.isDimmed()) {
// We're actually dimmed, but our content isn't dimmable,
// let's ensure we have a ripple
return false;
}
result = mNotificationTapHelper.onTouchEvent(ev, mView.getActualHeight());
- } else {
- return false;
+ } else if (ev.getAction() == MotionEvent.ACTION_UP) {
+ // If this is a false tap, capture the even so it doesn't result in a click.
+ return mFalsingManager.isFalseTap(true, 0.1);
}
return result;
}
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 046fbd5..6cf5c30 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
@@ -77,6 +77,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
@@ -215,6 +216,7 @@
private NotificationGuts mGuts;
private NotificationEntry mEntry;
private String mAppName;
+ private FalsingManager mFalsingManager;
private FalsingCollector mFalsingCollector;
/**
@@ -887,6 +889,16 @@
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Other parts of the system may intercept and handle all the falsing.
+ // Otherwise, if we see motion and follow-on events, try to classify them as a tap.
+ if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ mFalsingManager.isFalseTap(true, 0.3);
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getActionMasked() != MotionEvent.ACTION_DOWN
|| !isChildInGroup() || isGroupExpanded()) {
@@ -1569,6 +1581,7 @@
OnExpandClickListener onExpandClickListener,
NotificationMediaManager notificationMediaManager,
CoordinateOnClickListener onFeedbackClickListener,
+ FalsingManager falsingManager,
FalsingCollector falsingCollector,
StatusBarStateController statusBarStateController,
PeopleNotificationIdentifier peopleNotificationIdentifier,
@@ -1594,6 +1607,7 @@
mOnExpandClickListener = onExpandClickListener;
mMediaManager = notificationMediaManager;
setOnFeedbackClickListener(onFeedbackClickListener);
+ mFalsingManager = falsingManager;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 0d0e97e..c9fcdac8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -26,6 +26,7 @@
import androidx.annotation.NonNull;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
@@ -78,6 +79,7 @@
private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
private final NotificationGutsManager mNotificationGutsManager;
private final OnUserInteractionCallback mOnUserInteractionCallback;
+ private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
private final boolean mAllowLongPress;
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@@ -104,6 +106,7 @@
NotificationGutsManager notificationGutsManager,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
OnUserInteractionCallback onUserInteractionCallback,
+ FalsingManager falsingManager,
FalsingCollector falsingCollector,
PeopleNotificationIdentifier peopleNotificationIdentifier,
Optional<BubblesManager> bubblesManagerOptional) {
@@ -125,6 +128,7 @@
mStatusBarStateController = statusBarStateController;
mNotificationGutsManager = notificationGutsManager;
mOnUserInteractionCallback = onUserInteractionCallback;
+ mFalsingManager = falsingManager;
mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
mAllowLongPress = allowLongPress;
mFalsingCollector = falsingCollector;
@@ -150,6 +154,7 @@
mOnExpandClickListener,
mMediaManager,
mOnFeedbackClickListener,
+ mFalsingManager,
mFalsingCollector,
mStatusBarStateController,
mPeopleNotificationIdentifier,
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 fdd8f34..58b87cd 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
@@ -27,8 +27,10 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.CancellationSignal;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.View;
@@ -768,10 +770,26 @@
return mReInflateFlags;
}
+ void updateApplicationInfo(StatusBarNotification sbn) {
+ String packageName = sbn.getPackageName();
+ int userId = UserHandle.getUserId(sbn.getUid());
+ final ApplicationInfo appInfo;
+ try {
+ // This method has an internal cache, so we don't need to add our own caching here.
+ appInfo = mContext.getPackageManager().getApplicationInfoAsUser(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+ Notification.addFieldsFromContext(appInfo, sbn.getNotification());
+ }
+
@Override
protected InflationProgress doInBackground(Void... params) {
try {
final StatusBarNotification sbn = mEntry.getSbn();
+ // Ensure the ApplicationInfo is updated before a builder is recovered.
+ updateApplicationInfo(sbn);
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
sbn.getNotification());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index d3065aa..55a27b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -22,8 +22,6 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -32,7 +30,6 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.NotificationHeaderView;
@@ -44,7 +41,6 @@
import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -349,7 +345,9 @@
invalidateOutline();
selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
mForceSelectNextLayout = false;
- updateExpandButtons(mExpandable);
+ // TODO(b/182314698): move this to onMeasure. This requires switching to getMeasuredHeight,
+ // and also requires revisiting all of the logic called earlier in this method.
+ updateExpandButtonsDuringLayout(mExpandable, true /* duringLayout */);
}
@Override
@@ -1272,23 +1270,6 @@
}
}
if (hasRemoteInput) {
- int color = entry.getSbn().getNotification().color;
- if (color == Notification.COLOR_DEFAULT) {
- color = mContext.getColor(R.color.default_remote_input_background);
- }
- if (mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_tintNotificationsWithTheme)) {
- Resources.Theme theme = new ContextThemeWrapper(mContext,
- com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme();
- TypedArray ta = theme.obtainStyledAttributes(
- new int[]{com.android.internal.R.attr.colorAccent});
- color = ta.getColor(0, color);
- ta.recycle();
- }
- existing.setBackgroundColor(ContrastColorUtil.ensureTextBackgroundColor(color,
- mContext.getColor(R.color.remote_input_text_enabled),
- mContext.getColor(R.color.remote_input_hint)));
-
existing.setWrapper(wrapper);
existing.setOnVisibilityChangedListener(this::setRemoteInputVisible);
@@ -1310,6 +1291,10 @@
}
}
}
+
+ if (existing != null && entry.getSbn().getNotification().isColorized()) {
+ existing.overrideBackgroundTintColor(entry.getSbn().getNotification().color);
+ }
return existing;
}
return null;
@@ -1344,9 +1329,7 @@
}
ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
- LinearLayout actionContainerLayout =
- layout.findViewById(com.android.internal.R.id.actions_container_layout);
- if (bubbleButton == null || actionContainer == null || actionContainerLayout == null) {
+ if (bubbleButton == null || actionContainer == null) {
return;
}
boolean isPersonWithShortcut =
@@ -1589,6 +1572,10 @@
}
public void updateExpandButtons(boolean expandable) {
+ updateExpandButtonsDuringLayout(expandable, false /* duringLayout */);
+ }
+
+ private void updateExpandButtonsDuringLayout(boolean expandable, boolean duringLayout) {
mExpandable = expandable;
// if the expanded child has the same height as the collapsed one we hide it.
if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
@@ -1602,14 +1589,15 @@
expandable = false;
}
}
+ boolean requestLayout = duringLayout && mIsContentExpandable != expandable;
if (mExpandedChild != null) {
- mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
+ mExpandedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
}
if (mContractedChild != null) {
- mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
+ mContractedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.updateExpandability(expandable, mExpandClickListener);
+ mHeadsUpWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
}
mIsContentExpandable = expandable;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index adeba90..b4ab8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -69,9 +69,11 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.wmshell.BubblesManager;
import java.lang.annotation.Retention;
@@ -86,15 +88,16 @@
NotificationGuts.GutsContent {
private static final String TAG = "ConversationGuts";
-
private INotificationManager mINotificationManager;
private ShortcutManager mShortcutManager;
private PackageManager mPm;
+ private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private ConversationIconFactory mIconFactory;
private OnUserInteractionCallback mOnUserInteractionCallback;
private Handler mMainHandler;
private Handler mBgHandler;
private Optional<BubblesManager> mBubblesManagerOptional;
+ private ShadeController mShadeController;
private String mPackageName;
private String mAppName;
private int mAppUid;
@@ -169,9 +172,16 @@
private OnClickListener mOnDone = v -> {
mPressedApply = true;
- // If the user selected Priority, maybe show the priority onboarding
+
+ // If the user selected Priority, maybe show the priority onboarding.
+ // If the user selected Priority and the previous selection was not priority, show a
+ // People Tile add request. If showing the priority onboarding, however, delay the request
+ // to when the onboarding dialog closes.
if (mSelectedAction == ACTION_FAVORITE && shouldShowPriorityOnboarding()) {
showPriorityOnboarding();
+ } else if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
+ mShadeController.animateCollapsePanels();
+ mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo);
}
mGutsContainer.closeControls(v, true);
};
@@ -209,6 +219,7 @@
@Action int selectedAction,
ShortcutManager shortcutManager,
PackageManager pm,
+ PeopleSpaceWidgetManager peopleSpaceWidgetManager,
INotificationManager iNotificationManager,
OnUserInteractionCallback onUserInteractionCallback,
String pkg,
@@ -224,10 +235,12 @@
@Main Handler mainHandler,
@Background Handler bgHandler,
OnConversationSettingsClickListener onConversationSettingsClickListener,
- Optional<BubblesManager> bubblesManagerOptional) {
+ Optional<BubblesManager> bubblesManagerOptional,
+ ShadeController shadeController) {
mPressedApply = false;
mSelectedAction = selectedAction;
mINotificationManager = iNotificationManager;
+ mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mOnUserInteractionCallback = onUserInteractionCallback;
mPackageName = pkg;
mEntry = entry;
@@ -245,6 +258,7 @@
mUserContext = userContext;
mBubbleMetadata = bubbleMetadata;
mBubblesManagerOptional = bubblesManagerOptional;
+ mShadeController = shadeController;
mBuilderProvider = builderProvider;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
@@ -527,7 +541,7 @@
}
private boolean shouldShowPriorityOnboarding() {
- return !Prefs.getBoolean(mUserContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, false);
+ return !Prefs.getBoolean(mUserContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, false);
}
private void showPriorityOnboarding() {
@@ -566,9 +580,11 @@
.setBadge(mIconFactory.getAppBadge(
mPackageName, UserHandle.getUserId(mSbn.getUid())))
.setOnSettingsClick(mOnConversationSettingsClickListener)
+ .setPeopleSpaceWidgetManager(mPeopleSpaceWidgetManager)
+ .setShadeController(mShadeController)
.build();
- controller.init();
+ controller.init(mShortcutInfo);
controller.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6a873b6..1a7f5b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -21,6 +21,7 @@
import android.app.INotificationManager;
import android.app.NotificationChannel;
+import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
@@ -49,6 +50,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
@@ -59,11 +61,13 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
@@ -120,11 +124,15 @@
private final Optional<BubblesManager> mBubblesManagerOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
+ private final NotificationEntryManager mNotificationEntryManager;
+ private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private final LauncherApps mLauncherApps;
private final ShortcutManager mShortcutManager;
private final UserContextProvider mContextTracker;
private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
private final UiEventLogger mUiEventLogger;
+ private final ShadeController mShadeController;
+ private final AppWidgetManager mAppWidgetManager;
/**
* Injected constructor. See {@link NotificationsModule}.
@@ -136,6 +144,8 @@
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
+ NotificationEntryManager notificationEntryManager,
+ PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
ShortcutManager shortcutManager,
ChannelEditorDialogController channelEditorDialogController,
@@ -144,7 +154,8 @@
AssistantFeedbackController assistantFeedbackController,
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
- OnUserInteractionCallback onUserInteractionCallback) {
+ OnUserInteractionCallback onUserInteractionCallback,
+ ShadeController shadeController) {
mContext = context;
mStatusBarLazy = statusBarLazy;
mMainHandler = mainHandler;
@@ -152,6 +163,8 @@
mAccessibilityManager = accessibilityManager;
mHighPriorityProvider = highPriorityProvider;
mNotificationManager = notificationManager;
+ mNotificationEntryManager = notificationEntryManager;
+ mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mLauncherApps = launcherApps;
mShortcutManager = shortcutManager;
mContextTracker = contextTracker;
@@ -161,6 +174,8 @@
mBubblesManagerOptional = bubblesManagerOptional;
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
+ mShadeController = shadeController;
+ mAppWidgetManager = AppWidgetManager.getInstance(context);
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -477,6 +492,7 @@
notificationInfoView.getSelectedAction(),
mShortcutManager,
pmUser,
+ mPeopleSpaceWidgetManager,
mNotificationManager,
mOnUserInteractionCallback,
packageName,
@@ -492,7 +508,8 @@
mMainHandler,
mBgHandler,
onConversationSettingsListener,
- mBubblesManagerOptional);
+ mBubblesManagerOptional,
+ mShadeController);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
index fab367d..ae1285d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
@@ -22,6 +22,7 @@
import android.animation.ValueAnimator
import android.app.Dialog
import android.content.Context
+import android.content.pm.ShortcutInfo
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
@@ -31,7 +32,6 @@
import android.text.style.BulletSpan
import android.view.Gravity
import android.view.View
-import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.Window
@@ -44,31 +44,36 @@
import com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN
import com.android.systemui.Prefs
import com.android.systemui.R
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.statusbar.notification.row.NotificationConversationInfo.OnConversationSettingsClickListener
+import com.android.systemui.statusbar.phone.ShadeController
import javax.inject.Inject
-
/**
* Controller to handle presenting the priority conversations onboarding dialog
*/
class PriorityOnboardingDialogController @Inject constructor(
- val view: View,
- val context: Context,
- private val ignoresDnd: Boolean,
- private val showsAsBubble: Boolean,
- val icon : Drawable,
- private val onConversationSettingsClickListener : OnConversationSettingsClickListener,
- val badge : Drawable
+ val view: View,
+ val context: Context,
+ private val ignoresDnd: Boolean,
+ private val showsAsBubble: Boolean,
+ val icon: Drawable,
+ private val onConversationSettingsClickListener: OnConversationSettingsClickListener,
+ val badge: Drawable,
+ private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
+ private val shadeController: ShadeController
) {
private lateinit var dialog: Dialog
+ private lateinit var shortcutInfo: ShortcutInfo
private val OVERSHOOT: Interpolator = PathInterpolator(0.4f, 0f, 0.2f, 1.4f)
private val IMPORTANCE_ANIM_DELAY = 150L
private val IMPORTANCE_ANIM_GROW_DURATION = 250L
private val IMPORTANCE_ANIM_SHRINK_DURATION = 200L
private val IMPORTANCE_ANIM_SHRINK_DELAY = 25L
- fun init() {
+ fun init(info: ShortcutInfo) {
+ shortcutInfo = info
initDialog()
}
@@ -78,13 +83,15 @@
private fun done() {
// Log that the user has seen the onboarding
- Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true)
+ Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true)
dialog.dismiss()
+ shadeController.animateCollapsePanels()
+ peopleSpaceWidgetManager.requestPinAppWidget(shortcutInfo)
}
private fun settings() {
// Log that the user has seen the onboarding
- Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true)
+ Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true)
dialog.dismiss()
onConversationSettingsClickListener?.onClick()
}
@@ -95,9 +102,11 @@
private var ignoresDnd = false
private var showAsBubble = false
private lateinit var icon: Drawable
- private lateinit var onConversationSettingsClickListener
- : OnConversationSettingsClickListener
- private lateinit var badge : Drawable
+ private lateinit var onConversationSettingsClickListener:
+ OnConversationSettingsClickListener
+ private lateinit var badge: Drawable
+ private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+ private lateinit var shadeController: ShadeController
fun setView(v: View): Builder {
view = v
@@ -119,24 +128,36 @@
return this
}
- fun setIcon(draw : Drawable) : Builder {
+ fun setIcon(draw: Drawable): Builder {
icon = draw
return this
}
- fun setBadge(badge : Drawable) : Builder {
+ fun setBadge(badge: Drawable): Builder {
this.badge = badge
return this
}
- fun setOnSettingsClick(onClick : OnConversationSettingsClickListener) : Builder {
+ fun setOnSettingsClick(onClick: OnConversationSettingsClickListener): Builder {
onConversationSettingsClickListener = onClick
return this
}
+ fun setShadeController(shadeController: ShadeController): Builder {
+ this.shadeController = shadeController
+ return this
+ }
+
+ fun setPeopleSpaceWidgetManager(peopleSpaceWidgetManager: PeopleSpaceWidgetManager):
+ Builder {
+ this.peopleSpaceWidgetManager = peopleSpaceWidgetManager
+ return this
+ }
+
fun build(): PriorityOnboardingDialogController {
val controller = PriorityOnboardingDialogController(
view, context, ignoresDnd, showAsBubble, icon,
- onConversationSettingsClickListener, badge)
+ onConversationSettingsClickListener, badge, peopleSpaceWidgetManager,
+ shadeController)
return controller
}
}
@@ -185,8 +206,8 @@
val bgSize = context.resources.getDimensionPixelSize(
com.android.internal.R.dimen.conversation_icon_size_badged)
- val animatorUpdateListener: ValueAnimator.AnimatorUpdateListener
- = ValueAnimator.AnimatorUpdateListener { animation ->
+ val animatorUpdateListener: ValueAnimator.AnimatorUpdateListener =
+ ValueAnimator.AnimatorUpdateListener { animation ->
val strokeWidth = animation.animatedValue as Int
ring.setStroke(strokeWidth, ringColor)
val newSize = baseSize + strokeWidth * 2
@@ -199,8 +220,8 @@
growAnimation.duration = IMPORTANCE_ANIM_GROW_DURATION
growAnimation.addUpdateListener(animatorUpdateListener)
- val shrinkAnimation: ValueAnimator
- = ValueAnimator.ofInt(largeThickness, standardThickness)
+ val shrinkAnimation: ValueAnimator =
+ ValueAnimator.ofInt(largeThickness, standardThickness)
shrinkAnimation.duration = IMPORTANCE_ANIM_SHRINK_DURATION
shrinkAnimation.startDelay = IMPORTANCE_ANIM_SHRINK_DELAY
shrinkAnimation.interpolator = OVERSHOOT
@@ -208,15 +229,14 @@
shrinkAnimation.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
// Shrink the badge bg so that it doesn't peek behind the animation
- bg.setSize(baseSize, baseSize);
- conversationIconBadgeBg.invalidate();
+ bg.setSize(baseSize, baseSize)
+ conversationIconBadgeBg.invalidate()
}
override fun onAnimationEnd(animation: Animator?) {
// Reset bg back to normal size
- bg.setSize(bgSize, bgSize);
- conversationIconBadgeBg.invalidate();
-
+ bg.setSize(bgSize, bgSize)
+ conversationIconBadgeBg.invalidate()
}
})
@@ -228,20 +248,20 @@
R.dimen.conversation_onboarding_bullet_gap_width)
val description = SpannableStringBuilder()
description.append(context.getText(R.string.priority_onboarding_show_at_top_text),
- BulletSpan(gapWidth), /* flags */0)
+ BulletSpan(gapWidth), /* flags */0)
description.append(System.lineSeparator())
description.append(context.getText(R.string.priority_onboarding_show_avatar_text),
- BulletSpan(gapWidth), /* flags */0)
+ BulletSpan(gapWidth), /* flags */0)
if (showsAsBubble) {
description.append(System.lineSeparator())
description.append(context.getText(
R.string.priority_onboarding_appear_as_bubble_text),
- BulletSpan(gapWidth), /* flags */0)
+ BulletSpan(gapWidth), /* flags */0)
}
if (ignoresDnd) {
description.append(System.lineSeparator())
description.append(context.getText(R.string.priority_onboarding_ignores_dnd_text),
- BulletSpan(gapWidth), /* flags */0)
+ BulletSpan(gapWidth), /* flags */0)
}
findViewById<TextView>(R.id.behaviors).setText(description)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index fb0fdcc..383bb7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -147,8 +147,11 @@
override fun setRemoteInputVisible(visible: Boolean) =
conversationLayout.showHistoricMessages(visible)
- override fun updateExpandability(expandable: Boolean, onClickListener: View.OnClickListener?) =
- conversationLayout.updateExpandability(expandable, onClickListener)
+ override fun updateExpandability(
+ expandable: Boolean,
+ onClickListener: View.OnClickListener,
+ requestLayout: Boolean
+ ) = conversationLayout.updateExpandability(expandable, onClickListener)
override fun disallowSingleClick(x: Float, y: Float): Boolean {
val isOnExpandButton = expandBtnContainer.visibility == View.VISIBLE &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index bdafd23..5a55545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -261,7 +261,8 @@
}
@Override
- public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+ boolean requestLayout) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mAltExpandTarget != null) {
@@ -273,6 +274,13 @@
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
+ // Unfortunately, the NotificationContentView has to layout its children in order to
+ // determine their heights, and that affects the button visibility. If that happens
+ // (thankfully it is rare) then we need to request layout of the expand button's parent
+ // in order to ensure it gets laid out correctly.
+ if (requestLayout) {
+ mExpandButton.getParent().requestLayout();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 9ced12d..3a7b461 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -291,8 +291,10 @@
*
* @param expandable should this view be expandable
* @param onClickListener the listener to invoke when the expand affordance is clicked on
+ * @param requestLayout the expandability changed during onLayout, so a requestLayout required
*/
- public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+ boolean requestLayout) {}
/** Set the expanded state on the view wrapper */
public void setExpanded(boolean expanded) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 2b194ba..1d30736 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -324,7 +324,7 @@
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
- RemoteViews header = builder.makeNotificationHeader();
+ RemoteViews header = builder.makeNotificationGroupHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
@@ -337,6 +337,7 @@
} else {
header.reapply(getContext(), mNotificationHeader);
}
+ mNotificationHeaderWrapper.setExpanded(mChildrenExpanded);
mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
NotificationHeaderViewWrapper headerWrapper =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 970efd5..f6c1b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -456,6 +456,7 @@
private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final FeatureFlags mFeatureFlags;
+ private boolean mShouldUseSplitNotificationShade;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
new ExpandableView.OnHeightChangedListener() {
@@ -500,7 +501,8 @@
super(context, attrs, 0, 0);
Resources res = getResources();
mSectionsManager = notificationSectionsManager;
-
+ mFeatureFlags = featureFlags;
+ mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res);
mSectionsManager.initialize(this, LayoutInflater.from(context));
mSections = mSectionsManager.createSectionsForBuckets();
@@ -533,7 +535,6 @@
mGroupMembershipManager = groupMembershipManager;
mGroupExpansionManager = groupExpansionManager;
mStatusbarStateController = statusbarStateController;
- mFeatureFlags = featureFlags;
}
void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) {
@@ -1164,7 +1165,7 @@
if (stackStartPosition <= stackEndPosition) {
stackHeight = stackEndPosition;
} else {
- if (shouldUseSplitNotificationShade(mFeatureFlags, getResources())) {
+ if (mShouldUseSplitNotificationShade) {
// This prevents notifications from being collapsed when QS is expanded.
stackHeight = (int) height;
} else {
@@ -1552,8 +1553,10 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
- float densityScale = getResources().getDisplayMetrics().density;
+ Resources res = getResources();
+ mShouldUseSplitNotificationShade = shouldUseSplitNotificationShade(mFeatureFlags, res);
+ mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
+ float densityScale = res.getDisplayMetrics().density;
mSwipeHelper.setDensityScale(densityScale);
float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index e6731e6..c60bbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -166,6 +166,7 @@
private int mGradientAlpha;
private int mColor;
+ private float mOverrideAlpha = 1f;
private PorterDuffColorFilter mTintFilter;
private Paint mPaint = new Paint();
@@ -195,6 +196,23 @@
mFrame = frame;
}
+ public void setOverrideAlpha(float overrideAlpha) {
+ mOverrideAlpha = overrideAlpha;
+ invalidateSelf();
+ }
+
+ public float getOverrideAlpha() {
+ return mOverrideAlpha;
+ }
+
+ public int getColor() {
+ return mColor;
+ }
+
+ public Rect getFrame() {
+ return mFrame;
+ }
+
@Override
public void setAlpha(int alpha) {
// noop
@@ -296,11 +314,13 @@
mGradient.setAlpha(mGradientAlpha);
mGradient.draw(canvas);
}
+
if (Color.alpha(mColor) > 0) {
mPaint.setColor(mColor);
if (mTintFilter != null) {
mPaint.setColorFilter(mTintFilter);
}
+ mPaint.setAlpha((int) (Color.alpha(mColor) * mOverrideAlpha));
if (mFrame != null) {
canvas.drawRect(mFrame, mPaint);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 713daaa..6b69103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -533,6 +533,17 @@
}
}
+ /**
+ * 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() {
+ if (mKeyguardViewController != null) {
+ mKeyguardViewController.updateResources();
+ }
+ }
+
public void dump(PrintWriter pw) {
pw.println("KeyguardBouncer");
pw.println(" isShowing(): " + isShowing());
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 ca6e53d..4ef6668 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -305,6 +305,7 @@
// Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
// If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
private final int mMaxKeyguardNotifications;
+ private boolean mShouldUseSplitNotificationShade;
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
@@ -598,6 +599,8 @@
mKeyguardUserSwitcherEnabled && mResources.getBoolean(
R.bool.config_keyguard_user_switch_opens_qs_details);
keyguardUpdateMonitor.setKeyguardQsUserSwitchEnabled(mKeyguardQsUserSwitchEnabled);
+ mShouldUseSplitNotificationShade =
+ Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources);
mView.setWillNotDraw(!DEBUG);
mLayoutInflater = layoutInflater;
mFalsingManager = falsingManager;
@@ -736,7 +739,7 @@
mView.setAccessibilityDelegate(mAccessibilityDelegate);
// dynamically apply the split shade value overrides.
- if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ if (mShouldUseSplitNotificationShade) {
updateResources();
}
}
@@ -835,12 +838,13 @@
public void updateResources() {
int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
-
+ mShouldUseSplitNotificationShade =
+ Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources);
// To change the constraints at runtime, all children of the ConstraintLayout must have ids
ensureAllViewsHaveIds(mNotificationContainerParent);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(mNotificationContainerParent);
- if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ if (mShouldUseSplitNotificationShade) {
// width = 0 to take up all available space within constraints
qsWidth = 0;
panelWidth = 0;
@@ -1915,7 +1919,7 @@
mBarState != KEYGUARD
&& (!mQsExpanded
|| mQsExpansionFromOverscroll
- || Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)));
+ || mShouldUseSplitNotificationShade));
if (mKeyguardUserSwitcherController != null && mQsExpanded
&& !mStackScrollerOverscrolling) {
@@ -1987,7 +1991,7 @@
private float calculateQsTopPadding() {
// in split shade mode we want notifications to be directly below status bar
- if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources) && !mKeyguardShowing) {
+ if (mShouldUseSplitNotificationShade && !mKeyguardShowing) {
return 0f;
}
if (mKeyguardShowing && (mQsExpandImmediate
@@ -2203,8 +2207,7 @@
return true;
}
- return !Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)
- && (isInSettings() || mIsPanelCollapseOnQQS);
+ return !mShouldUseSplitNotificationShade && (isInSettings() || mIsPanelCollapseOnQQS);
}
@Override
@@ -2631,7 +2634,7 @@
super.onTrackingStarted();
if (mQsFullyExpanded) {
mQsExpandImmediate = true;
- if (!Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ if (!mShouldUseSplitNotificationShade) {
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
}
}
@@ -2882,7 +2885,7 @@
*/
protected void updateHorizontalPanelPosition(float x) {
if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()
- || Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ || mShouldUseSplitNotificationShade) {
resetHorizontalPanelPosition();
return;
}
@@ -3127,7 +3130,7 @@
}
final float dozeAmount = dozing ? 1 : 0;
- mStatusBarStateController.setDozeAmount(dozeAmount, animate);
+ mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate);
}
public void setPulsing(boolean pulsing) {
@@ -3386,6 +3389,9 @@
return new TouchHandler() {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
+ return true;
+ }
if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
return false;
}
@@ -3413,6 +3419,12 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
+ final boolean showingOrAnimatingAltAuth =
+ mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating();
+ if (showingOrAnimatingAltAuth) {
+ mStatusBarKeyguardViewManager.resetAlternateAuth();
+ }
+
if (mBlockTouches || (mQsFullyExpanded && mQs != null
&& mQs.disallowPanelTouches())) {
return false;
@@ -3464,7 +3476,7 @@
handled = true;
}
handled |= super.onTouch(v, event);
- return !mDozing || mPulsing || handled;
+ return !mDozing || mPulsing || handled || showingOrAnimatingAltAuth;
}
};
}
@@ -3564,7 +3576,7 @@
@Override
public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
// When in split shade, overscroll shouldn't carry through to QS
- if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+ if (mShouldUseSplitNotificationShade) {
return;
}
cancelQsAnimation();
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 3ac6937..ed4f324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -453,7 +453,8 @@
// We need to collapse the panel since we peeked to the small height.
mView.postOnAnimation(mPostCollapseRunnable);
}
- } else if (!mStatusBar.isBouncerShowing()) {
+ } else if (!mStatusBar.isBouncerShowing()
+ && !mStatusBarKeyguardViewManager.isShowingAlternativeAuthOrAnimating()) {
boolean expands = onEmptySpaceClick(mInitialTouchX);
onTrackingStopped(expands);
}
@@ -1394,13 +1395,8 @@
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- if (mStatusBarKeyguardViewManager.isBouncerShowing()
- && mFalsingManager.isFalseTap(true, 0.5)) {
- endMotionEvent(event, x, y, true /* forceCancel */);
- } else {
- addMovement(event);
- endMotionEvent(event, x, y, false /* forceCancel */);
- }
+ addMovement(event);
+ endMotionEvent(event, x, y, false /* forceCancel */);
InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index d537241..f1405de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -675,8 +675,7 @@
mIconController.setIconVisibility(mSlotCamera, showCamera);
mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
- if (mPrivacyItemController.getAllIndicatorsAvailable()
- || mPrivacyItemController.getLocationAvailable()) {
+ if (mPrivacyItemController.getLocationAvailable()) {
mIconController.setIconVisibility(mSlotLocation, showLocation);
}
mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone, showLocation);
@@ -684,8 +683,7 @@
@Override
public void onLocationActiveChanged(boolean active) {
- if (!mPrivacyItemController.getAllIndicatorsAvailable()
- && !mPrivacyItemController.getLocationAvailable()) {
+ if (!mPrivacyItemController.getLocationAvailable()) {
updateLocationFromController();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 9b8b716..b82863e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -88,6 +88,17 @@
}
},
+ AUTH_SCRIMMED {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mFrontTint = Color.BLACK;
+
+ mBehindAlpha = 0f;
+ mFrontAlpha = .66f;
+ mBubbleAlpha = 0f;
+ }
+ },
+
/**
* Showing password challenge on the keyguard.
*/
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 14c1144..427df5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -144,6 +144,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.charging.WirelessChargingAnimation;
@@ -666,6 +667,16 @@
mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged");
}
};
+
+
+ private final FalsingManager.FalsingBeliefListener mFalsingBeliefListener =
+ new FalsingManager.FalsingBeliefListener() {
+ @Override
+ public void onFalse() {
+ mStatusBarKeyguardViewManager.reset(true);
+ }
+ };
+
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private HeadsUpAppearanceController mHeadsUpAppearanceController;
@@ -731,6 +742,7 @@
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
+ AccessibilityFloatingMenuController accessibilityFloatingMenuController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -1006,6 +1018,8 @@
mInitController.addPostInitTask(
() -> setUpDisableFlags(disabledFlags1, disabledFlags2));
+ mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener);
+
mPluginManager.addPluginListener(
new PluginListener<OverlayPlugin>() {
private ArraySet<OverlayPlugin> mOverlays = new ArraySet<>();
@@ -2958,6 +2972,9 @@
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.updateResources();
}
+ if (mStatusBarKeyguardViewManager != null) {
+ mStatusBarKeyguardViewManager.updateResources();
+ }
mPowerButtonReveal = new PowerButtonReveal(mContext.getResources().getDimensionPixelSize(
R.dimen.global_actions_top_padding));
@@ -4154,7 +4171,7 @@
}
@VisibleForTesting
- void updateScrimController() {
+ public void updateScrimController() {
Trace.beginSection("StatusBar#updateScrimController");
// We don't want to end up in KEYGUARD state when we're unlocking with
@@ -4170,7 +4187,9 @@
mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
- if (mBouncerShowing) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternativeAuth()) {
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ } else if (mBouncerShowing) {
// Bouncer needs the front scrim when it's on top of an activity,
// tapping on a notification, editing QS or being dismissed by
// FLAG_DISMISS_KEYGUARD_ACTIVITY.
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 01ada0f..c1f300b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -38,6 +38,7 @@
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
@@ -199,6 +200,7 @@
private final DockManager mDockManager;
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
private KeyguardBypassController mBypassController;
+ @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@@ -271,6 +273,10 @@
registerListeners();
}
+ public void setAlternateAuthInterceptor(@Nullable AlternateAuthInterceptor authInterceptor) {
+ mAlternateAuthInterceptor = authInterceptor;
+ }
+
private void registerListeners() {
mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
mStatusBarStateController.addCallback(this);
@@ -434,11 +440,7 @@
if (mShowing) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
- mAfterKeyguardGoneAction = null;
- if (mKeyguardGoneCancelAction != null) {
- mKeyguardGoneCancelAction.run();
- mKeyguardGoneCancelAction = null;
- }
+ cancelPostAuthActions();
}
mBouncer.hide(destroyView);
cancelPendingWakeupAction();
@@ -474,6 +476,14 @@
return;
}
+ if (mAlternateAuthInterceptor != null
+ && mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
+ mStatusBar.updateScrimController();
+ mAfterKeyguardGoneAction = r;
+ mKeyguardGoneCancelAction = cancelAction;
+ return;
+ }
+
if (!afterKeyguardGone) {
mBouncer.showWithDismissAction(r, cancelAction);
} else {
@@ -508,11 +518,21 @@
} else {
showBouncerOrKeyguard(hideBouncerWhenShowing);
}
+ resetAlternateAuth();
mKeyguardUpdateManager.sendKeyguardReset();
updateStates();
}
}
+ /**
+ * Stop showing any alternate auth methods
+ */
+ public void resetAlternateAuth() {
+ if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.reset()) {
+ mStatusBar.updateScrimController();
+ }
+ }
+
@Override
public void onStartedWakingUp() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
@@ -834,6 +854,20 @@
return mBouncer.isFullscreenBouncer();
}
+ /**
+ * Clear out any potential actions that were saved to run when the device is unlocked
+ */
+ public void cancelPostAuthActions() {
+ if (bouncerIsOrWillBeShowing()) {
+ return; // allow bouncer to trigger saved actions
+ }
+ mAfterKeyguardGoneAction = null;
+ if (mKeyguardGoneCancelAction != null) {
+ mKeyguardGoneCancelAction.run();
+ mKeyguardGoneCancelAction = null;
+ }
+ }
+
private long getNavBarShowDelay() {
if (mKeyguardStateController.isKeyguardFadingAway()) {
return mKeyguardStateController.getKeyguardFadingAwayDelay();
@@ -1050,6 +1084,17 @@
|| mBouncer.isFullscreenBouncer();
}
+ /**
+ * 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() {
+ if (mBouncer != null) {
+ mBouncer.updateResources();
+ }
+ }
+
public void dump(PrintWriter pw) {
pw.println("StatusBarKeyguardViewManager:");
pw.println(" mShowing: " + mShowing);
@@ -1063,6 +1108,11 @@
if (mBouncer != null) {
mBouncer.dump(pw);
}
+
+ if (mAlternateAuthInterceptor != null) {
+ pw.println("AltAuthInterceptor: ");
+ mAlternateAuthInterceptor.dump(pw);
+ }
}
@Override
@@ -1079,6 +1129,17 @@
return mBouncer;
}
+ public boolean isShowingAlternativeAuth() {
+ return mAlternateAuthInterceptor != null
+ && mAlternateAuthInterceptor.isShowingAlternativeAuth();
+ }
+
+ public boolean isShowingAlternativeAuthOrAnimating() {
+ return mAlternateAuthInterceptor != null
+ && (mAlternateAuthInterceptor.isShowingAlternativeAuth()
+ || mAlternateAuthInterceptor.isAnimating());
+ }
+
private static class DismissWithActionRequest {
final OnDismissAction dismissAction;
final Runnable cancelAction;
@@ -1093,4 +1154,36 @@
this.message = message;
}
}
+
+ /**
+ * Delegate used to send show/reset events to an alternate authentication method instead of the
+ * bouncer.
+ */
+ public interface AlternateAuthInterceptor {
+ /**
+ * @return whether alternative auth method was newly shown
+ */
+ boolean showAlternativeAuthMethod();
+
+ /**
+ * reset the state to the default (only keyguard showing, no auth methods showing)
+ * @return whether alternative auth method was newly hidden
+ */
+ boolean reset();
+
+ /**
+ * @return true if alternative auth method is showing
+ */
+ boolean isShowingAlternativeAuth();
+
+ /**
+ * print information for the alternate auth interceptor registered
+ */
+ void dump(PrintWriter pw);
+
+ /**
+ * @return true if the new auth method is currently animating in or out.
+ */
+ boolean isAnimating();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 589193e..4f32712 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -29,6 +29,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
@@ -165,6 +166,7 @@
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
+ AccessibilityFloatingMenuController accessibilityFloatingMenuController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -247,6 +249,7 @@
visualStabilityManager,
deviceProvisionedController,
navigationBarController,
+ accessibilityFloatingMenuController,
assistManagerLazy,
configurationController,
notificationShadeWindowController,
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 59c1138..4615877 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -26,6 +26,7 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardConstants;
@@ -143,6 +144,16 @@
openQsUserPanel();
});
+ mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK,
+ mContext.getString(
+ R.string.accessibility_quick_settings_choose_user_action)));
+ }
+ });
+
updateView(true /* forceUpdate */);
}
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 fbdaf9c..db039b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -31,6 +31,7 @@
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkScoreManager;
@@ -307,7 +308,8 @@
mWifiManager.registerScanResultsCallback(mReceiverHandler::post, scanResultsCallback);
}
- ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback(){
+ NetworkCallback callback =
+ new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO){
private Network mLastNetwork;
private NetworkCapabilities mLastNetworkCapabilities;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 320b00a..f72d2ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -31,8 +31,15 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.ServiceManager;
@@ -70,6 +77,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -137,12 +145,40 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
+ /**
+ * The remote view needs to adapt to colorized notifications when set
+ * @param color colorized notification color
+ */
+ public void overrideBackgroundTintColor(int color) {
+ mEditText.setBackgroundTintColor(color);
+ final boolean dark = !ContrastColorUtil.isColorLight(color);
+ int[][] states = new int[][] {
+ new int[] {android.R.attr.state_enabled},
+ new int[] {},
+ };
+
+ final int finalColor = dark
+ ? Color.WHITE
+ : Color.BLACK;
+
+ int[] colors = new int[] {
+ finalColor,
+ finalColor & 0x4DFFFFFF // %30 opacity
+ };
+
+ final ColorStateList tint = new ColorStateList(states, colors);
+ mSendButton.setImageTintList(tint);
+ mProgressBar.setProgressTintList(tint);
+ mProgressBar.setIndeterminateTintList(tint);
+ mProgressBar.setSecondaryProgressTintList(tint);
+ mEditText.setForegroundColor(finalColor);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mProgressBar = findViewById(R.id.remote_input_progress);
-
mSendButton = findViewById(R.id.remote_input_send);
mSendButton.setOnClickListener(this);
@@ -362,6 +398,12 @@
}
}
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mEditText.updateCornerRadius(heightMeasureSpec / 2);
+ }
+
/** Populates the text field of the remote input with the given content. */
public void setEditTextContent(@Nullable CharSequence editTextContent) {
mEditText.setText(editTextContent);
@@ -651,17 +693,47 @@
private final OnReceiveContentListener mOnReceiveContentListener = this::onReceiveContent;
- private final Drawable mBackground;
private RemoteInputView mRemoteInputView;
+ private GradientDrawable mTextBackground;
+ private ColorDrawable mBackgroundColor;
+ private LayerDrawable mBackground;
boolean mShowImeOnInputConnection;
private LightBarController mLightBarController;
private InputMethodManager mInputMethodManager;
+ private int mColor = Notification.COLOR_DEFAULT;
UserHandle mUser;
+ private int mStokeWidth;
public RemoteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
- mBackground = getBackground();
mLightBarController = Dependency.get(LightBarController.class);
+ mTextBackground = createBackground(context, attrs);
+ mBackgroundColor = new ColorDrawable();
+ mBackground = new LayerDrawable(new Drawable[] {mBackgroundColor, mTextBackground});
+ float density = context.getResources().getDisplayMetrics().density;
+ mStokeWidth = (int) (2 * density);
+ setDefaultColors();
+ }
+
+ private void setDefaultColors() {
+ Resources.Theme theme = getContext().getTheme();
+ TypedArray ta = theme.obtainStyledAttributes(
+ new int[]{android.R.attr.colorAccent,
+ com.android.internal.R.attr.colorBackgroundFloating});
+ mTextBackground.setStroke(mStokeWidth,
+ ta.getColor(0, Notification.COLOR_DEFAULT));
+ mColor = ta.getColor(1, Notification.COLOR_DEFAULT);
+ mTextBackground.setColor(mColor);
+ }
+
+ private GradientDrawable createBackground(Context context, AttributeSet attrs) {
+ float density = context.getResources().getDisplayMetrics().density;
+ int padding = (int) (12 * density);
+ GradientDrawable d = new GradientDrawable();
+ d.setShape(GradientDrawable.RECTANGLE);
+ d.setPadding(padding, padding, padding, padding);
+ d.setCornerRadius(padding);
+ return d;
}
void setSupportedMimeTypes(@Nullable Collection<String> mimeTypes) {
@@ -724,6 +796,19 @@
}
}
+ protected void setBackgroundTintColor(int color) {
+ mBackgroundColor.setColor(color);
+ mTextBackground.setColor(color);
+ }
+
+ protected void setForegroundColor(int color) {
+ mTextBackground.setStroke(mStokeWidth, color);
+ setTextColor(color);
+ // %60
+ setHintTextColor(color & 0x99FFFFFF);
+ setTextCursorDrawable(null);
+ }
+
@Override
public void getFocusedRect(Rect r) {
super.getFocusedRect(r);
@@ -811,6 +896,10 @@
setSelection(getText().length());
}
+ void updateCornerRadius(float radius) {
+ mTextBackground.setCornerRadius(radius);
+ }
+
void setInnerFocusable(boolean focusable) {
setFocusableInTouchMode(focusable);
setFocusable(focusable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index e8331a1..e76b803 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
import android.app.admin.DeviceAdminInfo;
+import android.content.ComponentName;
import android.graphics.drawable.Drawable;
import com.android.systemui.Dumpable;
@@ -33,6 +34,10 @@
String getProfileOwnerName();
CharSequence getDeviceOwnerOrganizationName();
CharSequence getWorkProfileOrganizationName();
+ /** Device owner component even if not on this user. **/
+ ComponentName getDeviceOwnerComponentOnAnyUser();
+ /** Device owner type for a device owner. **/
+ int getDeviceOwnerType(ComponentName admin);
boolean isNetworkLoggingEnabled();
boolean isVpnEnabled();
boolean isVpnRestricted();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 5638503..4afb86b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -15,9 +15,11 @@
*/
package com.android.systemui.statusbar.policy;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.DeviceOwnerType;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -225,6 +227,18 @@
}
@Override
+ @Nullable
+ public ComponentName getDeviceOwnerComponentOnAnyUser() {
+ return mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser();
+ }
+
+ @Override
+ @DeviceOwnerType
+ public int getDeviceOwnerType(@NonNull ComponentName admin) {
+ return mDevicePolicyManager.getDeviceOwnerType(admin);
+ }
+
+ @Override
public boolean isNetworkLoggingEnabled() {
return mDevicePolicyManager.isNetworkLoggingEnabled(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index fd19528..3892f31 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -26,6 +26,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
@@ -43,6 +44,7 @@
* directly. Instead, use {@link ToastFactory#createToast}.
*/
public class SystemUIToast implements ToastPlugin.Toast {
+ static final String TAG = "SystemUIToast";
final Context mContext;
final CharSequence mText;
final ToastPlugin.Toast mPluginToast;
@@ -225,8 +227,12 @@
int userId) {
final ApplicationsState appState =
ApplicationsState.getInstance((Application) context.getApplicationContext());
+ if (!appState.isUserAdded(userId)) {
+ Log.d(TAG, "user hasn't been fully initialized, not showing an app icon for "
+ + "packageName=" + packageName);
+ return null;
+ }
final AppEntry appEntry = appState.getEntry(packageName, userId);
-
if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(appEntry)) {
return null;
}
@@ -237,6 +243,5 @@
Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
appInfo.loadUnbadgedIcon(context.getPackageManager()), user, true).icon;
return new BitmapDrawable(context.getResources(), iconBmp);
-
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
index 6aadd10..dc86d58 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -17,8 +17,6 @@
package com.android.systemui.util
import android.content.res.Resources
-import android.graphics.Canvas
-import android.graphics.Path
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.DrawableWrapper
@@ -43,53 +41,25 @@
private const val MAX_LEVEL = 10000 // Taken from Drawable
}
- private var clipPath: Path = Path()
-
- init {
- setClipPath(Rect())
- }
-
override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
onLevelChange(level)
return super.onLayoutDirectionChanged(layoutDirection)
}
override fun onBoundsChange(bounds: Rect) {
- setClipPath(bounds)
super.onBoundsChange(bounds)
onLevelChange(level)
}
- private fun setClipPath(bounds: Rect) {
- clipPath.reset()
- clipPath.addRoundRect(
- bounds.left.toFloat(),
- bounds.top.toFloat(),
- bounds.right.toFloat(),
- bounds.bottom.toFloat(),
- bounds.height().toFloat() / 2,
- bounds.height().toFloat() / 2,
- Path.Direction.CW
- )
- }
-
override fun onLevelChange(level: Int): Boolean {
val db = drawable?.bounds!!
- val width = bounds.width() * level / MAX_LEVEL
- // Extra space on the left to keep the rounded shape on the right end
- val leftBound = bounds.left - bounds.height()
- drawable?.setBounds(leftBound, db.top, bounds.left + width, db.bottom)
+ // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width
+ val width = bounds.height() + (bounds.width() - bounds.height()) * level / MAX_LEVEL
+ drawable?.setBounds(bounds.left, db.top, bounds.left + width, db.bottom)
return super.onLevelChange(level)
}
- override fun draw(canvas: Canvas) {
- canvas.save()
- canvas.clipPath(clipPath)
- super.draw(canvas)
- canvas.restore()
- }
-
- override fun getConstantState(): ConstantState? {
+ override fun getConstantState(): ConstantState {
// This should not be null as it was created with a state in the constructor.
return RoundedCornerState(super.getConstantState()!!)
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 1b6c612..ddfa63a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -81,7 +81,7 @@
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.transition.RemoteTransitions;
+import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -399,8 +399,8 @@
@WMSingleton
@Provides
- static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
- return Transitions.asRemoteTransitions(transitions);
+ static ShellTransitions provideRemoteTransitions(Transitions transitions) {
+ return transitions.asRemoteTransitions();
}
@WMSingleton
@@ -480,7 +480,7 @@
@WMSingleton
@Provides
static StartingWindowController provideStartingWindowController(Context context,
- @ShellAnimationThread ShellExecutor executor, TransactionPool pool) {
+ @ShellSplashscreenThread ShellExecutor executor, TransactionPool pool) {
return new StartingWindowController(context, executor, pool);
}
@@ -509,27 +509,33 @@
@WMSingleton
@Provides
- static ShellInit provideShellInit(DisplayImeController displayImeController,
+ static ShellInit provideShellInit(ShellInitImpl impl) {
+ return impl.asShellInit();
+ }
+
+ @WMSingleton
+ @Provides
+ static ShellInitImpl provideShellInitImpl(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
- Optional<StartingSurface> startingSurface,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
+ StartingWindowController startingWindow,
@ShellMainThread ShellExecutor mainExecutor) {
- return ShellInitImpl.create(displayImeController,
+ return new ShellInitImpl(displayImeController,
dragAndDropController,
shellTaskOrganizer,
legacySplitScreenOptional,
splitScreenOptional,
appPairsOptional,
- startingSurface,
pipTouchHandlerOptional,
fullscreenTaskListener,
transitions,
+ startingWindow,
mainExecutor);
}
@@ -539,7 +545,13 @@
*/
@WMSingleton
@Provides
- static Optional<ShellCommandHandler> provideShellCommandHandler(
+ static Optional<ShellCommandHandler> provideShellCommandHandler(ShellCommandHandlerImpl impl) {
+ return Optional.of(impl.asShellCommandHandler());
+ }
+
+ @WMSingleton
+ @Provides
+ static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
@@ -548,8 +560,8 @@
Optional<HideDisplayCutoutController> hideDisplayCutout,
Optional<AppPairsController> appPairsOptional,
@ShellMainThread ShellExecutor mainExecutor) {
- return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+ return new ShellCommandHandlerImpl(shellTaskOrganizer,
legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, appPairsOptional, mainExecutor));
+ hideDisplayCutout, appPairsOptional, mainExecutor);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
index 64632af..714f1f2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
@@ -16,6 +16,9 @@
package com.android.keyguard;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -26,11 +29,14 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -46,7 +52,7 @@
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
+
private KeyguardHostView mKeyguardHostView;
@Mock
private AudioManager mAudioManager;
@@ -66,6 +72,10 @@
@Before
public void setup() {
+ mContext.ensureTestableResources();
+
+ mKeyguardHostView = new KeyguardHostView(mContext);
+
when(mKeyguardSecurityContainerControllerFactory.create(any(
KeyguardSecurityContainer.SecurityCallback.class)))
.thenReturn(mKeyguardSecurityContainerController);
@@ -76,10 +86,10 @@
@Test
public void testHasDismissActions() {
- Assert.assertFalse("Action not set yet", mKeyguardHostViewController.hasDismissActions());
+ assertFalse("Action not set yet", mKeyguardHostViewController.hasDismissActions());
mKeyguardHostViewController.setOnDismissAction(mock(OnDismissAction.class),
null /* cancelAction */);
- Assert.assertTrue("Action should exist", mKeyguardHostViewController.hasDismissActions());
+ assertTrue("Action should exist", mKeyguardHostViewController.hasDismissActions());
}
@Test
@@ -87,4 +97,31 @@
mKeyguardHostViewController.onStartingToHide();
verify(mKeyguardSecurityContainerController).onStartingToHide();
}
+
+ @Test
+ public void testGravityReappliedOnConfigurationChange() {
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mKeyguardHostView.setLayoutParams(lp);
+
+ // Set initial gravity
+ mContext.getOrCreateTestableResources().addOverride(R.integer.keyguard_host_view_gravity,
+ Gravity.CENTER);
+
+ // Kick off the initial pass...
+ mKeyguardHostViewController.init();
+ assertEquals(
+ ((FrameLayout.LayoutParams) mKeyguardHostView.getLayoutParams()).gravity,
+ Gravity.CENTER);
+
+ // Now simulate a config change
+ mContext.getOrCreateTestableResources().addOverride(R.integer.keyguard_host_view_gravity,
+ Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+
+ mKeyguardHostViewController.updateResources();
+ assertEquals(
+ ((FrameLayout.LayoutParams) mKeyguardHostView.getLayoutParams()).gravity,
+ Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 49ba646..096ce0f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -16,13 +16,19 @@
package com.android.keyguard;
+import static android.view.WindowInsets.Type.ime;
+
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.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowInsetsController;
@@ -33,9 +39,8 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -84,24 +89,37 @@
@Mock
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
@Mock
- private ConfigurationController mConfigurationController;
+ private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
@Mock
- private KeyguardViewController mKeyguardViewController;
- private FalsingManager mFalsingManager = new FalsingManagerFake();
+ private KeyguardMessageArea mKeyguardMessageArea;
+ @Mock
+ private ConfigurationController mConfigurationController;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
+ private KeyguardPasswordViewController mKeyguardPasswordViewController;
+ private KeyguardPasswordView mKeyguardPasswordView;
@Before
public void setup() {
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+ mKeyguardPasswordView = spy(new KeyguardPasswordView(getContext()));
+ when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
+ when(mKeyguardPasswordView.findViewById(R.id.keyguard_message_area))
+ .thenReturn(mKeyguardMessageArea);
+ when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+ mKeyguardPasswordViewController = new KeyguardPasswordViewController(
+ (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
+ SecurityMode.Password, mLockPatternUtils, null,
+ mKeyguardMessageAreaControllerFactory, null, null, null, mock(Resources.class),
+ null);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
- mConfigurationController, mKeyguardViewController, mFalsingManager)
+ mConfigurationController)
.create(mSecurityCallback);
}
@@ -125,14 +143,13 @@
public void startDisappearAnimation_animatesKeyboard() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
SecurityMode.Password);
- when(mInputViewController.getSecurityMode()).thenReturn(
- SecurityMode.Password);
when(mKeyguardSecurityViewFlipperController.getSecurityView(
eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
- .thenReturn(mInputViewController);
- mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */);
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
mKeyguardSecurityContainerController.startDisappearAnimation(null);
- verify(mInputViewController).startDisappearAnimation(eq(null));
+ verify(mWindowInsetsController).controlWindowInsetsAnimation(
+ eq(ime()), anyLong(), any(), any(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 1783fa4..104318e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -19,9 +19,6 @@
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -90,13 +87,6 @@
}
@Test
- public void startDisappearAnimation_animatesKeyboard() {
- mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password);
- verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
- any(), any());
- }
-
- @Test
public void onMeasure_usesFullWidthWithoutOneHandedMode() {
setUpKeyguard(
/* deviceConfigCanUseOneHandedKeyguard= */false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index b9d8d27..52e2016 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -50,6 +50,7 @@
import android.content.pm.ServiceInfo;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
@@ -192,10 +193,21 @@
// IBiometricsFace@1.0 does not support detection, only authentication.
when(mFaceSensorProperties.isEmpty()).thenReturn(false);
+
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorPropertiesInternal(
0 /* id */,
FaceSensorProperties.STRENGTH_STRONG, 1 /* maxTemplatesAllowed */,
- false /* supportsFaceDetection */, true /* supportsSelfIllumination */));
+ componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
+ false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */));
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index e1ddaad..daa896c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -41,6 +41,7 @@
import android.view.SurfaceHolder;
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import org.junit.Before;
import org.junit.Ignore;
@@ -99,7 +100,7 @@
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper() {
+ return new ImageWallpaper(mock(StatusBarStateController.class)) {
@Override
public Engine onCreateEngine() {
return new GLEngine(mHandler) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
new file mode 100644
index 0000000..01b7ade
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.accessibility;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class AccessibilityButtonModeObserverTest extends SysuiTestCase {
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityButtonModeObserver.ModeChangedListener mListener;
+
+ private AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+
+ private static final int TEST_A11Y_BTN_MODE_VALUE =
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+
+ @Before
+ public void setUp() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+ mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext);
+ }
+
+ @Test
+ public void onChange_haveListener_invokeCallback() {
+ mAccessibilityButtonModeObserver.addListener(mListener);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE);
+
+ mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
+
+ verify(mListener).onAccessibilityButtonModeChanged(TEST_A11Y_BTN_MODE_VALUE);
+ }
+
+ @Test
+ public void onChange_noListener_noInvokeCallback() {
+ mAccessibilityButtonModeObserver.addListener(mListener);
+ mAccessibilityButtonModeObserver.removeListener(mListener);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE);
+
+ mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
+
+ verify(mListener, never()).onAccessibilityButtonModeChanged(anyInt());
+ }
+
+ @Test
+ public void getCurrentAccessibilityButtonMode_expectedValue() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE);
+
+ final int actualValue =
+ mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+
+ assertThat(actualValue).isEqualTo(TEST_A11Y_BTN_MODE_VALUE);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
new file mode 100644
index 0000000..1e49fc9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test for {@link AccessibilityButtonTargetsObserver}. */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class AccessibilityButtonTargetsObserverTest extends SysuiTestCase {
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityButtonTargetsObserver.TargetsChangedListener mListener;
+
+ private AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
+
+ private static final String TEST_A11Y_BTN_TARGETS = "Magnification";
+
+ @Before
+ public void setUp() {
+ mAccessibilityButtonTargetsObserver = new AccessibilityButtonTargetsObserver(mContext);
+ }
+
+ @Test
+ public void onChange_haveListener_invokeCallback() {
+ mAccessibilityButtonTargetsObserver.addListener(mListener);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+
+ mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
+
+ verify(mListener).onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
+ }
+
+ @Test
+ public void onChange_listenerRemoved_noInvokeCallback() {
+ mAccessibilityButtonTargetsObserver.addListener(mListener);
+ mAccessibilityButtonTargetsObserver.removeListener(mListener);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+
+ mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
+
+ verify(mListener, never()).onAccessibilityButtonTargetsChanged(anyString());
+ }
+
+ @Test
+ public void getCurrentAccessibilityButtonTargets_expectedValue() {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+
+ final String actualValue =
+ mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
+
+ assertThat(actualValue).isEqualTo(TEST_A11Y_BTN_TARGETS);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java
index 92dad9b..550e77d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MotionEventHelper.java
@@ -23,11 +23,11 @@
import java.util.ArrayList;
import java.util.List;
-class MotionEventHelper {
+public class MotionEventHelper {
@GuardedBy("this")
private final List<MotionEvent> mMotionEvents = new ArrayList<>();
- void recycleEvents() {
+ public void recycleEvents() {
for (MotionEvent event:mMotionEvents) {
event.recycle();
}
@@ -36,7 +36,7 @@
}
}
- MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
+ public MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
float y) {
MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0);
synchronized (this) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
new file mode 100644
index 0000000..5b1c441
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test for {@link SecureSettingsContentObserver}. */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SecureSettingsContentObserverTest extends SysuiTestCase {
+
+ private FakeSecureSettingsContentObserver mTestObserver;
+
+ @Before
+ public void setUpObserver() {
+ mTestObserver = new FakeSecureSettingsContentObserver(mContext,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void addNullListener_throwNPE() {
+ mTestObserver.addListener(null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void removeNullListener_throwNPE() {
+ mTestObserver.removeListener(null);
+ }
+
+ @Test
+ public void checkValue() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1);
+
+ assertThat(mTestObserver.getSettingsValue()).isEqualTo("1");
+ }
+
+
+ private static class FakeSecureSettingsContentObserver extends
+ SecureSettingsContentObserver<Object> {
+
+ protected FakeSecureSettingsContentObserver(Context context,
+ String secureSettingsKey) {
+ super(context, secureSettingsKey);
+ }
+
+ @Override
+ void onValueChanged(Object listener, String value) {
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
new file mode 100644
index 0000000..a83f038
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Test for {@link AccessibilityFloatingMenuController}. */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
+
+ private static final String TEST_A11Y_BTN_TARGETS = "Magnification";
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ private AccessibilityFloatingMenuController mController;
+ private AccessibilityButtonTargetsObserver mTargetsObserver;
+ private AccessibilityButtonModeObserver mModeObserver;
+ @Mock
+ private AccessibilityManager mMockA11yManager;
+
+ @Test
+ public void initController_registerListeners() {
+ mController = setUpController();
+
+ verify(mTargetsObserver).addListener(
+ any(AccessibilityButtonTargetsObserver.TargetsChangedListener.class));
+ verify(mModeObserver).addListener(
+ any(AccessibilityButtonModeObserver.ModeChangedListener.class));
+ verify(mMockA11yManager).addAccessibilityStateChangeListener(any(
+ AccessibilityManager.AccessibilityStateChangeListener.class));
+ }
+
+ @Test
+ public void initController_accessibilityManagerEnabled_showWidget() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+ when(mMockA11yManager.isEnabled()).thenReturn(true);
+
+ mController = setUpController();
+
+ assertThat(mController.mFloatingMenu).isNotNull();
+ verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController);
+ }
+
+ @Test
+ public void initController_accessibilityManagerDisabledThenCallbackToEnabled_showWidget() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+ when(mMockA11yManager.isEnabled()).thenReturn(false);
+
+ mController = setUpController();
+ mController.onAccessibilityStateChanged(true);
+
+ assertThat(mController.mFloatingMenu).isNotNull();
+ verify(mMockA11yManager).removeAccessibilityStateChangeListener(mController);
+ }
+
+ @Test
+ public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+ mController = setUpController();
+
+ mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+
+ assertThat(mController.mFloatingMenu).isNotNull();
+ }
+
+ @Test
+ public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "");
+ mController = setUpController();
+
+ mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+
+ assertThat(mController.mFloatingMenu).isNull();
+ }
+
+ @Test
+ public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_showWidget() {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS);
+ mController = setUpController();
+
+ mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+
+ assertThat(mController.mFloatingMenu).isNull();
+ }
+
+ @Test
+ public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "");
+ mController = setUpController();
+
+ mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+
+ assertThat(mController.mFloatingMenu).isNull();
+ }
+
+ @Test
+ public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+ mController = setUpController();
+
+ mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
+
+ assertThat(mController.mFloatingMenu).isNotNull();
+ }
+
+ @Test
+ public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+ mController = setUpController();
+
+ mController.onAccessibilityButtonTargetsChanged("");
+
+ assertThat(mController.mFloatingMenu).isNull();
+ }
+
+ @Test
+ public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_showWidget() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+ mController = setUpController();
+
+ mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
+
+ assertThat(mController.mFloatingMenu).isNull();
+ }
+
+ @Test
+ public void onAccessibilityButtonTargetsChanged_buttonModeAndNoButtonTargets_destroyWidget() {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
+ mController = setUpController();
+
+ mController.onAccessibilityButtonTargetsChanged("");
+
+ assertThat(mController.mFloatingMenu).isNull();
+ }
+
+ private AccessibilityFloatingMenuController setUpController() {
+ mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class));
+ mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class));
+ mContext.addMockSystemService(AccessibilityManager.class, mMockA11yManager);
+
+ return new AccessibilityFloatingMenuController(mContext, mTargetsObserver,
+ mModeObserver);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
new file mode 100644
index 0000000..337d97e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link AccessibilityFloatingMenu}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AccessibilityFloatingMenuTest extends SysuiTestCase {
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ private AccessibilityFloatingMenuView mMenuView;
+ private AccessibilityFloatingMenu mMenu;
+
+ @Before
+ public void initMenu() {
+ MockitoAnnotations.initMocks(this);
+
+ final List<AccessibilityTarget> mTargets = new ArrayList<>();
+ mTargets.add(mock(AccessibilityTarget.class));
+
+ final List<String> assignedTargets = new ArrayList<>();
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+ assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME);
+ doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
+ anyInt());
+
+ mMenuView = new AccessibilityFloatingMenuView(mContext);
+ mMenu = new AccessibilityFloatingMenu(mContext, mMenuView);
+ }
+
+ @Test
+ public void showMenuView_success() {
+ mMenu.show();
+
+ assertThat(mMenuView.isShowing()).isTrue();
+ }
+
+ @Test
+ public void hideMenuView_success() {
+ mMenu.show();
+ mMenu.hide();
+
+ assertThat(mMenuView.isShowing()).isFalse();
+ }
+
+ @After
+ public void tearDown() {
+ mMenu.hide();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
new file mode 100644
index 0000000..8683dd6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.MotionEventHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link AccessibilityFloatingMenuView}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
+ private AccessibilityFloatingMenuView mMenuView;
+
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private ViewPropertyAnimator mAnimator;
+
+ private MotionEvent mInterceptMotionEvent;
+
+ private RecyclerView mListView;
+
+ private int mMenuHalfWidth;
+ private int mMenuHalfHeight;
+ private int mScreenHalfWidth;
+ private int mScreenHalfHeight;
+ private int mMaxWindowX;
+
+ private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+ private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
+ mWindowManager).getMaximumWindowMetrics();
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+
+ mTargets.add(mock(AccessibilityTarget.class));
+ mListView = new RecyclerView(mContext);
+ mMenuView = new AccessibilityFloatingMenuView(mContext, mListView);
+
+ final Resources res = mContext.getResources();
+ final int margin =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
+ final int padding =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding);
+ final int iconWidthHeight =
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height);
+ final int menuWidth = padding * 2 + iconWidthHeight;
+ final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding;
+ final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
+ final int screenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
+ mMenuHalfWidth = menuWidth / 2;
+ mMenuHalfHeight = menuHeight / 2;
+ mScreenHalfWidth = screenWidth / 2;
+ mScreenHalfHeight = screenHeight / 2;
+ mMaxWindowX = screenWidth - margin - menuWidth;
+ }
+
+ @Test
+ public void initListView_success() {
+ assertThat(mMenuView.getChildCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void showMenuView_success() {
+ mMenuView.show();
+
+ assertThat(mMenuView.isShowing()).isTrue();
+ verify(mWindowManager).addView(eq(mMenuView), any(WindowManager.LayoutParams.class));
+ }
+
+ @Test
+ public void showMenuView_showTwice_addViewOnce() {
+ mMenuView.show();
+ mMenuView.show();
+
+ assertThat(mMenuView.isShowing()).isTrue();
+ verify(mWindowManager, times(1)).addView(eq(mMenuView),
+ any(WindowManager.LayoutParams.class));
+ }
+
+ @Test
+ public void hideMenuView_success() {
+ mMenuView.show();
+ mMenuView.hide();
+
+ assertThat(mMenuView.isShowing()).isFalse();
+ verify(mWindowManager).removeView(eq(mMenuView));
+ }
+
+ @Test
+ public void hideMenuView_hideTwice_removeViewOnce() {
+ mMenuView.show();
+ mMenuView.hide();
+ mMenuView.hide();
+
+ assertThat(mMenuView.isShowing()).isFalse();
+ verify(mWindowManager, times(1)).removeView(eq(mMenuView));
+ }
+
+ @Test
+ public void updateListViewRadius_singleTarget_matchResult() {
+ final float radius =
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_small_single_radius);
+ final float[] expectedRadii =
+ new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+
+ mMenuView.onTargetsChanged(mTargets);
+ final View view = mMenuView.getChildAt(0);
+ final LayerDrawable layerDrawable = (LayerDrawable) view.getBackground();
+ final GradientDrawable gradientDrawable =
+ (GradientDrawable) layerDrawable.getDrawable(0);
+ final float[] actualRadii = gradientDrawable.getCornerRadii();
+
+ assertThat(actualRadii).isEqualTo(expectedRadii);
+ }
+
+ @Test
+ public void setSizeType_largeSize_matchResult() {
+ final int shapeType = 2;
+ final float radius = getContext().getResources().getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_large_single_radius);
+ final float[] expectedRadii =
+ new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
+ final Drawable listViewBackground =
+ mContext.getDrawable(R.drawable.accessibility_floating_menu_background);
+ mListView = spy(new RecyclerView(mContext));
+ mListView.setBackground(listViewBackground);
+
+ mMenuView = new AccessibilityFloatingMenuView(mContext, mListView);
+ mMenuView.setSizeType(shapeType);
+ final LayerDrawable layerDrawable =
+ (LayerDrawable) mListView.getBackground();
+ final GradientDrawable gradientDrawable =
+ (GradientDrawable) layerDrawable.getDrawable(0);
+
+ assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
+ }
+
+ @Test
+ public void setShapeType_halfCircle_translationX() {
+ final RecyclerView listView = spy(new RecyclerView(mContext));
+ final AccessibilityFloatingMenuView menuView =
+ new AccessibilityFloatingMenuView(mContext, listView);
+ final int shapeType = 2;
+ doReturn(mAnimator).when(listView).animate();
+
+ menuView.setShapeType(shapeType);
+
+ verify(mAnimator).translationX(anyFloat());
+ }
+
+ @Test
+ public void onTargetsChanged_fadeInOut() {
+ final AccessibilityFloatingMenuView menuView = spy(mMenuView);
+ final InOrder inOrderMenuView = inOrder(menuView);
+
+ menuView.onTargetsChanged(mTargets);
+
+ inOrderMenuView.verify(menuView).fadeIn();
+ inOrderMenuView.verify(menuView).fadeOut();
+ }
+
+ @Test
+ public void setSizeType_fadeInOut() {
+ final AccessibilityFloatingMenuView menuView = spy(mMenuView);
+ final InOrder inOrderMenuView = inOrder(menuView);
+ final int smallSize = 0;
+ menuView.setSizeType(smallSize);
+
+ inOrderMenuView.verify(menuView).fadeIn();
+ inOrderMenuView.verify(menuView).fadeOut();
+ }
+
+ @Test
+ public void tapOnAndDragMenu_interceptUpEvent() {
+ final RecyclerView listView = new RecyclerView(mContext);
+ final TestAccessibilityFloatingMenu menuView =
+ new TestAccessibilityFloatingMenu(mContext, listView);
+
+ menuView.show();
+ menuView.onTargetsChanged(mTargets);
+ menuView.setSizeType(0);
+ menuView.setShapeType(0);
+ final int currentWindowX = mMenuView.mCurrentLayoutParams.x;
+ final int currentWindowY = mMenuView.mCurrentLayoutParams.y;
+ final MotionEvent downEvent =
+ mMotionEventHelper.obtainMotionEvent(0, 1,
+ MotionEvent.ACTION_DOWN,
+ currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
+ currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
+ final MotionEvent moveEvent =
+ mMotionEventHelper.obtainMotionEvent(2, 3,
+ MotionEvent.ACTION_MOVE,
+ /* screenCenterX */mScreenHalfWidth
+ - /* offsetXToScreenLeftHalfRegion */ 10,
+ /* screenCenterY */ mScreenHalfHeight);
+ final MotionEvent upEvent =
+ mMotionEventHelper.obtainMotionEvent(4, 5,
+ MotionEvent.ACTION_UP,
+ /* screenCenterX */ mScreenHalfWidth
+ - /* offsetXToScreenLeftHalfRegion */ 10,
+ /* screenCenterY */ mScreenHalfHeight);
+ listView.dispatchTouchEvent(downEvent);
+ listView.dispatchTouchEvent(moveEvent);
+ listView.dispatchTouchEvent(upEvent);
+
+ assertThat(mInterceptMotionEvent.getAction()).isEqualTo(MotionEvent.ACTION_UP);
+ }
+
+ @Test
+ public void tapOnAndDragMenu_matchLocation() {
+ mMenuView.show();
+ mMenuView.onTargetsChanged(mTargets);
+ mMenuView.setSizeType(0);
+ mMenuView.setShapeType(0);
+ final int currentWindowX = mMenuView.mCurrentLayoutParams.x;
+ final int currentWindowY = mMenuView.mCurrentLayoutParams.y;
+ final MotionEvent downEvent =
+ mMotionEventHelper.obtainMotionEvent(0, 1,
+ MotionEvent.ACTION_DOWN,
+ currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
+ currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
+ final MotionEvent moveEvent =
+ mMotionEventHelper.obtainMotionEvent(2, 3,
+ MotionEvent.ACTION_MOVE,
+ /* screenCenterX */mScreenHalfWidth
+ + /* offsetXToScreenRightHalfRegion */ 10,
+ /* screenCenterY */ mScreenHalfHeight);
+ final MotionEvent upEvent =
+ mMotionEventHelper.obtainMotionEvent(4, 5,
+ MotionEvent.ACTION_UP,
+ /* screenCenterX */ mScreenHalfWidth
+ + /* offsetXToScreenRightHalfRegion */ 10,
+ /* screenCenterY */ mScreenHalfHeight);
+ mListView.dispatchTouchEvent(downEvent);
+ mListView.dispatchTouchEvent(moveEvent);
+ mListView.dispatchTouchEvent(upEvent);
+ mMenuView.mDragAnimator.end();
+
+ assertThat(mMenuView.mCurrentLayoutParams.x).isEqualTo(mMaxWindowX);
+ assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(
+ /* newWindowY = screenCenterY - offsetY */ mScreenHalfHeight - mMenuHalfHeight);
+ }
+
+
+ @Test
+ public void tapOnAndDragMenuToScreenSide_transformShapeHalfOval() {
+ mMenuView.show();
+ mMenuView.onTargetsChanged(mTargets);
+ mMenuView.setSizeType(0);
+ mMenuView.setShapeType(/* oval */ 0);
+ final int currentWindowX = mMenuView.mCurrentLayoutParams.x;
+ final int currentWindowY = mMenuView.mCurrentLayoutParams.y;
+ final MotionEvent downEvent =
+ mMotionEventHelper.obtainMotionEvent(0, 1,
+ MotionEvent.ACTION_DOWN,
+ currentWindowX + /* offsetXToMenuCenterX */ mMenuHalfWidth,
+ currentWindowY + /* offsetYToMenuCenterY */ mMenuHalfHeight);
+ final MotionEvent moveEvent =
+ mMotionEventHelper.obtainMotionEvent(2, 3,
+ MotionEvent.ACTION_MOVE,
+ /* downX */(currentWindowX + mMenuHalfWidth)
+ + /* offsetXToScreenRightSide */ mMenuHalfWidth,
+ /* downY */ (currentWindowY + mMenuHalfHeight));
+ final MotionEvent upEvent =
+ mMotionEventHelper.obtainMotionEvent(4, 5,
+ MotionEvent.ACTION_UP,
+ /* downX */(currentWindowX + mMenuHalfWidth)
+ + /* offsetXToScreenRightSide */ mMenuHalfWidth,
+ /* downY */ (currentWindowY + mMenuHalfHeight));
+ mListView.dispatchTouchEvent(downEvent);
+ mListView.dispatchTouchEvent(moveEvent);
+ mListView.dispatchTouchEvent(upEvent);
+
+ assertThat(mMenuView.mShapeType).isEqualTo(/* halfOval */ 1);
+ }
+
+ @After
+ public void tearDown() {
+ mInterceptMotionEvent = null;
+ mMotionEventHelper.recycleEvents();
+ }
+
+ private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView {
+ TestAccessibilityFloatingMenu(Context context, RecyclerView listView) {
+ super(context, listView);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
+ @NonNull MotionEvent event) {
+ final boolean intercept = super.onInterceptTouchEvent(recyclerView, event);
+
+ if (intercept) {
+ mInterceptMotionEvent = event;
+ }
+
+ return intercept;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
new file mode 100644
index 0000000..899625e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests for {@link AccessibilityTargetAdapter}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AccessibilityTargetAdapterTest extends SysuiTestCase {
+ @Mock
+ private AccessibilityTarget mAccessibilityTarget;
+
+ @Mock
+ private Drawable mIcon;
+
+ @Mock
+ private Drawable.ConstantState mConstantState;
+
+ private ViewHolder mViewHolder;
+ private AccessibilityTargetAdapter mAdapter;
+ private final List<AccessibilityTarget> mTargets = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTargets.add(mAccessibilityTarget);
+ mAdapter = new AccessibilityTargetAdapter(mTargets);
+
+ final View root = LayoutInflater.from(mContext).inflate(
+ R.layout.accessibility_floating_menu_item, null);
+ mViewHolder = new ViewHolder(root);
+ when(mAccessibilityTarget.getIcon()).thenReturn(mIcon);
+ when(mIcon.getConstantState()).thenReturn(mConstantState);
+ }
+
+ @Test
+ public void onBindViewHolder_setIconWidthHeight_matchResult() {
+ final int iconWidthHeight = 50;
+ mAdapter.setIconWidthHeight(iconWidthHeight);
+
+ mAdapter.onBindViewHolder(mViewHolder, 0);
+ final int actualIconWith = mViewHolder.mIconView.getLayoutParams().width;
+
+ assertThat(actualIconWith).isEqualTo(iconWidthHeight);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index 7f8be91..1565dee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.FingerprintSensorProperties;
@@ -49,6 +50,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -341,8 +345,18 @@
final int sensorLocationX = 540;
final int sensorLocationY = 1600;
final int sensorRadius = 100;
+
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
sensorRadius);
@@ -379,8 +393,18 @@
final int sensorLocationX = 540;
final int sensorLocationY = 1600;
final int sensorRadius = 100;
+
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
sensorRadius);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 5088a53..f41c100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -34,8 +34,8 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -248,9 +248,19 @@
config.mPromptInfo = promptInfo;
final List<FingerprintSensorPropertiesInternal> fpProps = new ArrayList<>();
+
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
fpProps.add(new FingerprintSensorPropertiesInternal(0,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
+ componentInfo,
FingerprintSensorProperties.TYPE_REAR,
false /* resetLockoutRequiresHardwareAuthToken */));
mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 30c4cf6..fa190a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -34,7 +34,6 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +42,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
@@ -123,10 +123,20 @@
when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
1 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
1 /* maxEnrollmentsPerUser */,
+ componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequireHardwareAuthToken */);
List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
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 d6f4958..3f1a927 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -26,6 +26,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
@@ -44,8 +45,10 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -91,6 +94,10 @@
@Mock
private StatusBar mStatusBar;
@Mock
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock
+ private DumpManager mDumpManager;
+ @Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
private FakeExecutor mFgExecutor;
@@ -114,9 +121,19 @@
setUpResources();
when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView);
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
props.add(new FingerprintSensorPropertiesInternal(TEST_UDFPS_SENSOR_ID,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
+ componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */));
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
@@ -129,7 +146,9 @@
mWindowManager,
mStatusBarStateController,
mFgExecutor,
- mStatusBar);
+ mStatusBar,
+ mStatusBarKeyguardViewManager,
+ mDumpManager);
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 480b335..65f0f7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -26,9 +28,11 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import org.junit.Before;
import org.junit.Test;
@@ -51,24 +55,34 @@
private StatusBarStateController mStatusBarStateController;
@Mock
private StatusBar mStatusBar;
+ @Mock
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock
+ private DumpManager mDumpManager;
private UdfpsKeyguardViewController mController;
// Capture listeners so that they can be used to send events
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
- private StatusBarStateController.StateListener mParentListener;
- private StatusBarStateController.StateListener mDozeListener;
+ private StatusBarStateController.StateListener mParentStatusBarStateListener;
+ private StatusBarStateController.StateListener mStatusBarStateListener;
@Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor;
private StatusBar.ExpansionChangedListener mExpansionListener;
+ @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
+ mAltAuthInterceptorCaptor;
+ private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mController = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
- mStatusBar);
+ mStatusBar,
+ mStatusBarKeyguardViewManager,
+ mDumpManager);
}
@Test
@@ -86,7 +100,7 @@
@Test
public void testViewControllerQueriesSBStateOnAttached() {
mController.onViewAttached();
- verify(mStatusBarStateController).getState();
+ verify(mStatusBarStateController, times(2)).getState();
verify(mStatusBarStateController).getDozeAmount();
final float dozeAmount = .88f;
@@ -106,8 +120,8 @@
captureExpansionListener();
mController.onViewDetached();
- verify(mStatusBarStateController).removeCallback(mParentListener);
- verify(mStatusBarStateController).removeCallback(mDozeListener);
+ verify(mStatusBarStateController).removeCallback(mParentStatusBarStateListener);
+ verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
verify(mStatusBar).removeExpansionChangedListener(mExpansionListener);
}
@@ -118,21 +132,88 @@
final float linear = .55f;
final float eased = .65f;
- mDozeListener.onDozeAmountChanged(linear, eased);
+ mStatusBarStateListener.onDozeAmountChanged(linear, eased);
verify(mView).onDozeAmountChanged(linear, eased);
}
+ @Test
+ public void testShouldNotPauseAuthOnKeyguard() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureExpansionListener();
+
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ assertFalse(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testShouldPauseAuthOnShadeLocked() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureExpansionListener();
+
+ sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
+
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testOverrideShouldPauseAuthOnShadeLocked() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureAltAuthInterceptor();
+
+ sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
+ assertTrue(mController.shouldPauseAuth());
+
+ mAltAuthInterceptor.showAlternativeAuthMethod(); // force show
+ assertFalse(mController.shouldPauseAuth());
+ assertTrue(mAltAuthInterceptor.isShowingAlternativeAuth());
+
+ mAltAuthInterceptor.reset(); // stop force show
+ assertTrue(mController.shouldPauseAuth());
+ assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+ }
+
+ @Test
+ public void testOnDetachedStateReset() {
+ // GIVEN view is attached, alt auth is force being shown
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureAltAuthInterceptor();
+
+ mAltAuthInterceptor.showAlternativeAuthMethod(); // alt auth force show
+
+ // WHEN view is detached
+ mController.onViewDetached();
+
+ // THEN alt auth state reports not showing
+ assertFalse(mAltAuthInterceptor.isShowingAlternativeAuth());
+ }
+
+ private void sendStatusBarStateChanged(int statusBarState) {
+ mStatusBarStateListener.onStateChanged(statusBarState);
+ mParentStatusBarStateListener.onStateChanged(statusBarState);
+ }
+
private void captureStatusBarStateListeners() {
verify(mStatusBarStateController, times(2)).addCallback(mStateListenerCaptor.capture());
List<StatusBarStateController.StateListener> stateListeners =
mStateListenerCaptor.getAllValues();
- mParentListener = stateListeners.get(0);
- mDozeListener = stateListeners.get(1);
+ mParentStatusBarStateListener = stateListeners.get(0);
+ mStatusBarStateListener = stateListeners.get(1);
}
private void captureExpansionListener() {
verify(mStatusBar).addExpansionChangedListener(mExpansionListenerCaptor.capture());
mExpansionListener = mExpansionListenerCaptor.getValue();
}
+
+ private void captureAltAuthInterceptor() {
+ verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
+ mAltAuthInterceptorCaptor.capture());
+ mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index b232850..67c1d07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,6 +36,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -71,26 +73,34 @@
private FalsingClassifier mClassifierB;
private final List<MotionEvent> mMotionEventList = new ArrayList<>();
@Mock
- private HistoryTracker mHistoryTracker;;
+ private HistoryTracker mHistoryTracker;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
- private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, "");
+ private final FalsingClassifier.Result mFalsedResult =
+ FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
private GestureCompleteListener mGestureCompleteListener;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
- when(mClassifierB.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mPassedResult);
+ when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mPassedResult);
when(mSingleTapClassfier.isTap(any(List.class))).thenReturn(mPassedResult);
- when(mDoubleTapClassifier.classifyGesture()).thenReturn(mPassedResult);
+ when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mPassedResult);
mClassifiers.add(mClassifierA);
mClassifiers.add(mClassifierB);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mDockManager,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
- mHistoryTracker, false);
+ mHistoryTracker, mKeyguardStateController, false);
ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
@@ -120,26 +130,29 @@
}
@Test
- public void testIsFalseTouch_ClassffiersPass() {
+ public void testIsFalseTouch_ClassifiersPass() {
assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
}
@Test
public void testIsFalseTouch_ClassifierARejects() {
- when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mFalsedResult);
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isTrue();
}
@Test
public void testIsFalseTouch_ClassifierBRejects() {
- when(mClassifierB.classifyGesture(anyDouble(), anyDouble())).thenReturn(mFalsedResult);
+ when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isTrue();
}
@Test
public void testIsFalseTouch_FaceAuth() {
// Even when the classifiers report a false, we should allow.
- when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mPassedResult);
when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
@@ -148,7 +161,8 @@
@Test
public void testIsFalseTouch_Docked() {
// Even when the classifiers report a false, we should allow.
- when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mPassedResult);
mDockManager.setIsDocked(true);
assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
@@ -168,7 +182,8 @@
@Test
public void testIsFalseTap_RobustCheck_NoFaceAuth() {
when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mPassedResult);
- when(mDoubleTapClassifier.classifyGesture()).thenReturn(mFalsedResult);
+ when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
when(mHistoryTracker.falseBelief()).thenReturn(1.0);
mFalsingDataProvider.setJustUnlockedWithFace(false);
assertThat(mBrightLineFalsingManager.isFalseTap(true, 0)).isTrue();
@@ -183,11 +198,13 @@
@Test
public void testIsFalseDoubleTap() {
- when(mDoubleTapClassifier.classifyGesture()).thenReturn(mPassedResult);
+ when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mPassedResult);
assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isFalse();
- when(mDoubleTapClassifier.classifyGesture()).thenReturn(mFalsedResult);
+ when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue();
}
@@ -233,4 +250,20 @@
assertThat(mFakeExecutor.numPending()).isEqualTo(0);
}
+
+ @Test
+ public void testNoFalsingUnlocked() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
+
+ when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseTap(false, 0)).isFalse();
+
+ when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
index ca0a4aa..7d6ff34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.classifier;
-import static com.android.systemui.classifier.Classifier.UNLOCK;
-
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -45,7 +43,6 @@
displayMetrics.heightPixels = 1000;
mFakeBatteryController = new FakeBatteryController(getLeakCheck());
mDataProvider = new FalsingDataProvider(displayMetrics, mFakeBatteryController);
- mDataProvider.setInteractionType(UNLOCK);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
index dafc871..14dcd58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/DiagonalClassifierTest.java
@@ -16,11 +16,12 @@
package com.android.systemui.classifier;
+import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -69,106 +70,104 @@
@Test
public void testPass_UnknownAngle() {
when(mDataProvider.getAngle()).thenReturn(Float.MAX_VALUE);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_VerticalSwipe() {
when(mDataProvider.getAngle()).thenReturn(UP_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(DOWN_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_MostlyVerticalSwipe() {
when(mDataProvider.getAngle()).thenReturn(UP_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(UP_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(DOWN_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(DOWN_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS * 2);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_BarelyVerticalSwipe() {
when(mDataProvider.getAngle()).thenReturn(
UP_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(
UP_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(
DOWN_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(
DOWN_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS * 2);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_HorizontalSwipe() {
when(mDataProvider.getAngle()).thenReturn(RIGHT_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(LEFT_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_MostlyHorizontalSwipe() {
when(mDataProvider.getAngle()).thenReturn(RIGHT_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(RIGHT_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(LEFT_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(LEFT_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_BarelyHorizontalSwipe() {
when(mDataProvider.getAngle()).thenReturn(
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(
LEFT_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(
LEFT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - 2 * FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.getAngle()).thenReturn(
RIGHT_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS + 2 * FIVE_DEG_IN_RADIANS * 2);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_AffordanceSwipe() {
- when(mDataProvider.getInteractionType()).thenReturn(LEFT_AFFORDANCE);
when(mDataProvider.getAngle()).thenReturn(
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(LEFT_AFFORDANCE, 0.5, 0).isFalse()).isFalse();
- when(mDataProvider.getInteractionType()).thenReturn(RIGHT_AFFORDANCE);
when(mDataProvider.getAngle()).thenReturn(
LEFT_IN_RADIANS - FORTY_FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(RIGHT_AFFORDANCE, 0.5, 0).isFalse()).isFalse();
// This classifier may return false for other angles, but these are the only
// two that actually matter, as affordances generally only travel in these two directions.
@@ -182,37 +181,37 @@
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.getAngle()).thenReturn(
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.getAngle()).thenReturn(
UP_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.getAngle()).thenReturn(
LEFT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.getAngle()).thenReturn(
DOWN_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
// Vertical Swipes
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.getAngle()).thenReturn(
RIGHT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.getAngle()).thenReturn(
UP_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.getAngle()).thenReturn(
LEFT_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS + FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.getAngle()).thenReturn(
DOWN_IN_RADIANS + FORTY_FIVE_DEG_IN_RADIANS - FIVE_DEG_IN_RADIANS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
index f6c1424..db06199 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
@@ -16,8 +16,7 @@
package com.android.systemui.classifier;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
import android.testing.AndroidTestingRunner;
@@ -51,32 +50,32 @@
@Test
public void testPass_noPointer() {
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
public void testPass_fling() {
mClassifier.onTouchEvent(appendDownEvent(1, 1));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendMoveEvent(1, 40));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendUpEvent(1, 80));
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
public void testFail_flingShort() {
mClassifier.onTouchEvent(appendDownEvent(1, 1));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendMoveEvent(1, 2));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendUpEvent(1, 10));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -84,26 +83,26 @@
// These events, in testing, result in a fling that falls just short of the threshold.
mClassifier.onTouchEvent(appendDownEvent(1, 1, 1));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendMoveEvent(1, 15, 2));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendMoveEvent(1, 16, 3));
mClassifier.onTouchEvent(appendMoveEvent(1, 17, 300));
mClassifier.onTouchEvent(appendMoveEvent(1, 18, 301));
mClassifier.onTouchEvent(appendUpEvent(1, 19, 501));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
public void testPass_swipe() {
mClassifier.onTouchEvent(appendDownEvent(1, 1));
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendMoveEvent(1, mDataProvider.getYdpi() * 3, 3));
mClassifier.onTouchEvent(appendUpEvent(1, mDataProvider.getYdpi() * 3, 300));
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
index 4e17424..f726cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/DoubleTapClassifierTest.java
@@ -16,8 +16,8 @@
package com.android.systemui.classifier;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.when;
@@ -52,7 +52,8 @@
private SingleTapClassifier mSingleTapClassifier;
private DoubleTapClassifier mClassifier;
- private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, "");
+ private final FalsingClassifier.Result mFalsedResult =
+ FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
@Before
@@ -81,8 +82,8 @@
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP, 1);
- boolean result = mClassifier.classifyGesture().isFalse();
- assertThat("Single tap recognized as a valid double tap", result, is(true));
+ boolean result = mClassifier.classifyGesture(0, 0.5, 1).isFalse();
+ assertThat(result).isTrue();
}
@Test
@@ -97,8 +98,8 @@
addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, TOUCH_SLOP, TOUCH_SLOP);
addMotionEvent(2, 3, MotionEvent.ACTION_UP, TOUCH_SLOP, TOUCH_SLOP);
- FalsingClassifier.Result result = mClassifier.classifyGesture();
- assertThat(result.getReason(), result.isFalse(), is(false));
+ FalsingClassifier.Result result = mClassifier.classifyGesture(0, 0.5, 1);
+ assertThat(result.isFalse()).isFalse();
}
@Test
@@ -113,8 +114,8 @@
addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(2, 3, MotionEvent.ACTION_UP, 1, 1);
- boolean result = mClassifier.classifyGesture().isFalse();
- assertThat("Bad first touch allowed", result, is(true));
+ boolean result = mClassifier.classifyGesture(0, 0.5, 1).isFalse();
+ assertThat(result).isTrue();
}
@Test
@@ -129,8 +130,8 @@
addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(2, 3, MotionEvent.ACTION_UP, 1, 1);
- boolean result = mClassifier.classifyGesture().isFalse();
- assertThat("Bad second touch allowed", result, is(true));
+ boolean result = mClassifier.classifyGesture(0, 0.5, 1).isFalse();
+ assertThat(result).isTrue();
}
@Test
@@ -145,8 +146,8 @@
addMotionEvent(2, 2, MotionEvent.ACTION_DOWN, TOUCH_SLOP + 1, TOUCH_SLOP);
addMotionEvent(2, 3, MotionEvent.ACTION_UP, TOUCH_SLOP, TOUCH_SLOP + 1);
- boolean result = mClassifier.classifyGesture().isFalse();
- assertThat("Sloppy second touch allowed", result, is(true));
+ boolean result = mClassifier.classifyGesture(0, 0.5, 1).isFalse();
+ assertThat(result).isTrue();
}
@Test
@@ -163,8 +164,8 @@
addMotionEvent(DOUBLE_TAP_TIMEOUT_MS + 1, DOUBLE_TAP_TIMEOUT_MS + 2,
MotionEvent.ACTION_UP, 1, 1);
- boolean result = mClassifier.classifyGesture().isFalse();
- assertThat("Slow second tap allowed", result, is(true));
+ boolean result = mClassifier.classifyGesture(0, 0.5, 1).isFalse();
+ assertThat(result).isTrue();
}
private void addMotionEvent(long downMs, long eventMs, int action, int x, int y) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index dc79b88..e6aeee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -34,6 +34,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -62,16 +63,19 @@
private ProximitySensor mProximitySensor;
@Mock
private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
- mStatusBarStateController, new FakeSystemClock());
+ mStatusBarStateController, mKeyguardStateController, new FakeSystemClock());
}
@Test
@@ -159,4 +163,20 @@
mFalsingCollector.onTouchEvent(up);
verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
}
+
+ @Test
+ public void testAvoidUnlocked() {
+ MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ // Nothing passed initially
+ mFalsingCollector.onTouchEvent(down);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+ // Up event would normally flush the up event.
+ mFalsingCollector.onTouchEvent(up);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java
index bb7545f..38355c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/HistoryTrackerTest.java
@@ -121,7 +121,8 @@
private void addResult(boolean falsed, double confidence) {
mHistoryTracker.addResults(Collections.singletonList(
falsed
- ? FalsingClassifier.Result.falsed(confidence, "test")
+ ? FalsingClassifier.Result.falsed(
+ confidence, getClass().getSimpleName(), "test")
: FalsingClassifier.Result.passed(confidence)),
mSystemClock.uptimeMillis());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/PointerCountClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/PointerCountClassifierTest.java
index 76802f4..b8ea062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/PointerCountClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/PointerCountClassifierTest.java
@@ -18,8 +18,10 @@
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyInt;
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
@@ -50,13 +52,15 @@
@Test
public void testPass_noPointer() {
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()).isFalse())
+ .isFalse();
}
@Test
public void testPass_singlePointer() {
mClassifier.onTouchEvent(appendDownEvent(1, 1));
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()).isFalse())
+ .isFalse();
}
@Test
@@ -72,7 +76,8 @@
0, 0);
mClassifier.onTouchEvent(motionEvent);
motionEvent.recycle();
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(Classifier.GENERIC, 0.5, 1).isFalse())
+ .isTrue();
}
@Test
@@ -88,7 +93,6 @@
0, 0);
mClassifier.onTouchEvent(motionEvent);
motionEvent.recycle();
- getDataProvider().setInteractionType(QUICK_SETTINGS);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse()).isFalse();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java
index ba8ca9a..52423bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ProximityClassifierTest.java
@@ -51,14 +51,13 @@
private FalsingClassifier mClassifier;
private final FalsingClassifier.Result mFalsedResult =
- FalsingClassifier.Result.falsed(1, "test");
+ FalsingClassifier.Result.falsed(1, getClass().getSimpleName() , "test");
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
@Before
public void setup() {
super.setup();
MockitoAnnotations.initMocks(this);
- when(mDataProvider.getInteractionType()).thenReturn(GENERIC);
when(mDistanceClassifier.isLongSwipe()).thenReturn(mFalsedResult);
mClassifier = new ProximityClassifier(
mDistanceClassifier, mDataProvider, new DeviceConfigProxyFake());
@@ -73,7 +72,7 @@
public void testPass_uncovered() {
touchDown();
touchUp(10);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(false));
}
@Test
@@ -82,17 +81,16 @@
mClassifier.onProximityEvent(createSensorEvent(true, 1));
mClassifier.onProximityEvent(createSensorEvent(false, 2));
touchUp(20);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(false));
}
@Test
public void testPass_quickSettings() {
touchDown();
- when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS);
mClassifier.onProximityEvent(createSensorEvent(true, 1));
mClassifier.onProximityEvent(createSensorEvent(false, 11));
touchUp(10);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse(), is(false));
}
@Test
@@ -101,7 +99,7 @@
mClassifier.onProximityEvent(createSensorEvent(true, 1));
mClassifier.onProximityEvent(createSensorEvent(false, 11));
touchUp(10);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(true));
}
@Test
@@ -112,7 +110,7 @@
mClassifier.onProximityEvent(createSensorEvent(true, 96));
mClassifier.onProximityEvent(createSensorEvent(false, 100));
touchUp(100);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(true));
}
@Test
@@ -122,7 +120,7 @@
mClassifier.onProximityEvent(createSensorEvent(false, 11));
touchUp(10);
when(mDistanceClassifier.isLongSwipe()).thenReturn(mPassedResult);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(false));
}
private void touchDown() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
index 62c876f9..1524107 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/SingleTapClassifierTest.java
@@ -16,8 +16,8 @@
package com.android.systemui.classifier;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -70,14 +70,14 @@
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP, 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
mMotionEvents.clear();
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, -TOUCH_SLOP + 2, 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@@ -86,14 +86,14 @@
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
mMotionEvents.clear();
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, -TOUCH_SLOP + 2);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@@ -102,14 +102,14 @@
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, TOUCH_SLOP + 1, 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mMotionEvents.clear();
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, -TOUCH_SLOP - 1, 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@@ -118,14 +118,14 @@
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP + 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mMotionEvents.clear();
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, -TOUCH_SLOP - 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -134,7 +134,7 @@
addMotionEvent(0, 1, MotionEvent.ACTION_MOVE, 1, TOUCH_SLOP + 1);
addMotionEvent(0, 2, MotionEvent.ACTION_UP, 1, 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -142,12 +142,12 @@
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, 1);
- assertThat(mClassifier.isTap(mMotionEvents).isFalse(), is(false));
+ assertThat(mClassifier.isTap(mMotionEvents).isFalse()).isFalse();
addMotionEvent(0, 0, MotionEvent.ACTION_DOWN, 1, 1);
addMotionEvent(0, 1, MotionEvent.ACTION_UP, 1, TOUCH_SLOP + 1);
- assertThat(mClassifier.isTap(mMotionEvents).isFalse(), is(true));
+ assertThat(mClassifier.isTap(mMotionEvents).isFalse()).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
index 4a896a8..068a1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TypeClassifierTest.java
@@ -25,8 +25,8 @@
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -56,248 +56,225 @@
@Test
public void testPass_QuickSettings() {
- when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS);
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_QuickSettings() {
- when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS);
-
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_PulseExpand() {
- when(mDataProvider.getInteractionType()).thenReturn(PULSE_EXPAND);
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(PULSE_EXPAND, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(PULSE_EXPAND, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_PulseExpand() {
- when(mDataProvider.getInteractionType()).thenReturn(PULSE_EXPAND);
-
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(PULSE_EXPAND, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(PULSE_EXPAND, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_NotificationDragDown() {
- when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DRAG_DOWN);
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DRAG_DOWN, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DRAG_DOWN, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_NotificationDragDown() {
- when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DRAG_DOWN);
-
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DRAG_DOWN, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DRAG_DOWN, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_NotificationDismiss() {
- when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DISMISS);
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
when(mDataProvider.isRight()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_NotificationDismiss() {
- when(mDataProvider.getInteractionType()).thenReturn(NOTIFICATION_DISMISS);
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
when(mDataProvider.isRight()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(NOTIFICATION_DISMISS, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_Unlock() {
- when(mDataProvider.getInteractionType()).thenReturn(UNLOCK);
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(UNLOCK, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(UNLOCK, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_Unlock() {
- when(mDataProvider.getInteractionType()).thenReturn(UNLOCK);
-
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(UNLOCK, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(UNLOCK, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(UNLOCK, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_BouncerUnlock() {
- when(mDataProvider.getInteractionType()).thenReturn(BOUNCER_UNLOCK);
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(false); // right should cause no effect.
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(BOUNCER_UNLOCK, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(BOUNCER_UNLOCK, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_BouncerUnlock() {
- when(mDataProvider.getInteractionType()).thenReturn(BOUNCER_UNLOCK);
-
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(BOUNCER_UNLOCK, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(BOUNCER_UNLOCK, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(BOUNCER_UNLOCK, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_LeftAffordance() {
- when(mDataProvider.getInteractionType()).thenReturn(LEFT_AFFORDANCE);
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(true);
when(mDataProvider.isVertical()).thenReturn(false); // vertical should cause no effect.
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(LEFT_AFFORDANCE, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isVertical()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(LEFT_AFFORDANCE, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_LeftAffordance() {
- when(mDataProvider.getInteractionType()).thenReturn(LEFT_AFFORDANCE);
-
when(mDataProvider.isRight()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(LEFT_AFFORDANCE, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isRight()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(LEFT_AFFORDANCE, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isRight()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(LEFT_AFFORDANCE, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_RightAffordance() {
- when(mDataProvider.getInteractionType()).thenReturn(RIGHT_AFFORDANCE);
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(false);
when(mDataProvider.isVertical()).thenReturn(false); // vertical should cause no effect.
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(RIGHT_AFFORDANCE, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isVertical()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(RIGHT_AFFORDANCE, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_RightAffordance() {
- when(mDataProvider.getInteractionType()).thenReturn(RIGHT_AFFORDANCE);
-
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(RIGHT_AFFORDANCE, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(RIGHT_AFFORDANCE, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(true);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(RIGHT_AFFORDANCE, 0.5, 0).isFalse()).isTrue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
index 09bee12..e004c30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
@@ -16,8 +16,7 @@
package com.android.systemui.classifier;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertThat;
import android.testing.AndroidTestingRunner;
@@ -51,11 +50,11 @@
@Test
public void testPass_fewTouchesVertical() {
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
appendMoveEvent(0, 0);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
appendMoveEvent(0, 100);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
@@ -63,16 +62,16 @@
appendMoveEvent(0, 0);
appendMoveEvent(0, 100);
appendMoveEvent(0, 200);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
public void testPass_fewTouchesHorizontal() {
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
appendMoveEvent(0, 0);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
appendMoveEvent(100, 0);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
@@ -80,7 +79,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(100, 0);
appendMoveEvent(200, 0);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@@ -89,7 +88,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(0, 100);
appendMoveEvent(0, 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -97,7 +96,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(100, 0);
appendMoveEvent(1, 0);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -105,7 +104,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(10, 10);
appendMoveEvent(20, 20);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
@@ -115,7 +114,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(5, 100);
appendMoveEvent(-5, 200);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
@@ -125,7 +124,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(100, 5);
appendMoveEvent(200, -5);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
@@ -135,7 +134,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(6, 10);
appendMoveEvent(-6, 20);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -145,7 +144,7 @@
appendMoveEvent(0, 0);
appendMoveEvent(10, 5);
appendMoveEvent(20, -5);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -153,25 +152,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(100, 5);
appendMoveEvent(200, 10);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(100, 0);
appendMoveEvent(200, 10);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(100, -10);
appendMoveEvent(200, 10);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(100, -10);
appendMoveEvent(200, 50);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -179,25 +178,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(10, 50);
appendMoveEvent(8, 100);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(1, 800);
appendMoveEvent(2, 900);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-10, 600);
appendMoveEvent(30, 700);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(40, 100);
appendMoveEvent(0, 101);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -205,25 +204,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(-10, 50);
appendMoveEvent(-24, 100);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-20, 800);
appendMoveEvent(-20, 900);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(30, 600);
appendMoveEvent(-10, 700);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-80, 100);
appendMoveEvent(-10, 101);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -231,25 +230,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(-120, 10);
appendMoveEvent(-200, 20);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-20, 8);
appendMoveEvent(-40, 2);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-500, -2);
appendMoveEvent(-600, 70);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-80, 100);
appendMoveEvent(-100, 1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -257,25 +256,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(-120, -10);
appendMoveEvent(-200, -20);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-20, -8);
appendMoveEvent(-40, -2);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-500, 2);
appendMoveEvent(-600, -70);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-80, -100);
appendMoveEvent(-100, -1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -283,25 +282,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(-12, -20);
appendMoveEvent(-20, -40);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-20, -130);
appendMoveEvent(-40, -260);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(1, -100);
appendMoveEvent(-6, -200);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-80, -100);
appendMoveEvent(-10, -110);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -309,25 +308,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(12, -20);
appendMoveEvent(20, -40);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(20, -130);
appendMoveEvent(40, -260);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(-1, -100);
appendMoveEvent(6, -200);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(80, -100);
appendMoveEvent(10, -110);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
@@ -335,25 +334,25 @@
appendMoveEvent(0, 0);
appendMoveEvent(120, -20);
appendMoveEvent(200, -40);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(200, -13);
appendMoveEvent(400, -30);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(100, 10);
appendMoveEvent(600, -20);
- assertThat(mClassifier.classifyGesture().isFalse(), is(false));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
resetDataProvider();
appendMoveEvent(0, 0);
appendMoveEvent(80, -100);
appendMoveEvent(100, -1);
- assertThat(mClassifier.classifyGesture().isFalse(), is(true));
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 9278570..17eb15b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -19,6 +19,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -63,6 +64,8 @@
private lateinit var taskViewFactory: Optional<TaskViewFactory>
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var cvh: ControlViewHolder
+ @Mock
+ private lateinit var metricsLogger: ControlsMetricsLogger
companion object {
fun <T> any(): T = Mockito.any<T>()
@@ -86,7 +89,8 @@
globalActionsComponent,
taskViewFactory,
getFakeBroadcastDispatcher(),
- lazyUiController
+ lazyUiController,
+ metricsLogger
))
`when`(cvh.cws.ci.controlId).thenReturn(ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 1f9862c..3d4425c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -35,6 +35,7 @@
import org.junit.After
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -49,7 +50,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class SeekBarViewModelTest : SysuiTestCase() {
private lateinit var viewModel: SeekBarViewModel
@@ -124,6 +125,7 @@
}
@Test
+ @Ignore
fun updateDurationWithPlayback() {
// GIVEN that the duration is contained within the metadata
val duration = 12000L
@@ -146,6 +148,7 @@
}
@Test
+ @Ignore
fun updateDurationWithoutPlayback() {
// GIVEN that the duration is contained within the metadata
val duration = 12000L
@@ -204,6 +207,7 @@
}
@Test
+ @Ignore
fun updateDurationNoMetadata() {
// GIVEN that the metadata is null
whenever(mockController.getMetadata()).thenReturn(null)
@@ -235,6 +239,7 @@
}
@Test
+ @Ignore
fun updateSeekAvailable() {
// GIVEN that seek is included in actions
val state = PlaybackState.Builder().run {
@@ -249,6 +254,7 @@
}
@Test
+ @Ignore
fun updateSeekNotAvailable() {
// GIVEN that seek is not included in actions
val state = PlaybackState.Builder().run {
@@ -303,6 +309,7 @@
}
@Test
+ @Ignore
fun onSeekProgressWithSeekStarting() {
val pos = 42L
with(viewModel) {
@@ -314,6 +321,7 @@
}
@Test
+ @Ignore
fun onProgressChangedFromUser() {
// WHEN user starts dragging the seek bar
val pos = 42
@@ -614,6 +622,7 @@
}
@Test
+ @Ignore
fun clearSeekBar() {
// GIVEN that the duration is contained within the metadata
val metadata = MediaMetadata.Builder().run {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index e761da4..c29b812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -45,6 +45,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -93,6 +94,7 @@
mock(MetricsLogger.class),
mock(OverviewProxyService.class),
mock(NavigationModeController.class),
+ mock(AccessibilityButtonModeObserver.class),
mock(StatusBarStateController.class),
mock(SysUiState.class),
mock(BroadcastDispatcher.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 2b76f1c..f0c48bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -23,6 +23,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
import static org.junit.Assert.assertEquals;
@@ -31,6 +32,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -44,12 +46,16 @@
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
@@ -61,6 +67,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -79,6 +86,7 @@
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.Pip;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -101,6 +109,7 @@
private OverviewProxyService mOverviewProxyService;
private CommandQueue mCommandQueue;
private SysUiState mMockSysUiState;
+ @Mock
private Handler mHandler;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@@ -124,12 +133,17 @@
mDependency.injectMockDependency(NavigationBarController.class);
mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class);
TestableLooper.get(this).runWithLooper(() -> {
- mHandler = new Handler();
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
});
}
+ @After
+ public void tearDown() throws Exception {
+ DeviceConfig.resetToDefaults(
+ Settings.RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_SYSTEMUI);
+ }
+
private void setupSysuiDependency() {
Display display = new Display(DisplayManagerGlobal.getInstance(), EXTERNAL_DISPLAY_ID,
new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -163,6 +177,32 @@
}
@Test
+ public void testHomeLongPressWithCustomDuration() throws Exception {
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI)
+ .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100)
+ .build());
+ mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+
+ mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain(
+ /*downTime=*/SystemClock.uptimeMillis(),
+ /*eventTime=*/SystemClock.uptimeMillis(),
+ /*action=*/MotionEvent.ACTION_DOWN,
+ 0, 0, 0
+ ));
+ verify(mHandler, times(1)).postDelayed(any(), eq(100L));
+
+ mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain(
+ /*downTime=*/SystemClock.uptimeMillis(),
+ /*eventTime=*/SystemClock.uptimeMillis(),
+ /*action=*/MotionEvent.ACTION_UP,
+ 0, 0, 0
+ ));
+
+ verify(mHandler, times(1)).removeCallbacks(any());
+ }
+
+ @Test
public void testRegisteredWithDispatcher() {
mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
verify(mBroadcastDispatcher).registerReceiverWithHandler(
@@ -220,6 +260,7 @@
new MetricsLogger(),
mOverviewProxyService,
mock(NavigationModeController.class),
+ mock(AccessibilityButtonModeObserver.class),
mock(StatusBarStateController.class),
mMockSysUiState,
mBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java
index b3ad6ef..24a63e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java
@@ -37,6 +37,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.system.PeopleProviderUtils;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import junit.framework.Assert;
@@ -73,6 +74,8 @@
private PackageManager mPackageManager;
@Mock
private IPeopleManager mPeopleManager;
+ @Mock
+ private NotificationEntryManager mNotificationEntryManager;
@Before
public void setUp() throws Exception {
@@ -84,6 +87,7 @@
mContext, PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY);
provider.setLauncherApps(mLauncherApps);
provider.setPeopleManager(mPeopleManager);
+ provider.setNotificationEntryManager(mNotificationEntryManager);
mContext.getContentResolver().addProvider(
PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY, provider);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
index ac18934..6834fa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
@@ -21,6 +21,8 @@
import android.content.pm.LauncherApps;
import android.content.pm.ProviderInfo;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+
public class PeopleProviderTestable extends PeopleProvider {
public void initializeForTesting(Context context, String authority) {
@@ -37,4 +39,8 @@
void setPeopleManager(IPeopleManager peopleManager) {
mPeopleManager = peopleManager;
}
+
+ void setNotificationEntryManager(NotificationEntryManager notificationEntryManager) {
+ mNotificationEntryManager = notificationEntryManager;
+ }
}
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 3fed074..9185cd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -21,15 +21,15 @@
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.getPeopleTileFromPersistentStorage;
+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.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -59,6 +59,8 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -68,12 +70,13 @@
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.appwidget.IAppWidgetService;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -95,8 +98,8 @@
import java.util.Map;
import java.util.stream.Collectors;
-@SmallTest
@RunWith(AndroidTestingRunner.class)
+@SmallTest
public class PeopleSpaceUtilsTest extends SysuiTestCase {
private static final int WIDGET_ID_WITH_SHORTCUT = 1;
@@ -226,6 +229,8 @@
@Mock
private NotificationEntryManager mNotificationEntryManager;
+ private Bundle mOptions;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -233,12 +238,12 @@
Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
- Bundle options = new Bundle();
- options.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
+ mOptions = new Bundle();
+ mOptions.putParcelable(OPTIONS_PEOPLE_TILE, PERSON_TILE);
when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT)))
- .thenReturn(options);
+ .thenReturn(mOptions);
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
.thenReturn(new Bundle());
@@ -252,6 +257,10 @@
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));
@@ -569,6 +578,25 @@
}
@Test
+ public void testAugmentSingleTileFromVisibleNotificationsSingleTile() {
+ PeopleSpaceTile tile =
+ new PeopleSpaceTile
+ .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+ .setPackageName(PACKAGE_NAME)
+ .setUserHandle(new UserHandle(0))
+ .build();
+ PeopleSpaceTile augmentedTile = PeopleSpaceUtils
+ .augmentSingleTileFromVisibleNotifications(
+ mContext, tile, mNotificationEntryManager);
+
+ assertThat(augmentedTile).isNotNull();
+ assertThat(augmentedTile.getNotificationContent().toString())
+ .isEqualTo(NOTIFICATION_TEXT_2);
+
+ verify(mNotificationEntryManager, times(1)).getVisibleNotifications();
+ }
+
+ @Test
public void testDoNotUpdateSingleConversationAppWidgetWhenNotBirthday() {
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT};
when(mMockCursor.moveToNext()).thenReturn(true, false);
@@ -665,7 +693,7 @@
@Test
public void testCreateRemoteViewsWithLastInteractionTime() {
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
- PERSON_TILE_WITHOUT_NOTIFICATION, 0);
+ PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -676,13 +704,22 @@
// No availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.GONE, availability.getVisibility());
- // No new story.
- View personIcon = result.findViewById(R.id.person_icon_only);
- View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+ // Shows person icon.
+ View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
- assertEquals(View.GONE, personIconWithStory.getVisibility());
// No status.
- assertThat((View) result.findViewById(R.id.status)).isNull();
+ 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
@@ -694,7 +731,7 @@
PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
ACTIVITY_GAME).build())).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mMockContext,
- tileWithAvailabilityAndNewStory, 0);
+ tileWithAvailabilityAndNewStory, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -705,13 +742,23 @@
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
- // Has new story.
- View personIcon = result.findViewById(R.id.person_icon_only);
- View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
- assertEquals(View.GONE, personIcon.getVisibility());
- assertEquals(View.VISIBLE, personIconWithStory.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.status)).isNull();
+ 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
@@ -723,7 +770,7 @@
PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
ACTIVITY_BIRTHDAY).build())).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0);
+ tileWithStatusTemplate, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -731,14 +778,25 @@
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
- // Has new story.
- View personIcon = result.findViewById(R.id.person_icon_only);
- View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
- assertEquals(View.GONE, personIcon.getVisibility());
- assertEquals(View.VISIBLE, personIconWithStory.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.status);
+ 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
@@ -748,7 +806,7 @@
Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY)).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusTemplate, 0);
+ tileWithStatusTemplate, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -756,14 +814,25 @@
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
- // Has new story.
- View personIcon = result.findViewById(R.id.person_icon_only);
- View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
- assertEquals(View.GONE, personIcon.getVisibility());
- assertEquals(View.VISIBLE, personIconWithStory.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.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
@@ -774,7 +843,7 @@
.setNotificationContent(MISSED_CALL)
.build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithMissedCallNotification, 0);
+ tileWithMissedCallNotification, 0, mOptions);
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -782,16 +851,25 @@
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.GONE, availability.getVisibility());
- // Has new story.
- View personIcon = result.findViewById(R.id.person_icon_only);
- View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
+ // Has person icon.
+ View personIcon = result.findViewById(R.id.person_icon);
assertEquals(View.VISIBLE, personIcon.getVisibility());
- assertEquals(View.GONE, personIconWithStory.getVisibility());
- // Has status.
- TextView statusContent = (TextView) result.findViewById(R.id.status);
+ // 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() {
@@ -800,41 +878,35 @@
.setStatuses(Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY)).build();
RemoteViews views = PeopleSpaceUtils.createRemoteViews(mContext,
- tileWithStatusAndNotification, 0);
+ 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);
- assertTrue(subtext.getText().toString().contains("weeks ago"));
+ assertEquals(View.GONE, subtext.getVisibility());
// Has availability.
View availability = result.findViewById(R.id.availability);
assertEquals(View.VISIBLE, availability.getVisibility());
- // Has new story.
- View personIcon = result.findViewById(R.id.person_icon_only);
- View personIconWithStory = result.findViewById(R.id.person_icon_with_story);
- assertEquals(View.GONE, personIcon.getVisibility());
- assertEquals(View.VISIBLE, personIconWithStory.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.content);
+ TextView statusContent = (TextView) result.findViewById(R.id.text_content);
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
- }
- @Test
- public void testGetPeopleTileFromPersistentStorageExistingConversation()
- throws Exception {
- when(mPeopleManager.getConversation(PACKAGE_NAME, 0, SHORTCUT_ID_1)).thenReturn(
- getConversationChannelWithoutTimestamp(SHORTCUT_ID_1));
- PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID_1, 0, PACKAGE_NAME);
- PeopleSpaceTile tile = getPeopleTileFromPersistentStorage(mContext, key, mPeopleManager);
- assertThat(tile.getId()).isEqualTo(key.getShortcutId());
- }
+ 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);
- @Test
- public void testGetPeopleTileFromPersistentStorageNoConversation() {
- PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID_2, 0, PACKAGE_NAME);
- PeopleSpaceTile tile = getPeopleTileFromPersistentStorage(mContext, key, mPeopleManager);
- assertThat(tile).isNull();
+ // 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,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
index fce0217..0ef3ca2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
@@ -20,11 +20,14 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
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.content.Intent;
+import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.testing.AndroidTestingRunner;
@@ -37,6 +40,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.wmshell.BubblesManager;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +50,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -55,7 +61,7 @@
private static final String NOTIF_KEY = "notifKey";
private static final String NOTIF_KEY_NO_ENTRY = "notifKeyNoEntry";
private static final String NOTIF_KEY_NO_RANKING = "notifKeyNoRanking";
-
+ private static final String NOTIF_KEY_CAN_BUBBLE = "notifKeyCanBubble";
private static final UserHandle USER_HANDLE = UserHandle.of(0);
private static final int NOTIF_COUNT = 10;
@@ -72,16 +78,27 @@
@Mock
private NotificationEntry mNotifEntryNoRanking;
@Mock
+ private NotificationEntry mNotifEntryCanBubble;
+ @Mock
+ private BubblesManager mBubblesManager;
+ @Mock
private NotificationListenerService.Ranking mRanking;
@Captor
private ArgumentCaptor<NotificationVisibility> mNotificationVisibilityCaptor;
+ private Intent mIntent;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mActivity = new LaunchConversationActivity(mNotificationEntryManager);
+ mActivity = new LaunchConversationActivity(mNotificationEntryManager,
+ Optional.of(mBubblesManager));
+ mActivity.setIsForTesting(true, mIStatusBarService);
+ mIntent = new Intent();
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, "tile ID");
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, PACKAGE_NAME);
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, USER_HANDLE);
when(mNotificationEntryManager.getActiveNotificationsCount()).thenReturn(NOTIF_COUNT);
when(mNotificationEntryManager.getPendingOrActiveNotif(NOTIF_KEY)).thenReturn(mNotifEntry);
@@ -89,15 +106,21 @@
.thenReturn(null);
when(mNotificationEntryManager.getPendingOrActiveNotif(NOTIF_KEY_NO_RANKING))
.thenReturn(mNotifEntryNoRanking);
+ when(mNotificationEntryManager.getPendingOrActiveNotif(NOTIF_KEY_CAN_BUBBLE))
+ .thenReturn(mNotifEntryCanBubble);
when(mNotifEntry.getRanking()).thenReturn(mRanking);
+ when(mNotifEntryCanBubble.getRanking()).thenReturn(mRanking);
+ when(mNotifEntryCanBubble.canBubble()).thenReturn(true);
when(mNotifEntryNoRanking.getRanking()).thenReturn(null);
when(mRanking.getRank()).thenReturn(NOTIF_RANK);
}
@Test
public void testDoNotClearNotificationIfNoKey() throws Exception {
- mActivity.clearNotificationIfPresent(mIStatusBarService,
- EMPTY_STRING, PACKAGE_NAME, USER_HANDLE);
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+ EMPTY_STRING);
+ mActivity.setIntent(mIntent);
+ mActivity.onCreate(new Bundle());
verify(mIStatusBarService, never()).onNotificationClear(
any(), anyInt(), any(), anyInt(), anyInt(), any());
@@ -105,8 +128,10 @@
@Test
public void testDoNotClearNotificationIfNoNotificationEntry() throws Exception {
- mActivity.clearNotificationIfPresent(mIStatusBarService,
- NOTIF_KEY_NO_ENTRY, PACKAGE_NAME, USER_HANDLE);
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+ NOTIF_KEY_NO_ENTRY);
+ mActivity.setIntent(mIntent);
+ mActivity.onCreate(new Bundle());
verify(mIStatusBarService, never()).onNotificationClear(
any(), anyInt(), any(), anyInt(), anyInt(), any());
@@ -114,24 +139,41 @@
@Test
public void testDoNotClearNotificationIfNoRanking() throws Exception {
- mActivity.clearNotificationIfPresent(mIStatusBarService,
- NOTIF_KEY_NO_RANKING, PACKAGE_NAME, USER_HANDLE);
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+ NOTIF_KEY_NO_RANKING);
+ mActivity.setIntent(mIntent);
+ mActivity.onCreate(new Bundle());
verify(mIStatusBarService, never()).onNotificationClear(
any(), anyInt(), any(), anyInt(), anyInt(), any());
}
@Test
- public void testClearNotification() throws Exception {
- mActivity.clearNotificationIfPresent(mIStatusBarService,
- NOTIF_KEY, PACKAGE_NAME, USER_HANDLE);
+ public void testEntryClearsNotificationAndDoesNotOpenBubble() throws Exception {
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+ NOTIF_KEY);
+ mActivity.setIntent(mIntent);
+ mActivity.onCreate(new Bundle());
verify(mIStatusBarService, times(1)).onNotificationClear(any(),
anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
+ verify(mBubblesManager, never()).expandStackAndSelectBubble(any());
NotificationVisibility nv = mNotificationVisibilityCaptor.getValue();
assertThat(nv.count).isEqualTo(NOTIF_COUNT);
assertThat(nv.rank).isEqualTo(NOTIF_RANK);
}
+ @Test
+ public void testBubbleEntryOpensBubbleAndDoesNotClearNotification() throws Exception {
+ mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+ NOTIF_KEY_CAN_BUBBLE);
+ mActivity.setIntent(mIntent);
+ mActivity.onCreate(new Bundle());
+
+ // Don't clear the notification for bubbles.
+ verify(mIStatusBarService, never()).onNotificationClear(any(),
+ anyInt(), any(), anyInt(), anyInt(), any());
+ verify(mBubblesManager, times(1)).expandStackAndSelectBubble(eq(mNotifEntryCanBubble));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index aef75be..5834aef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -73,6 +73,7 @@
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NoManSimulator;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -134,6 +135,9 @@
private PeopleSpaceWidgetManager mManager;
@Mock
+ private Context mMockContext;
+
+ @Mock
private NotificationListener mListenerService;
@Mock
@@ -144,6 +148,8 @@
private PeopleManager mPeopleManager;
@Mock
private LauncherApps mLauncherApps;
+ @Mock
+ private NotificationEntryManager mNotificationEntryManager;
@Captor
private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -159,10 +165,10 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLauncherApps = mock(LauncherApps.class);
- mManager =
- new PeopleSpaceWidgetManager(mContext);
+ mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
+ mManager = new PeopleSpaceWidgetManager(mContext);
mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager,
- mLauncherApps);
+ mLauncherApps, mNotificationEntryManager);
mManager.attach(mListenerService);
mProvider = new PeopleSpaceWidgetProvider();
mProvider.setPeopleSpaceWidgetManager(mManager);
@@ -626,14 +632,6 @@
mManager.updateWidgets(widgetIdsArray);
mClock.advanceTime(MIN_LINGER_DURATION);
- // If we had to fetch Tile from persistent storage, we want to make sure we write it to
- // options.
- verify(mAppWidgetManager, times(1))
- .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
- mBundleArgumentCaptor.capture());
- Bundle bundle = mBundleArgumentCaptor.getValue();
- PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
- assertThat(tile.getId()).isEqualTo(SHORTCUT_ID);
verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
any());
}
@@ -676,17 +674,22 @@
mManager.onAppWidgetOptionsChanged(SECOND_WIDGET_ID_WITH_SHORTCUT, newOptions);
- verify(mAppWidgetManager, times(1)).updateAppWidgetOptions(
+ verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(
eq(SECOND_WIDGET_ID_WITH_SHORTCUT), mBundleArgumentCaptor.capture());
- Bundle bundle = mBundleArgumentCaptor.getValue();
- assertThat(bundle.getString(PeopleSpaceUtils.SHORTCUT_ID, EMPTY_STRING))
+ List<Bundle> bundles = mBundleArgumentCaptor.getAllValues();
+ Bundle first = bundles.get(0);
+ assertThat(first.getString(PeopleSpaceUtils.SHORTCUT_ID, EMPTY_STRING))
.isEqualTo(EMPTY_STRING);
- assertThat(bundle.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID);
- assertThat(bundle.getString(PACKAGE_NAME, EMPTY_STRING)).isEqualTo(EMPTY_STRING);
+ assertThat(first.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(INVALID_USER_ID);
+ assertThat(first.getString(PACKAGE_NAME, EMPTY_STRING)).isEqualTo(EMPTY_STRING);
verify(mLauncherApps, times(1)).cacheShortcuts(eq(TEST_PACKAGE_A),
eq(Arrays.asList(SHORTCUT_ID)), eq(UserHandle.of(0)),
eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS));
+ Bundle second = bundles.get(1);
+ PeopleSpaceTile tile = second.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getId()).isEqualTo(SHORTCUT_ID);
+
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
assertThat(sp.getStringSet(KEY.toString(), new HashSet<>())).contains(
String.valueOf(SECOND_WIDGET_ID_WITH_SHORTCUT));
@@ -699,6 +702,52 @@
assertThat(widgetSp.getInt(USER_ID, INVALID_USER_ID)).isEqualTo(0);
}
+ @Test
+ public void testGetPeopleTileFromPersistentStorageExistingConversation()
+ throws Exception {
+ when(mIPeopleManager.getConversation(PACKAGE_NAME, 0, SHORTCUT_ID)).thenReturn(
+ getConversationWithShortcutId(SHORTCUT_ID));
+ PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, PACKAGE_NAME);
+ PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key);
+ assertThat(tile.getId()).isEqualTo(key.getShortcutId());
+ }
+
+ @Test
+ public void testGetPeopleTileFromPersistentStorageNoConversation() {
+ PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, PACKAGE_NAME);
+ PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key);
+ assertThat(tile).isNull();
+ }
+
+ @Test
+ public void testRequestPinAppWidgetExistingConversation() throws Exception {
+ when(mMockContext.getPackageName()).thenReturn(PACKAGE_NAME);
+ when(mMockContext.getUserId()).thenReturn(0);
+ when(mIPeopleManager.getConversation(PACKAGE_NAME, 0, SHORTCUT_ID))
+ .thenReturn(getConversationWithShortcutId(SHORTCUT_ID));
+ when(mAppWidgetManager.requestPinAppWidget(any(), any(), any())).thenReturn(true);
+
+ ShortcutInfo info = new ShortcutInfo.Builder(mMockContext, SHORTCUT_ID).build();
+ boolean valid = mManager.requestPinAppWidget(info);
+
+ assertThat(valid).isTrue();
+ verify(mAppWidgetManager, times(1)).requestPinAppWidget(
+ any(), any(), any());
+ }
+
+ @Test
+ public void testRequestPinAppWidgetNoConversation() throws Exception {
+ when(mMockContext.getPackageName()).thenReturn(PACKAGE_NAME);
+ when(mMockContext.getUserId()).thenReturn(0);
+ when(mIPeopleManager.getConversation(PACKAGE_NAME, 0, SHORTCUT_ID)).thenReturn(null);
+
+ ShortcutInfo info = new ShortcutInfo.Builder(mMockContext, SHORTCUT_ID).build();
+ boolean valid = mManager.requestPinAppWidget(info);
+
+ assertThat(valid).isFalse();
+ verify(mAppWidgetManager, never()).requestPinAppWidget(any(), any(), any());
+ }
+
/**
* Adds another widget for {@code PERSON_TILE} with widget ID: {@code
* SECOND_WIDGET_ID_WITH_SHORTCUT}.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 072f7b8..791dd12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -395,9 +395,8 @@
`when`(permissionManager.indicatorAppOpUsageData).thenReturn(
listOf(usage_camera, usage_location, usage_microphone)
)
- `when`(privacyItemController.micCameraAvailable).thenReturn(false)
- `when`(privacyItemController.locationAvailable).thenReturn(false)
- `when`(privacyItemController.allIndicatorsAvailable).thenReturn(true)
+ `when`(privacyItemController.micCameraAvailable).thenReturn(true)
+ `when`(privacyItemController.locationAvailable).thenReturn(true)
controller.showDialog(context)
exhaustExecutors()
@@ -422,7 +421,6 @@
)
`when`(privacyItemController.micCameraAvailable).thenReturn(false)
`when`(privacyItemController.locationAvailable).thenReturn(false)
- `when`(privacyItemController.allIndicatorsAvailable).thenReturn(false)
controller.showDialog(context)
exhaustExecutors()
@@ -525,7 +523,6 @@
`when`(privacyItemController.locationAvailable).thenReturn(true)
`when`(privacyItemController.micCameraAvailable).thenReturn(true)
- `when`(privacyItemController.allIndicatorsAvailable).thenReturn(false)
`when`(userTracker.userProfiles).thenReturn(listOf(
UserInfo(USER_ID, "", 0),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
index 132bee0..f991e71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -37,7 +37,6 @@
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -51,8 +50,6 @@
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
fun <T> any(): T = Mockito.any<T>()
- private const val ALL_INDICATORS =
- SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
}
@@ -96,11 +93,6 @@
}
@Test
- fun testNotListeningAllByDefault() {
- assertFalse(privacyItemController.allIndicatorsAvailable)
- }
-
- @Test
fun testMicCameraListeningByDefault() {
assertTrue(privacyItemController.micCameraAvailable)
}
@@ -111,10 +103,8 @@
executor.runAllReady()
verify(callback).onFlagMicCameraChanged(false)
- verify(callback, never()).onFlagAllChanged(anyBoolean())
assertFalse(privacyItemController.micCameraAvailable)
- assertFalse(privacyItemController.allIndicatorsAvailable)
}
@Test
@@ -127,26 +117,15 @@
}
@Test
- fun testAllChanged() {
- changeAll(true)
- executor.runAllReady()
-
- verify(callback).onFlagAllChanged(true)
- verify(callback, never()).onFlagMicCameraChanged(anyBoolean())
-
- assertTrue(privacyItemController.allIndicatorsAvailable)
- }
-
- @Test
fun testBothChanged() {
changeAll(true)
changeMicCamera(false)
executor.runAllReady()
- verify(callback, atLeastOnce()).onFlagAllChanged(true)
+ verify(callback, atLeastOnce()).onFlagLocationChanged(true)
verify(callback, atLeastOnce()).onFlagMicCameraChanged(false)
- assertTrue(privacyItemController.allIndicatorsAvailable)
+ assertTrue(privacyItemController.locationAvailable)
assertFalse(privacyItemController.micCameraAvailable)
}
@@ -186,28 +165,6 @@
}
@Test
- fun testSomeListening_stillListening() {
- // Mic and camera are true by default
- changeAll(true)
- executor.runAllReady()
- changeAll(false)
- executor.runAllReady()
-
- verify(appOpsController, never()).removeCallback(any(), any())
- }
-
- @Test
- fun testAllDeleted_micCameraFalse_stopListening() {
- changeMicCamera(false)
- changeAll(true)
- executor.runAllReady()
- changeAll(null)
- executor.runAllReady()
-
- verify(appOpsController).removeCallback(any(), any())
- }
-
- @Test
fun testMicDeleted_stillListening() {
changeMicCamera(true)
executor.runAllReady()
@@ -219,7 +176,10 @@
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeLocation(value: Boolean?) = changeProperty(LOCATION, value)
- private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
+ private fun changeAll(value: Boolean?) {
+ changeMicCamera(value)
+ changeLocation(value)
+ }
private fun changeProperty(name: String, value: Boolean?) {
deviceConfigProxy.setProperty(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index 7ca468e..b87c7a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -43,7 +43,6 @@
import org.junit.Assert.assertThat
import org.junit.Assert.assertTrue
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -72,8 +71,8 @@
val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
const val TEST_PACKAGE_NAME = "test"
- private const val ALL_INDICATORS =
- SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
+ private const val LOCATION_INDICATOR =
+ SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
@@ -119,7 +118,8 @@
deviceConfigProxy = DeviceConfigProxyFake()
// Listen to everything by default
- changeAll(true)
+ changeMicCamera(true)
+ changeLocation(true)
`when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0)))
@@ -259,9 +259,8 @@
}
@Test
- @Ignore // TODO(b/168209929)
fun testNotListeningWhenIndicatorsDisabled() {
- changeAll(false)
+ changeLocation(false)
changeMicCamera(false)
privacyItemController.addCallback(callback)
executor.runAllReady()
@@ -271,7 +270,7 @@
@Test
fun testNotSendingLocationWhenOnlyMicCamera() {
- changeAll(false)
+ changeLocation(false)
changeMicCamera(true)
executor.runAllReady()
@@ -294,7 +293,7 @@
.`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback)
- changeAll(false)
+ changeLocation(false)
changeMicCamera(true)
executor.runAllReady()
reset(callback) // Clean callback
@@ -521,7 +520,7 @@
}
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
- private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
+ private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value)
private fun changeProperty(name: String, value: Boolean?) {
deviceConfigProxy.setProperty(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index fb817ea..a2113b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -242,10 +242,18 @@
when(mMediaHost.getVisible()).thenReturn(true);
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+ mQSCustomizerController, mMediaHost,
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
+ mQSCustomizerController, mMediaHost,
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+
assertThat(mController.shouldUseHorizontalLayout()).isFalse();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 862e374..5422ae8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -14,6 +14,9 @@
package com.android.systemui.qs;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -24,6 +27,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
+import android.content.DialogInterface;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
@@ -74,6 +79,8 @@
private final String VPN_PACKAGE = "TestVPN";
private final String VPN_PACKAGE_2 = "TestVPN 2";
private static final String PARENTAL_CONTROLS_LABEL = "Parental Control App";
+ private static final ComponentName DEVICE_OWNER_COMPONENT =
+ new ComponentName("TestDPC", "Test");
private ViewGroup mRootView;
private TextView mFooterText;
@@ -101,6 +108,11 @@
mFooterIcon = mRootView.findViewById(R.id.footer_icon);
mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
mFooter.setHostEnvironment(null);
+
+ when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
+ .thenReturn(DEVICE_OWNER_COMPONENT);
+ when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
}
@Test
@@ -148,6 +160,27 @@
}
@Test
+ public void testManagedFinancedDeviceWithOwnerName() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+ when(mSecurityController.getDeviceOwnerOrganizationName())
+ .thenReturn(MANAGING_ORGANIZATION);
+ when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+
+ mFooter.refreshState();
+
+ TestableLooper.get(this).processAllMessages();
+ assertEquals(mContext.getString(
+ R.string.quick_settings_financed_disclosure_named_management,
+ MANAGING_ORGANIZATION), mFooterText.getText());
+ assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mFooterIcon.getVisibility());
+ assertEquals(View.GONE, mPrimaryFooterIcon.getVisibility());
+ // -1 == never set.
+ assertEquals(-1, mFooterIcon.getLastImageResource());
+ }
+
+ @Test
public void testManagedDemoMode() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null);
@@ -383,6 +416,25 @@
}
@Test
+ public void testGetManagementTitleForNonFinancedDevice() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+
+ assertEquals(mContext.getString(R.string.monitoring_title_device_owned),
+ mFooter.getManagementTitle(MANAGING_ORGANIZATION));
+ }
+
+ @Test
+ public void testGetManagementTitleForFinancedDevice() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+ when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+
+ assertEquals(mContext.getString(R.string.monitoring_title_financed_device,
+ MANAGING_ORGANIZATION),
+ mFooter.getManagementTitle(MANAGING_ORGANIZATION));
+ }
+
+ @Test
public void testGetManagementMessage_noManagement() {
assertEquals(null, mFooter.getManagementMessage(
/* isDeviceManaged= */ false,
@@ -409,6 +461,21 @@
}
@Test
+ public void testGetManagementMessage_deviceOwner_asFinancedDevice() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+ when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+
+ assertEquals(mContext.getString(R.string.monitoring_financed_description_named_management,
+ MANAGING_ORGANIZATION, MANAGING_ORGANIZATION),
+ mFooter.getManagementMessage(
+ /* isDeviceManaged= */ true,
+ MANAGING_ORGANIZATION,
+ /* isProfileOwnerOfOrganizationOwnedDevice= */ false,
+ /* workProfileOrganizationName= */ null));
+ }
+
+ @Test
public void testGetManagementMessage_profileOwnerOfOrganizationOwnedDevice() {
assertEquals(mContext.getString(R.string.monitoring_description_named_management,
MANAGING_ORGANIZATION),
@@ -587,6 +654,34 @@
assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText());
}
+ @Test
+ public void testCreateDialogViewForFinancedDevice() {
+ when(mSecurityController.isDeviceManaged()).thenReturn(true);
+ when(mSecurityController.getDeviceOwnerOrganizationName())
+ .thenReturn(MANAGING_ORGANIZATION);
+ when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+
+ // Initialize AlertDialog which sets the text for the negative button, which is used when
+ // creating the dialog for a financed device.
+ mFooter.showDeviceMonitoringDialog();
+ // The above statement would display the Quick Settings dialog which requires user input,
+ // so simulate the press to continue with the unit test (otherwise, it is stuck).
+ mFooter.onClick(null, DialogInterface.BUTTON_NEGATIVE);
+ View view = mFooter.createDialogView();
+
+ TextView managementSubtitle = view.findViewById(R.id.device_management_subtitle);
+ assertEquals(View.VISIBLE, managementSubtitle.getVisibility());
+ assertEquals(mContext.getString(R.string.monitoring_title_financed_device,
+ MANAGING_ORGANIZATION), managementSubtitle.getText());
+ TextView managementMessage = view.findViewById(R.id.device_management_warning);
+ assertEquals(View.VISIBLE, managementMessage.getVisibility());
+ assertEquals(mContext.getString(R.string.monitoring_financed_description_named_management,
+ MANAGING_ORGANIZATION, MANAGING_ORGANIZATION), managementMessage.getText());
+ assertEquals(mContext.getString(R.string.monitoring_button_view_policies),
+ mFooter.getSettingsButton());
+ }
+
private CharSequence addLink(CharSequence description) {
final SpannableStringBuilder message = new SpannableStringBuilder();
message.append(description);
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 97a8459..4948c2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -148,7 +148,7 @@
@Test
fun testIgnoredSlotsOnAttached_noIndicators() {
- setPrivacyController(false, false, false)
+ setPrivacyController(micCamera = false, location = false)
controller.init()
@@ -160,7 +160,7 @@
@Test
fun testIgnoredSlotsOnAttached_onlyMicCamera() {
- setPrivacyController(false, true, false)
+ setPrivacyController(micCamera = true, location = false)
controller.init()
@@ -177,7 +177,7 @@
@Test
fun testIgnoredSlotsOnAttached_onlyLocation() {
- setPrivacyController(false, false, true)
+ setPrivacyController(micCamera = false, location = true)
controller.init()
@@ -192,26 +192,7 @@
@Test
fun testIgnoredSlotsOnAttached_locationMicCamera() {
- setPrivacyController(false, true, true)
-
- controller.init()
-
- val captor = argumentCaptor<List<String>>()
- verify(iconContainer).setIgnoredSlots(capture(captor))
-
- val cameraString = mContext.resources.getString(
- com.android.internal.R.string.status_bar_camera)
- val micString = mContext.resources.getString(
- com.android.internal.R.string.status_bar_microphone)
- val locationString = mContext.resources.getString(
- com.android.internal.R.string.status_bar_location)
-
- assertThat(captor.value).containsExactly(cameraString, micString, locationString)
- }
-
- @Test
- fun testIgnoredSlotsOnAttached_all() {
- setPrivacyController(true, false, false)
+ setPrivacyController(micCamera = true, location = true)
controller.init()
@@ -248,8 +229,7 @@
`when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
}
- private fun setPrivacyController(all: Boolean, micCamera: Boolean, location: Boolean) {
- `when`(privacyItemController.allIndicatorsAvailable).thenReturn(all)
+ private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
`when`(privacyItemController.micCameraAvailable).thenReturn(micCamera)
`when`(privacyItemController.locationAvailable).thenReturn(location)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
new file mode 100644
index 0000000..33166cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.qs.tiles;
+
+import static android.content.pm.PackageManager.FEATURE_NFC_HOST_CARD_EMULATION;
+import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
+import android.service.quicksettings.Tile;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class QuickAccessWalletTileTest extends SysuiTestCase {
+
+ @Mock
+ private QSTileHost mHost;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private QSLogger mQSLogger;
+ private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+ @Mock
+ private QuickAccessWalletClient mQuickAccessWalletClient;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private SecureSettings mSecureSettings;
+ @Mock
+ private FeatureFlags mFeatureFlags;
+
+ private TestableLooper mTestableLooper;
+ private QuickAccessWalletTile mTile;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTestableLooper = TestableLooper.get(this);
+
+ when(mHost.getContext()).thenReturn(mContext);
+ when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
+ when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(true);
+
+ mTile = new QuickAccessWalletTile(
+ mHost,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mQuickAccessWalletClient,
+ mKeyguardStateController,
+ mPackageManager,
+ mSecureSettings,
+ mFeatureFlags);
+ }
+
+ @Test
+ public void testNewTile() {
+ assertFalse(mTile.newTileState().handlesLongClick);
+ }
+
+ @Test
+ public void testIsAvailable_featureFlagIsOff() {
+ when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(false);
+ assertFalse(mTile.isAvailable());
+ }
+
+ @Test
+ public void testIsAvailable_qawServiceNotAvailable() {
+ when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
+ assertFalse(mTile.isAvailable());
+ }
+
+ @Test
+ public void testIsAvailable_qawServiceAvailable() {
+ when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);
+ when(mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT)).thenReturn("Component");
+
+ assertTrue(mTile.isAvailable());
+ }
+
+ @Test
+ public void testHandleClick_openGPay() {
+ Intent intent = new Intent("WalletIntent");
+ when(mQuickAccessWalletClient.createWalletIntent()).thenReturn(intent);
+ mTile.handleClick();
+
+ verify(mActivityStarter, times(1))
+ .postStartActivityDismissingKeyguard(eq(intent), anyInt());
+ }
+
+ @Test
+ public void testHandleUpdateState_updateLabelAndIcon() {
+ QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_wallet);
+ QSTile.State state = new QSTile.State();
+ when(mQuickAccessWalletClient.getServiceLabel()).thenReturn("QuickAccessWallet");
+
+ mTile.handleUpdateState(state, new Object());
+
+ assertEquals("QuickAccessWallet", state.label.toString());
+ assertTrue(state.label.toString().contentEquals(state.contentDescription));
+ assertEquals(icon, state.icon);
+ }
+
+ @Test
+ public void testHandleUpdateState_deviceLocked_tileInactive() {
+ QSTile.State state = new QSTile.State();
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+ when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
+
+ mTile.handleUpdateState(state, new Object());
+
+ assertEquals(Tile.STATE_INACTIVE, state.state);
+ assertNull(state.stateDescription);
+ }
+
+ @Test
+ public void testHandleUpdateState_deviceLocked_tileActive() {
+ QSTile.State state = new QSTile.State();
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+ when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
+
+ mTile.handleUpdateState(state, new Object());
+
+ assertEquals(Tile.STATE_ACTIVE, state.state);
+ assertTrue(state.secondaryLabel.toString().contentEquals(state.stateDescription));
+ assertEquals(
+ getContext().getString(R.string.wallet_secondary_label),
+ state.secondaryLabel.toString());
+ }
+
+ @Test
+ public void testHandleUpdateState_qawFeatureUnavailable_tileUnavailable() {
+ QSTile.State state = new QSTile.State();
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+ when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false);
+
+ mTile.handleUpdateState(state, new Object());
+
+ assertEquals(Tile.STATE_UNAVAILABLE, state.state);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
deleted file mode 100644
index 25104b8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
+++ /dev/null
@@ -1,123 +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.systemui.recents;
-
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.pm.PackageManager;
-import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
-import android.testing.TestableLooper;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.RemoteTransitions;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-import dagger.Lazy;
-
-/**
- * Unit tests for {@link com.android.systemui.recents.OverviewProxyService}
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class OverviewProxyServiceTest extends SysuiTestCase {
- private OverviewProxyService mSpiedOverviewProxyService;
- private TestableContext mSpiedContext;
-
- @Mock private BroadcastDispatcher mMockBroadcastDispatcher;
- @Mock private CommandQueue mMockCommandQueue;
- @Mock private Lazy<NavigationBarController> mMockNavBarControllerLazy;
- @Mock private IPinnedStackAnimationListener mMockPinnedStackAnimationListener;
- @Mock private NavigationModeController mMockNavModeController;
- @Mock private NotificationShadeWindowController mMockStatusBarWinController;
- @Mock private Optional<Pip> mMockPipOptional;
- @Mock private Optional<LegacySplitScreen> mMockLegacySplitScreenOptional;
- @Mock private Optional<SplitScreen> mMockSplitScreenOptional;
- @Mock private Optional<Lazy<StatusBar>> mMockStatusBarOptionalLazy;
- @Mock private Optional<com.android.wm.shell.onehanded.OneHanded> mMockOneHandedOptional;
- @Mock private PackageManager mPackageManager;
- @Mock private SysUiState mMockSysUiState;
- @Mock private RemoteTransitions mMockTransitions;
- @Mock private Optional<StartingSurface> mStartingSurface;
-
- @Before
- public void setUp() throws RemoteException {
- MockitoAnnotations.initMocks(this);
-
- mSpiedContext = spy(mContext);
-
- when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
- when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
-
- mSpiedOverviewProxyService = spy(new OverviewProxyService(mSpiedContext, mMockCommandQueue,
- mMockNavBarControllerLazy, mMockNavModeController, mMockStatusBarWinController,
- mMockSysUiState, mMockPipOptional, mMockLegacySplitScreenOptional,
- mMockSplitScreenOptional, mMockStatusBarOptionalLazy, mMockOneHandedOptional,
- mMockBroadcastDispatcher, mMockTransitions, mStartingSurface));
- }
-
- @Test
- public void testNonPipDevice_shouldNotNotifySwipeToHomeFinished() throws RemoteException {
- mSpiedOverviewProxyService.mSysUiProxy.notifySwipeToHomeFinished();
-
- verify(mMockPipOptional, never()).ifPresent(any());
- }
-
- @Test
- public void testNonPipDevice_shouldNotSetPinnedStackAnimationListener() throws RemoteException {
- mSpiedOverviewProxyService.mSysUiProxy.setPinnedStackAnimationListener(
- mMockPinnedStackAnimationListener);
-
- verify(mMockPipOptional, never()).ifPresent(any());
- }
-
- @Test
- public void testNonPipDevice_shouldNotSetShelfHeight() throws RemoteException {
- mSpiedOverviewProxyService.mSysUiProxy.setShelfHeight(true /* visible */,
- 100 /* shelfHeight */);
-
- verify(mMockPipOptional, never()).ifPresent(any());
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
index 9e62a62..63f7c97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
@@ -24,6 +24,7 @@
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
+import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.view.IScrollCaptureCallbacks;
@@ -37,19 +38,15 @@
class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub {
private final int[] mColors = {Color.RED, Color.GREEN, Color.BLUE};
private IScrollCaptureCallbacks mCallbacks;
- private Surface mSurface;
private Paint mPaint;
private int mNextColor;
private HwuiContext mHwuiContext;
-
- FakeScrollCaptureConnection(IScrollCaptureCallbacks cb) {
- mCallbacks = cb;
- }
+ private CancellationSignal mCancellationSignal;
@Override
- public ICancellationSignal startCapture(Surface surface) {
- mSurface = surface;
- mHwuiContext = new HwuiContext(false, surface);
+ public ICancellationSignal startCapture(Surface surface, IScrollCaptureCallbacks callbacks) {
+ mCallbacks = callbacks;
+ mHwuiContext = new HwuiContext(surface);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
try {
@@ -57,7 +54,9 @@
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
- return null;
+ ICancellationSignal signal = CancellationSignal.createTransport();
+ mCancellationSignal = CancellationSignal.fromTransport(signal);
+ return signal;
}
@Override
@@ -72,7 +71,9 @@
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
- return null;
+ ICancellationSignal signal = CancellationSignal.createTransport();
+ mCancellationSignal = CancellationSignal.fromTransport(signal);
+ return signal;
}
@Override
@@ -83,15 +84,15 @@
e.rethrowAsRuntimeException();
} finally {
mHwuiContext.destroy();
- mSurface = null;
mCallbacks = null;
}
- return null;
+ ICancellationSignal signal = CancellationSignal.createTransport();
+ mCancellationSignal = CancellationSignal.fromTransport(signal);
+ return signal;
}
@Override
public void close() throws RemoteException {
-
}
// From android.view.Surface, but issues render requests synchronously with waitForPresent(true)
@@ -99,21 +100,16 @@
private final RenderNode mRenderNode;
private final HardwareRenderer mHardwareRenderer;
private RecordingCanvas mCanvas;
- private final boolean mIsWideColorGamut;
- HwuiContext(boolean isWideColorGamut, Surface surface) {
+ HwuiContext(Surface surface) {
mRenderNode = RenderNode.create("HwuiCanvas", null);
mRenderNode.setClipToBounds(false);
mRenderNode.setForceDarkAllowed(false);
- mIsWideColorGamut = isWideColorGamut;
mHardwareRenderer = new HardwareRenderer();
mHardwareRenderer.setContentRoot(mRenderNode);
mHardwareRenderer.setSurface(surface, true);
- mHardwareRenderer.setColorMode(
- isWideColorGamut
- ? ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
- : ActivityInfo.COLOR_MODE_DEFAULT);
+ mHardwareRenderer.setColorMode(ActivityInfo.COLOR_MODE_DEFAULT);
mHardwareRenderer.setLightSourceAlpha(0.0f, 0.0f);
mHardwareRenderer.setLightSourceGeometry(0.0f, 0.0f, 0.0f, 0.0f);
}
@@ -142,9 +138,5 @@
void destroy() {
mHardwareRenderer.destroy();
}
-
- boolean isWideColorGamut() {
- return mIsWideColorGamut;
- }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index 802b462..cf7dc20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -16,15 +16,15 @@
package com.android.systemui.screenshot;
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
import static java.util.Objects.requireNonNull;
@@ -34,7 +34,7 @@
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.view.Display;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindowManager;
import android.view.ScrollCaptureResponse;
@@ -43,30 +43,29 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
-import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.google.common.util.concurrent.ListenableFuture;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import org.mockito.stubbing.Answer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class ScrollCaptureClientTest extends SysuiTestCase {
- private static final float MAX_PAGES = 3f;
+ private static final float MAX_PAGES = 3.0f;
private Context mContext;
private IWindowManager mWm;
- @Spy private TestableConsumer<Session> mSessionConsumer;
- @Spy private TestableConsumer<Connection> mConnectionConsumer;
- @Spy private TestableConsumer<CaptureResult> mResultConsumer;
- @Mock private Runnable mRunnable;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -79,46 +78,50 @@
}
@Test
- public void testBasicClientFlow() throws RemoteException {
+ public void testDetectAndConnect()
+ throws RemoteException, InterruptedException, ExecutionException, TimeoutException {
doAnswer((Answer<Void>) invocation -> {
- IScrollCaptureCallbacks cb = invocation.getArgument(3);
- cb.onScrollCaptureResponse(new ScrollCaptureResponse.Builder()
+ IScrollCaptureResponseListener listener = invocation.getArgument(3);
+ listener.onScrollCaptureResponse(new ScrollCaptureResponse.Builder()
.setBoundsInWindow(new Rect(0, 0, 100, 100))
.setWindowBounds(new Rect(0, 0, 100, 100))
- .setConnection(new FakeScrollCaptureConnection(cb))
+ .setConnection(new FakeScrollCaptureConnection())
.build());
return null;
}).when(mWm).requestScrollCapture(/* displayId */ anyInt(), /* token */ isNull(),
- /* taskId */ anyInt(), any(IScrollCaptureCallbacks.class));
+ /* taskId */ anyInt(), any(IScrollCaptureResponseListener.class));
// Create client
ScrollCaptureClient client = new ScrollCaptureClient(mContext, mWm);
- client.request(Display.DEFAULT_DISPLAY, mConnectionConsumer);
- verify(mConnectionConsumer, timeout(100)).accept(any(Connection.class));
+ // Request scroll capture
+ ListenableFuture<ScrollCaptureResponse> requestFuture =
+ client.request(Display.DEFAULT_DISPLAY);
+ assertNotNull(requestFuture.get(100, TimeUnit.MILLISECONDS));
- Connection conn = mConnectionConsumer.getValue();
+ ScrollCaptureResponse response = requestFuture.get();
+ assertTrue(response.isConnected());
- conn.start(mSessionConsumer, MAX_PAGES);
- verify(mSessionConsumer, timeout(100)).accept(any(Session.class));
+ // Start a session
+ ListenableFuture<Session> startFuture = client.start(response, MAX_PAGES);
+ assertNotNull(startFuture.get(100, TimeUnit.MILLISECONDS));
- Session session = mSessionConsumer.getValue();
+ Session session = startFuture.get();
Rect request = new Rect(0, 0, session.getPageWidth(), session.getTileHeight());
- session.requestTile(0, mResultConsumer);
- verify(mResultConsumer, timeout(100)).accept(any(CaptureResult.class));
+ // Request a tile
+ ListenableFuture<CaptureResult> tileFuture = session.requestTile(0);
+ assertNotNull(tileFuture.get(100, TimeUnit.MILLISECONDS));
- CaptureResult result = mResultConsumer.getValue();
- assertThat(result.requested).isEqualTo(request);
- assertThat(result.captured).isEqualTo(result.requested);
- assertThat(result.image).isNotNull();
+ CaptureResult result = tileFuture.get();
+ assertEquals(request, result.requested);
+ assertEquals(result.requested, result.captured);
+ assertNotNull(result.image);
- session.end(mRunnable);
- verify(mRunnable, timeout(100)).run();
-
- // TODO verify image
- // TODO test threading
- // TODO test failures
+ // End the session
+ ListenableFuture<Void> endFuture = session.end();
+ CountDownLatch latch = new CountDownLatch(1);
+ endFuture.addListener(latch::countDown, Runnable::run);
+ assertTrue(latch.await(100, TimeUnit.MILLISECONDS));
}
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
new file mode 100644
index 0000000..410d9de
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.screenshot;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.testing.AndroidTestingRunner;
+import android.view.ScrollCaptureResponse;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.systemui.SysuiTestCase;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Tests for ScrollCaptureController which manages sequential image acquisition for long
+ * screenshots.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ScrollCaptureControllerTest extends SysuiTestCase {
+
+ private static class FakeSession implements ScrollCaptureClient.Session {
+ public int availableTop = Integer.MIN_VALUE;
+ 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;
+
+ @Override
+ public ListenableFuture<ScrollCaptureClient.CaptureResult> requestTile(int 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);
+ if (emptyInsteadOfPartial && captured.height() != getTileHeight()) {
+ captured = new Rect();
+ }
+ Image image = mock(Image.class);
+ when(image.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class));
+ ScrollCaptureClient.CaptureResult result =
+ new ScrollCaptureClient.CaptureResult(image, requested, captured);
+ return Futures.immediateFuture(result);
+ }
+
+ public int getMaxHeight() {
+ return getTileHeight() * getMaxTiles();
+ }
+
+ @Override
+ public int getMaxTiles() {
+ return 10;
+ }
+
+ @Override
+ public int getTileHeight() {
+ return 50;
+ }
+
+ @Override
+ public int getPageHeight() {
+ return 100;
+ }
+
+ @Override
+ public int getPageWidth() {
+ return 100;
+ }
+
+ @Override
+ public Rect getWindowBounds() {
+ return null;
+ }
+
+ @Override
+ public ListenableFuture<Void> end() {
+ return Futures.immediateVoidFuture();
+ }
+
+ @Override
+ public void release() {
+ }
+ }
+
+ private ScrollCaptureController mController;
+ private FakeSession mSession;
+ private ScrollCaptureClient mScrollCaptureClient;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mSession = new FakeSession();
+ mScrollCaptureClient = mock(ScrollCaptureClient.class);
+ when(mScrollCaptureClient.request(anyInt(), anyInt())).thenReturn(
+ Futures.immediateFuture(new ScrollCaptureResponse.Builder().build()));
+ when(mScrollCaptureClient.start(any(), anyFloat())).thenReturn(
+ Futures.immediateFuture(mSession));
+ mController = new ScrollCaptureController(context, context.getMainExecutor(),
+ mScrollCaptureClient, new ImageTileSet(context.getMainThreadHandler()));
+ }
+
+ @Test
+ public void testInfinite() throws ExecutionException, InterruptedException {
+ ScrollCaptureController.LongScreenshot screenshot =
+ mController.run(new ScrollCaptureResponse.Builder().build()).get();
+ assertEquals(mSession.getMaxHeight(), screenshot.getHeight());
+ // TODO: the top and bottom ratio in the infinite case should be extracted and tested.
+ assertEquals(-150, screenshot.getTop());
+ assertEquals(350, screenshot.getBottom());
+ }
+
+ @Test
+ public void testLimitedBottom() throws ExecutionException, InterruptedException {
+ // We hit the bottom of the content, so expect it to scroll back up and go above the -150
+ // default top position
+ mSession.availableBottom = 275;
+ ScrollCaptureController.LongScreenshot screenshot =
+ mController.run(new ScrollCaptureResponse.Builder().build()).get();
+ // Bottom tile will be 25px tall, 10 tiles total
+ assertEquals(mSession.getMaxHeight() - 25, screenshot.getHeight());
+ assertEquals(-200, screenshot.getTop());
+ assertEquals(mSession.availableBottom, screenshot.getBottom());
+ }
+
+ @Test
+ public void testLimitedTopAndBottom() throws ExecutionException, InterruptedException {
+ mSession.availableBottom = 275;
+ mSession.availableTop = -200;
+ ScrollCaptureController.LongScreenshot screenshot =
+ mController.run(new ScrollCaptureResponse.Builder().build()).get();
+ assertEquals(mSession.availableBottom - mSession.availableTop, screenshot.getHeight());
+ assertEquals(mSession.availableTop, screenshot.getTop());
+ assertEquals(mSession.availableBottom, screenshot.getBottom());
+ }
+
+ @Test
+ public void testVeryLimitedTopInfiniteBottom() throws ExecutionException, InterruptedException {
+ // Hit the boundary before the "headroom" is hit in the up direction, then go down
+ // infinitely.
+ mSession.availableTop = -55;
+ ScrollCaptureController.LongScreenshot screenshot =
+ mController.run(new ScrollCaptureResponse.Builder().build()).get();
+ // The top tile will be 5px tall, so subtract 45px from the theoretical max.
+ assertEquals(mSession.getMaxHeight() - 45, screenshot.getHeight());
+ assertEquals(mSession.availableTop, screenshot.getTop());
+ assertEquals(mSession.availableTop + mSession.getMaxHeight() - 45, screenshot.getBottom());
+ }
+
+ @Test
+ public void testVeryLimitedTopLimitedBottom() throws ExecutionException, InterruptedException {
+ mSession.availableBottom = 275;
+ mSession.availableTop = -55;
+ ScrollCaptureController.LongScreenshot screenshot =
+ mController.run(new ScrollCaptureResponse.Builder().build()).get();
+ assertEquals(mSession.availableBottom - mSession.availableTop, screenshot.getHeight());
+ assertEquals(mSession.availableTop, screenshot.getTop());
+ assertEquals(mSession.availableBottom, screenshot.getBottom());
+ }
+
+ @Test
+ public void testLimitedTopAndBottomWithEmpty() throws ExecutionException, InterruptedException {
+ mSession.emptyInsteadOfPartial = true;
+ mSession.availableBottom = 275;
+ mSession.availableTop = -167;
+ ScrollCaptureController.LongScreenshot screenshot =
+ mController.run(new ScrollCaptureResponse.Builder().build()).get();
+ // Expecting output from -150 to 250
+ assertEquals(400, screenshot.getHeight());
+ assertEquals(-150, screenshot.getTop());
+ assertEquals(250, screenshot.getBottom());
+ }
+
+ @Test
+ public void testVeryLimitedTopWithEmpty() throws ExecutionException, InterruptedException {
+ // Hit the boundary before the "headroom" is hit in the up direction, then go down
+ // infinitely.
+ mSession.availableTop = -55;
+ mSession.emptyInsteadOfPartial = true;
+ ScrollCaptureController.LongScreenshot screenshot =
+ mController.run(new ScrollCaptureResponse.Builder().build()).get();
+ assertEquals(mSession.getMaxHeight(), screenshot.getHeight());
+ assertEquals(-50, screenshot.getTop());
+ assertEquals(-50 + mSession.getMaxHeight(), screenshot.getBottom());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java
similarity index 74%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java
index 6564d58..06b39ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java
@@ -16,15 +16,16 @@
package com.android.systemui.screenshot;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Intent;
-import android.graphics.Rect;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.util.Log;
import android.view.Display;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindowManager;
import android.view.ScrollCaptureResponse;
import android.view.WindowManagerGlobal;
@@ -45,8 +46,9 @@
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class ScrollCaptureTest extends SysuiTestCase {
- private static final String TAG = "ScrollCaptureTest";
+public class ScrollCaptureFrameworkSmokeTest extends SysuiTestCase {
+ private static final String TAG = "ScrollCaptureFrameworkSmokeTest";
+ private volatile ScrollCaptureResponse mResponse;
/**
* Verifies that a request traverses from SystemUI, to WindowManager and to the app process and
@@ -64,34 +66,23 @@
final CountDownLatch latch = new CountDownLatch(1);
try {
wms.requestScrollCapture(Display.DEFAULT_DISPLAY, null, -1,
- new IScrollCaptureCallbacks.Stub() {
+ new IScrollCaptureResponseListener.Stub() {
@Override
- public void onScrollCaptureResponse(ScrollCaptureResponse response)
+ public void onScrollCaptureResponse(
+ ScrollCaptureResponse response)
throws RemoteException {
- Log.d(TAG, "onScrollCaptureResponse: " + response);
+ mResponse = response;
latch.countDown();
}
-
- @Override
- public void onCaptureStarted() {
- }
-
- @Override
- public void onImageRequestCompleted(int i, Rect rect)
- throws RemoteException {
-
- }
-
- @Override
- public void onCaptureEnded() throws RemoteException {
-
- }
-
});
} catch (RemoteException e) {
Log.e(TAG, "request failed", e);
fail("caught remote exception " + e);
}
latch.await(1000, TimeUnit.MILLISECONDS);
+
+ assertNotNull(mResponse);
+ assertTrue(mResponse.isConnected());
+ assertTrue(mResponse.getWindowTitle().contains(ScrollViewActivity.class.getSimpleName()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TestableConsumer.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TestableConsumer.java
deleted file mode 100644
index a554e5f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TestableConsumer.java
+++ /dev/null
@@ -1,33 +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.systemui.screenshot;
-
-import java.util.function.Consumer;
-
-/** Accepts and retains the most recent value for verification */
-class TestableConsumer<T> implements Consumer<T> {
- T mValue;
-
- @Override
- public void accept(T t) {
- mValue = t;
- }
-
- public T getValue() {
- return mValue;
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 8ec03d7..58738e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -29,6 +29,7 @@
import android.graphics.Rect;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -464,6 +465,14 @@
}
@Test
+ public void testSetUdfpsHbmListener() {
+ final IUdfpsHbmListener listener = mock(IUdfpsHbmListener.class);
+ mCommandQueue.setUdfpsHbmListener(listener);
+ waitForIdleSync();
+ verify(mCallbacks).setUdfpsHbmListener(eq(listener));
+ }
+
+ @Test
public void testSuppressAmbientDisplay() {
mCommandQueue.suppressAmbientDisplay(true);
waitForIdleSync();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 5c37656..d4b21c6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -76,6 +76,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -141,6 +142,8 @@
@Mock
private LauncherApps mLauncherApps;
@Mock
+ private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
+ @Mock
private ShortcutManager mShortcutManager;
@Mock
private NotificationGuts mNotificationGuts;
@@ -236,6 +239,8 @@
.thenReturn(mock(NotificationManager.Policy.class));
when(mBuilder.build()).thenReturn(mock(PriorityOnboardingDialogController.class));
+
+ when(mPeopleSpaceWidgetManager.requestPinAppWidget(any())).thenReturn(true);
}
@Test
@@ -244,6 +249,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -257,7 +263,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
assertEquals(mIconDrawable, view.getDrawable());
}
@@ -269,6 +276,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -282,7 +290,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
assertTrue(textView.getText().toString().contains("App Name"));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -322,6 +331,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -335,7 +345,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertTrue(textView.getText().toString().contains(group.getName()));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -348,6 +359,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -361,7 +373,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
assertEquals(GONE, textView.getVisibility());
@@ -373,6 +386,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -386,7 +400,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
}
@@ -409,6 +424,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -422,7 +438,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -435,6 +452,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -451,7 +469,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -465,6 +484,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -478,7 +498,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -490,6 +511,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -506,7 +528,8 @@
mBuilderProvider,
false,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -519,6 +542,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -532,7 +556,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View view = mNotificationInfo.findViewById(R.id.silence);
assertThat(view.isSelected()).isTrue();
}
@@ -548,6 +573,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -561,7 +587,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
assertThat(view.isSelected()).isTrue();
assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -580,6 +607,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -593,7 +621,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
assertThat(view.isSelected()).isTrue();
assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -611,6 +640,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -624,7 +654,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -656,6 +687,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -669,7 +701,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mTestableLooper.processAllMessages();
@@ -700,6 +733,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -713,7 +747,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View silence = mNotificationInfo.findViewById(R.id.silence);
@@ -745,6 +780,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -758,7 +794,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -783,6 +820,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -796,7 +834,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -820,6 +859,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -833,7 +873,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -861,6 +902,7 @@
-1, // no action selected by default
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -874,7 +916,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
// THEN the selected action is -1, so the selected option is "Default" priority
assertEquals(mNotificationInfo.getSelectedAction(), -1);
@@ -892,6 +935,7 @@
NotificationConversationInfo.ACTION_FAVORITE, // "Favorite" selected by default
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -905,7 +949,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
// THEN the selected action is "Favorite", so the selected option is "priority" priority
assertEquals(mNotificationInfo.getSelectedAction(),
@@ -922,6 +967,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -935,7 +981,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -959,6 +1006,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -972,7 +1020,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -996,6 +1045,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -1009,7 +1059,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1032,6 +1083,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -1045,7 +1097,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
View silence = mNotificationInfo.findViewById(R.id.silence);
silence.performClick();
@@ -1067,6 +1120,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -1080,7 +1134,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
anyString(), anyInt(), any(), eq(CONVERSATION_ID));
@@ -1093,6 +1148,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -1106,7 +1162,8 @@
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
anyString(), anyInt(), any(), eq(CONVERSATION_ID));
@@ -1115,7 +1172,7 @@
@Test
public void testSelectPriorityPresentsOnboarding_firstTime() {
// GIVEN pref is false
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, false);
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, false);
// GIVEN the priority onboarding screen is present
PriorityOnboardingDialogController.Builder b =
@@ -1129,6 +1186,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -1142,7 +1200,8 @@
() -> b,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
// WHEN user clicks "priority"
mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
@@ -1158,7 +1217,7 @@
@Test
public void testSelectPriorityDoesNotShowOnboarding_secondTime() {
//WHEN pref is true
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true);
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
PriorityOnboardingDialogController.Builder b =
mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
@@ -1170,6 +1229,7 @@
-1,
mShortcutManager,
mMockPackageManager,
+ mPeopleSpaceWidgetManager,
mMockINotificationManager,
mOnUserInteractionCallback,
TEST_PACKAGE_NAME,
@@ -1183,12 +1243,128 @@
() -> b,
true,
mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager));
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+
+ // WHEN user clicks "priority"
+ mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
+ verify(controller, never()).show();
+
+ // and then done
+ mNotificationInfo.findViewById(R.id.done).performClick();
+
+ // THEN the user is presented with the priority onboarding screen
+ verify(controller, never()).show();
+ }
+
+ @Test
+ public void testSelectPriorityRequestsPinPeopleTile() {
+ //WHEN pref is true and channel is default importance
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
+ mNotificationChannel.setImportantConversation(false);
+ mNotificationInfo.bindNotification(
+ -1,
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ mBuilderProvider,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
// WHEN user clicks "priority"
mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
- // THEN the user is presented with the priority onboarding screen
- verify(controller, never()).show();
+ // and then done
+ mNotificationInfo.findViewById(R.id.done).performClick();
+
+ // THEN the user is presented with the People Tile pinning request
+ verify(mPeopleSpaceWidgetManager, times(1)).requestPinAppWidget(any());
+ }
+
+ @Test
+ public void testSelectDefaultDoesNotRequestPinPeopleTile() {
+ //WHEN pref is true
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
+
+ mNotificationInfo.bindNotification(
+ -1,
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ mBuilderProvider,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+
+ // WHEN user clicks "default"
+ mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_DEFAULT);
+
+ // and then done
+ mNotificationInfo.findViewById(R.id.done).performClick();
+
+ // THEN the user is not presented with the People Tile pinning request
+ verify(mPeopleSpaceWidgetManager, never()).requestPinAppWidget(mShortcutInfo);
+ }
+
+ @Test
+ public void testSelectPriority_AlreadyPriority_DoesNotRequestPinPeopleTile() {
+ //WHEN pref is true and channel is priority
+ Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
+ mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportantConversation(true);
+
+ mNotificationInfo.bindNotification(
+ -1,
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ mBuilderProvider,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+
+ // WHEN user clicks "priority"
+ mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
+
+ // and then done
+ mNotificationInfo.findViewById(R.id.done).performClick();
+
+ // THEN the user is not presented with the People Tile pinning request
+ verify(mPeopleSpaceWidgetManager, never()).requestPinAppWidget(mShortcutInfo);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 97313ba..950b95f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -260,6 +261,7 @@
mGutsManager,
true,
null,
+ new FalsingManagerFake(),
new FalsingCollectorFake(),
mPeopleNotificationIdentifier,
Optional.of(mock(BubblesManager.class))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 458a058..18481bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -67,17 +67,20 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
@@ -126,12 +129,15 @@
@Mock private AccessibilityManager mAccessibilityManager;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private INotificationManager mINotificationManager;
+ @Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private LauncherApps mLauncherApps;
@Mock private ShortcutManager mShortcutManager;
@Mock private ChannelEditorDialogController mChannelEditorDialogController;
@Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Mock private UserContextProvider mContextTracker;
@Mock private BubblesManager mBubblesManager;
+ @Mock private ShadeController mShadeController;
+ @Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
@Mock(answer = Answers.RETURNS_SELF)
private PriorityOnboardingDialogController.Builder mBuilder;
private Provider<PriorityOnboardingDialogController.Builder> mProvider = () -> mBuilder;
@@ -154,10 +160,10 @@
mGutsManager = new NotificationGutsManager(mContext,
() -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
- mINotificationManager, mLauncherApps, mShortcutManager,
- mChannelEditorDialogController, mContextTracker, mProvider,
- mAssistantFeedbackController, Optional.of(mBubblesManager),
- new UiEventLoggerFake(), mOnUserInteractionCallback);
+ mINotificationManager, mNotificationEntryManager, mPeopleSpaceWidgetManager,
+ mLauncherApps, mShortcutManager, mChannelEditorDialogController, mContextTracker,
+ mProvider, mAssistantFeedbackController, Optional.of(mBubblesManager),
+ new UiEventLoggerFake(), mOnUserInteractionCallback, mShadeController);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mCheckSaveListener, mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index f3813fc..b4a3393 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -45,6 +45,7 @@
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -429,6 +430,7 @@
mock(OnExpandClickListener.class),
mock(NotificationMediaManager.class),
mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
+ new FalsingManagerFake(),
new FalsingCollectorFake(),
mStatusBarStateController,
mPeopleNotificationIdentifier,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index bc014ec..d9e9389 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -431,4 +431,14 @@
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
assertThat(mBouncer.inTransit()).isFalse();
}
+
+ @Test
+ public void testUpdateResources_delegatesToRootView() {
+ mBouncer.ensureView();
+ mBouncer.updateResources();
+
+ // This is mocked, so won't pick up on the call to updateResources via
+ // mKeyguardViewController.init(), only updateResources above.
+ verify(mKeyguardHostViewController).updateResources();
+ }
}
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 ec5114e..91f3611 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
@@ -536,8 +536,7 @@
@Test
public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
mStatusBarStateController.setState(SHADE);
- when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ enableSplitShade();
mNotificationPanelViewController.setQsExpanded(true);
assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse();
@@ -562,6 +561,7 @@
private void enableSplitShade() {
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ mNotificationPanelViewController.updateResources();
}
private void onTouchEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index b1f1b5e..116e1b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -917,11 +917,11 @@
HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER,
ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED,
- ScrimState.BUBBLE_EXPANDED, ScrimState.SHADE_LOCKED));
+ ScrimState.BUBBLE_EXPANDED, ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED));
for (ScrimState state : ScrimState.values()) {
if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
- Assert.fail("Scrim state not whitelisted nor blacklisted as low power mode");
+ Assert.fail("Scrim state isn't categorized as a low power or regular state.");
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 83030ec..45b7917 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -276,4 +276,11 @@
verify(action).onDismiss();
verify(cancelAction, never()).run();
}
+
+ @Test
+ public void testUpdateResources_delegatesToBouncer() {
+ mStatusBarKeyguardViewManager.updateResources();
+
+ verify(mBouncer).updateResources();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 8f01b24..781cde6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -78,6 +78,7 @@
import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
@@ -211,6 +212,7 @@
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private NavigationBarController mNavigationBarController;
+ @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
@Mock private BypassHeadsUpNotifier mBypassHeadsUpNotifier;
@Mock private SysuiColorExtractor mColorExtractor;
@Mock private ColorExtractor.GradientColors mGradientColors;
@@ -387,6 +389,7 @@
mVisualStabilityManager,
mDeviceProvisionedController,
mNavigationBarController,
+ mAccessibilityFloatingMenuController,
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
@@ -887,6 +890,13 @@
verify(mDozeServiceHost).setDozeSuppressed(false);
}
+ @Test
+ public void testUpdateResources_updatesBouncer() {
+ mStatusBar.updateResources();
+
+ verify(mStatusBarKeyguardViewManager).updateResources();
+ }
+
public static class TestableNotificationInterruptStateProviderImpl extends
NotificationInterruptStateProviderImpl {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index 44184ee..ed87a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -62,6 +64,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SecurityControllerTest extends SysuiTestCase {
+ private static final ComponentName DEVICE_OWNER_COMPONENT =
+ new ComponentName("com.android.foo", "bar");
+
private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
private final UserManager mUserManager = mock(UserManager.class);
@@ -127,6 +132,22 @@
}
@Test
+ public void testGetDeviceOwnerComponentOnAnyUser() {
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+ .thenReturn(DEVICE_OWNER_COMPONENT);
+ assertEquals(mSecurityController.getDeviceOwnerComponentOnAnyUser(),
+ DEVICE_OWNER_COMPONENT);
+ }
+
+ @Test
+ public void testGetDeviceOwnerType() {
+ when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+ assertEquals(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT),
+ DEVICE_OWNER_TYPE_FINANCED);
+ }
+
+ @Test
public void testWorkAccount() throws Exception {
assertFalse(mSecurityController.hasCACertInCurrentUser());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index c0722a4..3640bcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -15,6 +15,7 @@
package com.android.systemui.utils.leaks;
import android.app.admin.DeviceAdminInfo;
+import android.content.ComponentName;
import android.graphics.drawable.Drawable;
import android.testing.LeakCheck;
@@ -68,6 +69,16 @@
}
@Override
+ public ComponentName getDeviceOwnerComponentOnAnyUser() {
+ return null;
+ }
+
+ @Override
+ public int getDeviceOwnerType(ComponentName admin) {
+ return 0;
+ }
+
+ @Override
public boolean isNetworkLoggingEnabled() {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java
index d40eecf..6b68946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java
@@ -34,10 +34,6 @@
}
@Override
- public void removeAllCallbacks() {
- }
-
- @Override
public void removeCallbacks(Runnable runnable) {
}
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
index f8b9309..f0de811 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
@@ -360,7 +360,7 @@
try {
mCallback.setProxyPort(port);
} catch (RemoteException e) {
- Log.w(TAG, "Proxy failed to report port to PacProxyInstaller", e);
+ Log.w(TAG, "Proxy failed to report port to PacProxyService", e);
}
}
mPort = port;
@@ -371,7 +371,7 @@
try {
callback.setProxyPort(mPort);
} catch (RemoteException e) {
- Log.w(TAG, "Proxy failed to report port to PacProxyInstaller", e);
+ Log.w(TAG, "Proxy failed to report port to PacProxyService", e);
}
}
mCallback = callback;
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
index bdf478d..a8e2622 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
@@ -30,7 +30,7 @@
private static ProxyServer server = null;
- /** Keep these values up-to-date with PacProxyInstaller.java */
+ /** Keep these values up-to-date with PacProxyService.java */
public static final String KEY_PROXY = "keyProxy";
public static final String HOST = "localhost";
public static final String EXCL_LIST = "";
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 5dd271c..f06a940 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -340,5 +340,9 @@
// Notify the user that window magnification is available.
// package: android
NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE = 1004;
+
+ // Notify the user that some accessibility service has view and control permissions.
+ // package: android
+ NOTE_A11Y_VIEW_AND_CONTROL_ACCESS = 1005;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index b3be044..e7ffb1a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -309,13 +309,19 @@
*/
public AccessibilityManagerService(Context context) {
mContext = context;
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mPowerManager = context.getSystemService(PowerManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mA11yController = mWindowManagerService.getAccessibilityController();
mMainHandler = new MainHandler(mContext.getMainLooper());
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManager = mContext.getPackageManager();
- mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
+ PolicyWarningUIController policyWarningUIController;
+ if (AccessibilitySecurityPolicy.POLICY_WARNING_ENABLED) {
+ policyWarningUIController = new PolicyWarningUIController(mMainHandler, context,
+ new PolicyWarningUIController.NotificationController(context));
+ }
+ mSecurityPolicy = new AccessibilitySecurityPolicy(policyWarningUIController, mContext,
+ this);
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
@@ -351,6 +357,8 @@
if (isA11yTracingEnabled()) {
logTrace(LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState);
}
+ mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
+ userState.mBoundServices);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
}
@@ -1302,6 +1310,7 @@
AccessibilityUserState userState = getCurrentUserStateLocked();
readConfigurationForUserStateLocked(userState);
+ mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
// Even if reading did not yield change, we have to update
// the state since the context in which the current user
// state was used has changed since it was inactive.
@@ -3665,6 +3674,8 @@
}
} else if (mEnabledAccessibilityServicesUri.equals(uri)) {
if (readEnabledAccessibilityServicesLocked(userState)) {
+ mSecurityPolicy.onEnabledServicesChangedLocked(userState.mUserId,
+ userState.mEnabledServices);
userState.updateCrashedServicesIfNeededLocked();
onUserStateChangedLocked(userState);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index bef6d3e..fd355d8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -41,21 +41,19 @@
import libcore.util.EmptyArray;
+import java.util.ArrayList;
+import java.util.Set;
+
/**
* This class provides APIs of accessibility security policies for accessibility manager
- * to grant accessibility capabilities or events access right to accessibility service.
+ * to grant accessibility capabilities or events access right to accessibility services. And also
+ * monitors the current bound accessibility services to prompt permission warnings for
+ * not accessibility-categorized ones.
*/
public class AccessibilitySecurityPolicy {
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
private static final String LOG_TAG = "AccessibilitySecurityPolicy";
- private final Context mContext;
- private final PackageManager mPackageManager;
- private final UserManager mUserManager;
- private final AppOpsManager mAppOpsManager;
-
- private AppWidgetManagerInternal mAppWidgetService;
-
private static final int KEEP_SOURCE_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED
| AccessibilityEvent.TYPE_VIEW_FOCUSED
| AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
@@ -72,6 +70,8 @@
| AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
| AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY;
+ public static final boolean POLICY_WARNING_ENABLED = true;
+
/**
* Methods that should find their way into separate modules, but are still in AMS
* TODO (b/111889696): Refactoring UserState to AccessibilityUserManager.
@@ -84,19 +84,32 @@
// TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
}
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final UserManager mUserManager;
+ private final AppOpsManager mAppOpsManager;
private final AccessibilityUserManager mAccessibilityUserManager;
+ private final PolicyWarningUIController mPolicyWarningUIController;
+ /** All bound accessibility services which don't belong to accessibility category. */
+ private final ArraySet<ComponentName> mNonA11yCategoryServices = new ArraySet<>();
+
+ private AppWidgetManagerInternal mAppWidgetService;
private AccessibilityWindowManager mAccessibilityWindowManager;
+ private int mCurrentUserId = UserHandle.USER_NULL;
/**
* Constructor for AccessibilityManagerService.
*/
- public AccessibilitySecurityPolicy(@NonNull Context context,
+ public AccessibilitySecurityPolicy(PolicyWarningUIController policyWarningUIController,
+ @NonNull Context context,
@NonNull AccessibilityUserManager a11yUserManager) {
mContext = context;
mAccessibilityUserManager = a11yUserManager;
mPackageManager = mContext.getPackageManager();
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mPolicyWarningUIController = policyWarningUIController;
+ mPolicyWarningUIController.setAccessibilityPolicyManager(this);
}
/**
@@ -568,4 +581,98 @@
+ permission);
}
}
+
+ /**
+ * Called after a service was bound or unbound. Checks the current bound accessibility
+ * services and updates alarms.
+ *
+ * @param userId The user id
+ * @param boundServices The bound services
+ */
+ public void onBoundServicesChangedLocked(int userId,
+ ArrayList<AccessibilityServiceConnection> boundServices) {
+ if (!POLICY_WARNING_ENABLED) {
+ return;
+ }
+ if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) {
+ return;
+ }
+
+ ArraySet<ComponentName> tempNonA11yCategoryServices = new ArraySet<>();
+ for (int i = 0; i < boundServices.size(); i++) {
+ final AccessibilityServiceInfo a11yServiceInfo = boundServices.get(
+ i).getServiceInfo();
+ final ComponentName service = a11yServiceInfo.getComponentName().clone();
+ if (!isA11yCategoryService(a11yServiceInfo)) {
+ tempNonA11yCategoryServices.add(service);
+ if (mNonA11yCategoryServices.contains(service)) {
+ mNonA11yCategoryServices.remove(service);
+ } else {
+ mPolicyWarningUIController.onNonA11yCategoryServiceBound(userId, service);
+ }
+ }
+ }
+
+ for (int i = 0; i < mNonA11yCategoryServices.size(); i++) {
+ final ComponentName service = mNonA11yCategoryServices.valueAt(i);
+ mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(userId, service);
+ }
+ mNonA11yCategoryServices.clear();
+ mNonA11yCategoryServices.addAll(tempNonA11yCategoryServices);
+ }
+
+ /**
+ * Called after switching to another user. Resets data and cancels old alarms after
+ * switching to another user.
+ *
+ * @param userId The user id
+ * @param enabledServices The enabled services
+ */
+ public void onSwitchUserLocked(int userId, Set<ComponentName> enabledServices) {
+ if (!POLICY_WARNING_ENABLED) {
+ return;
+ }
+ if (mCurrentUserId == userId) {
+ return;
+ }
+
+ mPolicyWarningUIController.onSwitchUserLocked(userId, enabledServices);
+
+ for (int i = 0; i < mNonA11yCategoryServices.size(); i++) {
+ mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(mCurrentUserId,
+ mNonA11yCategoryServices.valueAt(i));
+ }
+ mNonA11yCategoryServices.clear();
+ mCurrentUserId = userId;
+ }
+
+ /**
+ * Called after the enabled accessibility services changed.
+ *
+ * @param userId The user id
+ * @param enabledServices The enabled services
+ */
+ public void onEnabledServicesChangedLocked(int userId,
+ Set<ComponentName> enabledServices) {
+ if (!POLICY_WARNING_ENABLED) {
+ return;
+ }
+ if (mAccessibilityUserManager.getCurrentUserIdLocked() != userId) {
+ return;
+ }
+
+ mPolicyWarningUIController.onEnabledServicesChangedLocked(userId, enabledServices);
+ }
+
+ /**
+ * Identifies whether the accessibility service is true and designed for accessibility. An
+ * accessibility service is considered as accessibility category if
+ * {@link AccessibilityServiceInfo#isAccessibilityTool} is true.
+ *
+ * @param serviceInfo The accessibility service's serviceInfo.
+ * @return Returns true if it is a true accessibility service.
+ */
+ public boolean isA11yCategoryService(AccessibilityServiceInfo serviceInfo) {
+ return serviceInfo.isAccessibilityTool();
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java
new file mode 100644
index 0000000..ea3e650
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/PolicyWarningUIController.java
@@ -0,0 +1,370 @@
+/*
+ * 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.accessibility;
+
+import static android.app.AlarmManager.RTC_WAKEUP;
+
+import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_VIEW_AND_CONTROL_ACCESS;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.Manifest;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.app.ActivityOptions;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ImageUtils;
+
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The class handles permission warning notifications for not accessibility-categorized
+ * accessibility services from {@link AccessibilitySecurityPolicy}. And also maintains the setting
+ * {@link Settings.Secure#NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES} in order not to
+ * resend notifications to the same service.
+ */
+public class PolicyWarningUIController {
+ private static final String TAG = PolicyWarningUIController.class.getSimpleName();
+ @VisibleForTesting
+ protected static final String ACTION_SEND_NOTIFICATION = TAG + ".ACTION_SEND_NOTIFICATION";
+ @VisibleForTesting
+ protected static final String ACTION_A11Y_SETTINGS = TAG + ".ACTION_A11Y_SETTINGS";
+ @VisibleForTesting
+ protected static final String ACTION_DISMISS_NOTIFICATION =
+ TAG + ".ACTION_DISMISS_NOTIFICATION";
+ private static final int SEND_NOTIFICATION_DELAY_HOURS = 24;
+
+ /** Current enabled accessibility services. */
+ private final ArraySet<ComponentName> mEnabledA11yServices = new ArraySet<>();
+
+ private final Handler mMainHandler;
+ private final AlarmManager mAlarmManager;
+ private final Context mContext;
+ private final NotificationController mNotificationController;
+
+ public PolicyWarningUIController(@NonNull Handler handler, @NonNull Context context,
+ NotificationController notificationController) {
+ mMainHandler = handler;
+ mContext = context;
+ mNotificationController = notificationController;
+ mAlarmManager = mContext.getSystemService(AlarmManager.class);
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_SEND_NOTIFICATION);
+ filter.addAction(ACTION_A11Y_SETTINGS);
+ filter.addAction(ACTION_DISMISS_NOTIFICATION);
+ mContext.registerReceiver(mNotificationController, filter,
+ Manifest.permission.MANAGE_ACCESSIBILITY, mMainHandler);
+
+ }
+
+ protected void setAccessibilityPolicyManager(
+ AccessibilitySecurityPolicy accessibilitySecurityPolicy) {
+ mNotificationController.setAccessibilityPolicyManager(accessibilitySecurityPolicy);
+ }
+
+ /**
+ * Updates enabled accessibility services and notified accessibility services after switching
+ * to another user.
+ *
+ * @param enabledServices The current enabled services
+ */
+ public void onSwitchUserLocked(int userId, Set<ComponentName> enabledServices) {
+ mEnabledA11yServices.clear();
+ mEnabledA11yServices.addAll(enabledServices);
+ mMainHandler.sendMessage(obtainMessage(mNotificationController::onSwitchUser, userId));
+ }
+
+ /**
+ * Computes the newly disabled services and removes its record from the setting
+ * {@link Settings.Secure#NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES} after detecting the
+ * setting {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} changed.
+ *
+ * @param userId The user id
+ * @param enabledServices The enabled services
+ */
+ public void onEnabledServicesChangedLocked(int userId,
+ Set<ComponentName> enabledServices) {
+ final ArraySet<ComponentName> disabledServices = new ArraySet<>(mEnabledA11yServices);
+ disabledServices.removeAll(enabledServices);
+ mEnabledA11yServices.clear();
+ mEnabledA11yServices.addAll(enabledServices);
+ mMainHandler.sendMessage(
+ obtainMessage(mNotificationController::onServicesDisabled, userId,
+ disabledServices));
+ }
+
+ /**
+ * Called when the target service is bound. Sets an 24 hours alarm to the service which is not
+ * notified yet to execute action {@code ACTION_SEND_NOTIFICATION}.
+ *
+ * @param userId The user id
+ * @param service The service's component name
+ */
+ public void onNonA11yCategoryServiceBound(int userId, ComponentName service) {
+ mMainHandler.sendMessage(obtainMessage(this::setAlarm, userId, service));
+ }
+
+ /**
+ * Called when the target service is unbound. Cancels the old alarm with intent action
+ * {@code ACTION_SEND_NOTIFICATION} from the service.
+ *
+ * @param userId The user id
+ * @param service The service's component name
+ */
+ public void onNonA11yCategoryServiceUnbound(int userId, ComponentName service) {
+ mMainHandler.sendMessage(obtainMessage(this::cancelAlarm, userId, service));
+ }
+
+ private void setAlarm(int userId, ComponentName service) {
+ final Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.HOUR, SEND_NOTIFICATION_DELAY_HOURS);
+ mAlarmManager.set(RTC_WAKEUP, cal.getTimeInMillis(),
+ createPendingIntent(mContext, userId, ACTION_SEND_NOTIFICATION,
+ service.flattenToShortString()));
+ }
+
+ private void cancelAlarm(int userId, ComponentName service) {
+ mAlarmManager.cancel(
+ createPendingIntent(mContext, userId, ACTION_SEND_NOTIFICATION,
+ service.flattenToShortString()));
+ }
+
+ protected static PendingIntent createPendingIntent(Context context, int userId, String action,
+ String serviceComponentName) {
+ final Intent intent = new Intent(action);
+ intent.setPackage(context.getPackageName())
+ .setIdentifier(serviceComponentName)
+ .putExtra(Intent.EXTRA_USER_ID, userId);
+ return PendingIntent.getBroadcast(context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ }
+
+ /** A sub class to handle notifications and settings on the main thread. */
+ @MainThread
+ public static class NotificationController extends BroadcastReceiver {
+ private static final char RECORD_SEPARATOR = ':';
+
+ /** All accessibility services which are notified to the user by the policy warning rule. */
+ private final ArraySet<ComponentName> mNotifiedA11yServices = new ArraySet<>();
+ private final NotificationManager mNotificationManager;
+ private final Context mContext;
+
+ private int mCurrentUserId;
+ private AccessibilitySecurityPolicy mAccessibilitySecurityPolicy;
+
+ public NotificationController(Context context) {
+ mContext = context;
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+ }
+
+ protected void setAccessibilityPolicyManager(
+ AccessibilitySecurityPolicy accessibilitySecurityPolicy) {
+ mAccessibilitySecurityPolicy = accessibilitySecurityPolicy;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final String service = intent.getIdentifier();
+ final ComponentName componentName = ComponentName.unflattenFromString(service);
+ if (TextUtils.isEmpty(action) || TextUtils.isEmpty(service)
+ || componentName == null) {
+ return;
+ }
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_SYSTEM);
+ if (ACTION_SEND_NOTIFICATION.equals(action)) {
+ trySendNotification(userId, componentName);
+ } else if (ACTION_A11Y_SETTINGS.equals(action)) {
+ launchSettings(userId, componentName);
+ mNotificationManager.cancel(service, NOTE_A11Y_VIEW_AND_CONTROL_ACCESS);
+ onNotificationCanceled(userId, componentName);
+ } else if (ACTION_DISMISS_NOTIFICATION.equals(action)) {
+ onNotificationCanceled(userId, componentName);
+ }
+ }
+
+ protected void onSwitchUser(int userId) {
+ mCurrentUserId = userId;
+ mNotifiedA11yServices.clear();
+ mNotifiedA11yServices.addAll(readNotifiedServiceList(userId));
+ }
+
+ protected void onServicesDisabled(int userId,
+ ArraySet<ComponentName> disabledServices) {
+ if (mNotifiedA11yServices.removeAll(disabledServices)) {
+ writeNotifiedServiceList(userId, mNotifiedA11yServices);
+ }
+ }
+
+ private void trySendNotification(int userId, ComponentName componentName) {
+ if (!AccessibilitySecurityPolicy.POLICY_WARNING_ENABLED) {
+ return;
+ }
+ if (userId != mCurrentUserId) {
+ return;
+ }
+
+ List<AccessibilityServiceInfo> enabledServiceInfos = getEnabledServiceInfos();
+ for (int i = 0; i < enabledServiceInfos.size(); i++) {
+ final AccessibilityServiceInfo a11yServiceInfo = enabledServiceInfos.get(i);
+ if (componentName.flattenToShortString().equals(
+ a11yServiceInfo.getComponentName().flattenToShortString())) {
+ if (!mAccessibilitySecurityPolicy.isA11yCategoryService(a11yServiceInfo)
+ && !mNotifiedA11yServices.contains(componentName)) {
+ final CharSequence displayName =
+ a11yServiceInfo.getResolveInfo().serviceInfo.loadLabel(
+ mContext.getPackageManager());
+ final Drawable drawable = a11yServiceInfo.getResolveInfo().loadIcon(
+ mContext.getPackageManager());
+ final int size = mContext.getResources().getDimensionPixelSize(
+ android.R.dimen.app_icon_size);
+ sendNotification(userId, componentName.flattenToShortString(),
+ displayName,
+ ImageUtils.buildScaledBitmap(drawable, size, size));
+ }
+ break;
+ }
+ }
+ }
+
+ private void launchSettings(int userId, ComponentName componentName) {
+ if (userId != mCurrentUserId) {
+ return;
+ }
+ final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra(Intent.EXTRA_COMPONENT_NAME, componentName.flattenToShortString());
+ final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(
+ mContext.getDisplayId()).toBundle();
+ mContext.startActivityAsUser(intent, bundle, UserHandle.of(userId));
+ mContext.getSystemService(StatusBarManager.class).collapsePanels();
+ }
+
+ protected void onNotificationCanceled(int userId, ComponentName componentName) {
+ if (userId != mCurrentUserId) {
+ return;
+ }
+
+ if (mNotifiedA11yServices.add(componentName)) {
+ writeNotifiedServiceList(userId, mNotifiedA11yServices);
+ }
+ }
+
+ private void sendNotification(int userId, String serviceComponentName, CharSequence name,
+ Bitmap bitmap) {
+ final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+ SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY);
+ notificationBuilder.setSmallIcon(R.drawable.ic_accessibility_24dp)
+ .setContentTitle(
+ mContext.getString(R.string.view_and_control_notification_title))
+ .setContentText(
+ mContext.getString(R.string.view_and_control_notification_content,
+ name))
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(
+ mContext.getString(
+ R.string.view_and_control_notification_content,
+ name)))
+ .setTicker(mContext.getString(R.string.view_and_control_notification_title))
+ .setOnlyAlertOnce(true)
+ .setDeleteIntent(
+ createPendingIntent(mContext, userId, ACTION_DISMISS_NOTIFICATION,
+ serviceComponentName))
+ .setContentIntent(
+ createPendingIntent(mContext, userId, ACTION_A11Y_SETTINGS,
+ serviceComponentName));
+ if (bitmap != null) {
+ notificationBuilder.setLargeIcon(bitmap);
+ }
+ mNotificationManager.notify(serviceComponentName, NOTE_A11Y_VIEW_AND_CONTROL_ACCESS,
+ notificationBuilder.build());
+ }
+
+ private ArraySet<ComponentName> readNotifiedServiceList(int userId) {
+ final String notifiedServiceSetting = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
+ userId);
+ if (TextUtils.isEmpty(notifiedServiceSetting)) {
+ return new ArraySet<>();
+ }
+
+ final TextUtils.StringSplitter componentNameSplitter =
+ new TextUtils.SimpleStringSplitter(RECORD_SEPARATOR);
+ componentNameSplitter.setString(notifiedServiceSetting);
+
+ final ArraySet<ComponentName> notifiedServices = new ArraySet<>();
+ final Iterator<String> it = componentNameSplitter.iterator();
+ while (it.hasNext()) {
+ final String componentNameString = it.next();
+ final ComponentName notifiedService = ComponentName.unflattenFromString(
+ componentNameString);
+ if (notifiedService != null) {
+ notifiedServices.add(notifiedService);
+ }
+ }
+ return notifiedServices;
+ }
+
+ private void writeNotifiedServiceList(int userId, ArraySet<ComponentName> services) {
+ StringBuilder notifiedServicesBuilder = new StringBuilder();
+ for (int i = 0; i < services.size(); i++) {
+ if (i > 0) {
+ notifiedServicesBuilder.append(RECORD_SEPARATOR);
+ }
+ final ComponentName notifiedService = services.valueAt(i);
+ notifiedServicesBuilder.append(notifiedService.flattenToShortString());
+ }
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
+ notifiedServicesBuilder.toString(), userId);
+ }
+
+ @VisibleForTesting
+ protected List<AccessibilityServiceInfo> getEnabledServiceInfos() {
+ final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(
+ mContext);
+ return accessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index c7f0efa..5b74cbd 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -114,10 +114,13 @@
private boolean mUnregisterPending;
private boolean mDeleteAfterUnregister;
+ private boolean mForceShowMagnifiableBounds;
+
private final int mDisplayId;
private static final int INVALID_ID = -1;
private int mIdOfLastServiceToMagnify = INVALID_ID;
+ private boolean mMagnificationActivated = false;
DisplayMagnification(int displayId) {
mDisplayId = displayId;
@@ -322,6 +325,13 @@
mSpecAnimationBridge, spec, animationCallback);
mControllerCtx.getHandler().sendMessage(m);
}
+
+ final boolean lastMagnificationActivated = mMagnificationActivated;
+ mMagnificationActivated = spec.scale > 1.0f;
+ if (mMagnificationActivated != lastMagnificationActivated) {
+ mMagnificationRequestObserver.onFullScreenMagnificationActivationState(
+ mMagnificationActivated);
+ }
}
/**
@@ -412,12 +422,18 @@
@GuardedBy("mLock")
void setForceShowMagnifiableBounds(boolean show) {
if (mRegistered) {
+ mForceShowMagnifiableBounds = show;
mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
mDisplayId, show);
}
}
@GuardedBy("mLock")
+ boolean isForceShowMagnifiableBounds() {
+ return mRegistered && mForceShowMagnifiableBounds;
+ }
+
+ @GuardedBy("mLock")
boolean reset(boolean animate) {
return reset(transformToStubCallback(animate));
}
@@ -434,6 +450,7 @@
onMagnificationChangedLocked();
}
mIdOfLastServiceToMagnify = INVALID_ID;
+ mForceShowMagnifiableBounds = false;
sendSpecToAnimation(spec, animationCallback);
return changed;
}
@@ -1150,6 +1167,21 @@
}
}
+ /**
+ * Returns {@code true} if the magnifiable regions of the display is forced to be shown.
+ *
+ * @param displayId The logical display id.
+ */
+ public boolean isForceShowMagnifiableBounds(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.isForceShowMagnifiableBounds();
+ }
+ }
+
private void onScreenTurnedOff() {
final Message m = PooledLambda.obtainMessage(
FullScreenMagnificationController::resetAllIfNeeded, this, false);
@@ -1506,5 +1538,14 @@
* @param serviceId the ID of the service requesting the change
*/
void onRequestMagnificationSpec(int displayId, int serviceId);
+
+ /**
+ * Called when the state of the magnification activation is changed.
+ * It is for the logging data of the magnification activation state.
+ *
+ * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
+ */
+ @GuardedBy("mLock")
+ void onFullScreenMagnificationActivationState(boolean activated);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 2a65b64..2073c70 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility.magnification;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
@@ -25,11 +26,13 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.SystemClock;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.view.accessibility.MagnificationAnimationCallback;
+import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilityManagerService;
@@ -49,9 +52,13 @@
* <li> 4. {@link #onTripleTapped} updates magnification switch UI depending on magnification
* capabilities and magnification active state when triple-tap gesture is detected. </li>
* </ol>
+ *
+ * <b>Note</b> Updates magnification switch UI when magnification mode transition
+ * is done {@link DisableMagnificationCallback#onResult}.
*/
public class MagnificationController implements WindowMagnificationManager.Callback,
- MagnificationGestureHandler.Callback {
+ MagnificationGestureHandler.Callback,
+ FullScreenMagnificationController.MagnificationRequestObserver {
private static final boolean DEBUG = false;
private static final String TAG = "MagnificationController";
@@ -66,6 +73,9 @@
private WindowMagnificationManager mWindowMagnificationMgr;
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+ private long mWindowModeEnabledTime = 0;
+ private long mFullScreenModeEnabledTime = 0;
+
/**
* A callback to inform the magnification transition result.
*/
@@ -187,7 +197,8 @@
setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
}
- void onRequestMagnificationSpec(int displayId, int serviceId) {
+ @Override
+ public void onRequestMagnificationSpec(int displayId, int serviceId) {
synchronized (mLock) {
if (serviceId == AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID) {
return;
@@ -200,6 +211,39 @@
}
}
+ // TODO : supporting multi-display (b/182227245).
+ @Override
+ public void onWindowMagnificationActivationState(boolean activated) {
+ if (activated) {
+ mWindowModeEnabledTime = SystemClock.uptimeMillis();
+ } else {
+ logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ SystemClock.uptimeMillis() - mWindowModeEnabledTime);
+ }
+ }
+
+ @Override
+ public void onFullScreenMagnificationActivationState(boolean activated) {
+ if (activated) {
+ mFullScreenModeEnabledTime = SystemClock.uptimeMillis();
+ } else {
+ logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
+ SystemClock.uptimeMillis() - mFullScreenModeEnabledTime);
+ }
+ }
+
+ /**
+ * Wrapper method of logging the magnification activated mode and its duration of the usage
+ * when the magnification is disabled.
+ *
+ * @param mode The activated magnification mode.
+ * @param duration The duration in milliseconds during the magnification is activated.
+ */
+ @VisibleForTesting
+ public void logMagnificationUsageState(int mode, long duration) {
+ AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration);
+ }
+
/**
* Updates the active user ID of {@link FullScreenMagnificationController} and {@link
* WindowMagnificationManager}.
@@ -260,7 +304,7 @@
synchronized (mLock) {
if (mFullScreenMagnificationController == null) {
mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
- mAms, mLock, this::onRequestMagnificationSpec);
+ mAms, mLock, this);
mFullScreenMagnificationController.setUserId(mAms.getCurrentUserIdLocked());
}
}
@@ -317,7 +361,8 @@
boolean isActivated = false;
if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
&& mFullScreenMagnificationController != null) {
- isActivated = mFullScreenMagnificationController.isMagnifying(displayId);
+ isActivated = mFullScreenMagnificationController.isMagnifying(displayId)
+ || mFullScreenMagnificationController.isForceShowMagnifiableBounds(displayId);
} else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
&& mWindowMagnificationMgr != null) {
isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
@@ -340,7 +385,7 @@
mTransitionCallBack = transitionCallBack;
mDisplayId = displayId;
mTargetMode = targetMode;
- mCurrentMode = mTargetMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+ mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
mCurrentScale = scale;
mCurrentCenter.set(currentCenter);
}
@@ -359,6 +404,7 @@
adjustCurrentCenterIfNeededLocked();
applyMagnificationModeLocked(mTargetMode);
}
+ updateMagnificationButton(mDisplayId, mTargetMode);
mTransitionCallBack.onResult(success);
}
}
@@ -383,6 +429,7 @@
}
setExpiredAndRemoveFromListLocked();
applyMagnificationModeLocked(mCurrentMode);
+ updateMagnificationButton(mDisplayId, mCurrentMode);
mTransitionCallBack.onResult(true);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 40668d8..ded601e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -93,6 +93,13 @@
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
*/
void onPerformScaleAction(int displayId, float scale);
+
+ /**
+ * Called when the state of the magnification activation is changed.
+ *
+ * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
+ */
+ void onWindowMagnificationActivationState(boolean activated);
}
private final Callback mCallback;
@@ -264,6 +271,7 @@
*/
void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback animationCallback) {
+ final boolean enabled;
synchronized (mLock) {
if (mConnectionWrapper == null) {
return;
@@ -272,9 +280,13 @@
if (magnifier == null) {
magnifier = createWindowMagnifier(displayId);
}
- magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
+ enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
animationCallback);
}
+
+ if (enabled) {
+ mCallback.onWindowMagnificationActivationState(true);
+ }
}
/**
@@ -296,16 +308,21 @@
*/
void disableWindowMagnification(int displayId, boolean clear,
MagnificationAnimationCallback animationCallback) {
+ final boolean disabled;
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null || mConnectionWrapper == null) {
return;
}
- magnifier.disableWindowMagnificationInternal(animationCallback);
+ disabled = magnifier.disableWindowMagnificationInternal(animationCallback);
if (clear) {
mWindowMagnifiers.delete(displayId);
}
}
+
+ if (disabled) {
+ mCallback.onWindowMagnificationActivationState(false);
+ }
}
/**
@@ -560,26 +577,35 @@
}
@GuardedBy("mLock")
- void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
+ boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback animationCallback) {
if (mEnabled) {
- return;
+ return false;
}
final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
centerX, centerY, animationCallback)) {
mScale = normScale;
mEnabled = true;
+
+ return true;
}
+ return false;
}
@GuardedBy("mLock")
- void disableWindowMagnificationInternal(
+ boolean disableWindowMagnificationInternal(
@Nullable MagnificationAnimationCallback animationResultCallback) {
- if (mEnabled && mWindowMagnificationManager.disableWindowMagnificationInternal(
+ if (!mEnabled) {
+ return false;
+ }
+ if (mWindowMagnificationManager.disableWindowMagnificationInternal(
mDisplayId, animationResultCallback)) {
mEnabled = false;
+
+ return true;
}
+ return false;
}
@GuardedBy("mLock")
diff --git a/services/api/current.txt b/services/api/current.txt
index 7e8f7a2..a3e6715 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -88,6 +88,14 @@
}
+package com.android.server.am {
+
+ public interface ActivityManagerLocal {
+ method public boolean canStartForegroundService(int, int, @NonNull String);
+ }
+
+}
+
package com.android.server.role {
public interface RoleServicePlatformHelper {
diff --git a/services/api/non-updatable-current.txt b/services/api/non-updatable-current.txt
index 3c72d38..f01c182 100644
--- a/services/api/non-updatable-current.txt
+++ b/services/api/non-updatable-current.txt
@@ -35,6 +35,14 @@
}
+package com.android.server.am {
+
+ public interface ActivityManagerLocal {
+ method public boolean canStartForegroundService(int, int, @NonNull String);
+ }
+
+}
+
package com.android.server.role {
public interface RoleServicePlatformHelper {
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index b15d07b..9d4c9eb 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -16,10 +16,13 @@
package com.android.server.autofill.ui;
+import static android.view.inputmethod.InlineSuggestionInfo.TYPE_SUGGESTION;
+
import static com.android.server.autofill.Helper.sDebug;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.IntentSender;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.InlinePresentation;
@@ -49,6 +52,9 @@
InlineSuggestionInfo.TYPE_ACTION, () -> uiCallback.authenticate(requestId,
AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
mergedInlinePresentation(inlineFillUiInfo.mInlineRequest, 0, inlineAuthentication),
+ createInlineSuggestionTooltip(inlineFillUiInfo.mInlineRequest,
+ inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL,
+ response.getInlineTooltipPresentation()),
uiCallback);
}
@@ -66,6 +72,8 @@
final InlineSuggestionsRequest request = inlineFillUiInfo.mInlineRequest;
SparseArray<Pair<Dataset, InlineSuggestion>> response = new SparseArray<>(datasets.size());
+
+ boolean hasTooltip = false;
for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) {
final Dataset dataset = datasets.get(datasetIndex);
final int fieldIndex = dataset.getFieldIds().indexOf(inlineFillUiInfo.mFocusedId);
@@ -82,14 +90,25 @@
}
final String suggestionType =
- dataset.getAuthentication() == null ? InlineSuggestionInfo.TYPE_SUGGESTION
+ dataset.getAuthentication() == null ? TYPE_SUGGESTION
: InlineSuggestionInfo.TYPE_ACTION;
final int index = datasetIndex;
+ InlineSuggestion inlineSuggestionTooltip = null;
+ if (!hasTooltip) {
+ // Only available for first one inline suggestion tooltip.
+ inlineSuggestionTooltip = createInlineSuggestionTooltip(request,
+ inlineFillUiInfo, suggestionSource,
+ dataset.getFieldInlineTooltipPresentation(fieldIndex));
+ if (inlineSuggestionTooltip != null) {
+ hasTooltip = true;
+ }
+ }
InlineSuggestion inlineSuggestion = createInlineSuggestion(
inlineFillUiInfo, suggestionSource, suggestionType,
() -> uiCallback.autofill(dataset, index),
mergedInlinePresentation(request, datasetIndex, inlinePresentation),
+ inlineSuggestionTooltip,
uiCallback);
response.append(datasetIndex, Pair.create(dataset, inlineSuggestion));
}
@@ -103,11 +122,13 @@
@NonNull @InlineSuggestionInfo.Type String suggestionType,
@NonNull Runnable onClickAction,
@NonNull InlinePresentation inlinePresentation,
+ @Nullable InlineSuggestion tooltip,
@NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
+
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
inlinePresentation.getInlinePresentationSpec(), suggestionSource,
inlinePresentation.getAutofillHints(), suggestionType,
- inlinePresentation.isPinned());
+ inlinePresentation.isPinned(), tooltip);
return new InlineSuggestion(inlineSuggestionInfo,
createInlineContentProvider(inlineFillUiInfo, inlinePresentation,
@@ -135,6 +156,60 @@
inlinePresentation.isPinned());
}
+ // TODO(182306770): creates new class instead of the InlineSuggestion.
+ private static InlineSuggestion createInlineSuggestionTooltip(
+ @NonNull InlineSuggestionsRequest request,
+ @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
+ String suggestionSource,
+ @NonNull InlinePresentation tooltipPresentation) {
+ if (tooltipPresentation == null) {
+ return null;
+ }
+
+ final InlinePresentationSpec spec = request.getInlineTooltipPresentationSpec();
+ InlinePresentationSpec mergedSpec;
+ if (spec == null) {
+ mergedSpec = tooltipPresentation.getInlinePresentationSpec();
+ } else {
+ mergedSpec = new InlinePresentationSpec.Builder(
+ tooltipPresentation.getInlinePresentationSpec().getMinSize(),
+ tooltipPresentation.getInlinePresentationSpec().getMaxSize()).setStyle(
+ spec.getStyle()).build();
+ }
+
+ InlineFillUi.InlineSuggestionUiCallback uiCallback =
+ new InlineFillUi.InlineSuggestionUiCallback() {
+ @Override
+ public void autofill(Dataset dataset, int datasetIndex) {
+ /* nothing */
+ }
+
+ @Override
+ public void authenticate(int requestId, int datasetIndex) {
+ /* nothing */
+ }
+
+ @Override
+ public void startIntentSender(IntentSender intentSender) {
+ /* nothing */
+ }
+
+ @Override
+ public void onError() {
+ Slog.w(TAG, "An error happened on the tooltip");
+ }
+ };
+
+ InlinePresentation tooltipInline = new InlinePresentation(tooltipPresentation.getSlice(),
+ mergedSpec, false);
+ IInlineContentProvider tooltipContentProvider = createInlineContentProvider(
+ inlineFillUiInfo, tooltipInline, () -> { /* no operation */ }, uiCallback);
+ final InlineSuggestionInfo tooltipInlineSuggestionInfo = new InlineSuggestionInfo(
+ mergedSpec, suggestionSource, /* autofillHints */ null, TYPE_SUGGESTION,
+ /* pinned */ false, /* tooltip */ null);
+ return new InlineSuggestion(tooltipInlineSuggestionInfo, tooltipContentProvider);
+ }
+
private static IInlineContentProvider createInlineContentProvider(
@NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
@NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 602dc24..d0a8881 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -16,6 +16,8 @@
package com.android.server.backup.restore;
+import static android.app.backup.BackupManager.OperationType;
+
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
@@ -24,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.backup.BackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
@@ -51,6 +54,7 @@
*/
public class ActiveRestoreSession extends IRestoreSession.Stub {
private static final String TAG = "RestoreSession";
+ private static final String DEVICE_NAME_FOR_D2D_SET = "D2D";
private final TransportManager mTransportManager;
private final String mTransportName;
@@ -174,6 +178,7 @@
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
final long oldId = Binder.clearCallingIdentity();
+ RestoreSet restoreSet = mRestoreSets[i];
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -183,7 +188,7 @@
monitor,
token,
listener,
- mBackupEligibilityRules),
+ getBackupEligibilityRules(restoreSet)),
"RestoreSession.restoreAll()");
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -266,6 +271,7 @@
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
final long oldId = Binder.clearCallingIdentity();
+ RestoreSet restoreSet = mRestoreSets[i];
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -277,7 +283,7 @@
packages,
/* isSystemRestore */ packages.length > 1,
listener,
- mBackupEligibilityRules),
+ getBackupEligibilityRules(restoreSet)),
"RestoreSession.restorePackages(" + packages.length + " packages)");
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -290,6 +296,14 @@
return -1;
}
+ private BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) {
+ // TODO(b/182986784): Remove device name comparison once a designated field for operation
+ // type is added to RestoreSet object.
+ int operationType = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
+ ? OperationType.MIGRATION : OperationType.BACKUP;
+ return mBackupManagerService.getEligibilityRulesForOperation(operationType);
+ }
+
public synchronized int restorePackage(String packageName, IRestoreObserver observer,
IBackupManagerMonitor monitor) {
if (DEBUG) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 4b56845..9ac93d9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -963,7 +963,8 @@
private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
return CollectionUtils.filter(
getAllAssociations(userId),
- a -> Objects.equals(packageFilter, a.getPackageName()));
+ // Null filter == get all associations
+ a -> packageFilter == null || Objects.equals(packageFilter, a.getPackageName()));
}
private Set<Association> getAllAssociations() {
@@ -983,8 +984,10 @@
int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
return CollectionUtils.filter(
getAllAssociations(userId),
- a -> Objects.equals(packageFilter, a.getPackageName())
- && Objects.equals(addressFilter, a.getDeviceMacAddress()));
+ // Null filter == get all associations
+ a -> (packageFilter == null || Objects.equals(packageFilter, a.getPackageName()))
+ && (addressFilter == null
+ || Objects.equals(addressFilter, a.getDeviceMacAddress())));
}
private Set<Association> readAllAssociations(int userId) {
@@ -1075,6 +1078,7 @@
Date lastSeen = mDevicesLastNearby.get(address);
if (isDeviceDisappeared(lastSeen)) {
onDeviceDisappeared(address);
+ unscheduleTriggerDeviceDisappearedRunnable(address);
}
}
@@ -1210,7 +1214,18 @@
@Override
public void run() {
Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")");
- onDeviceDisappeared(mAddress);
+ if (!mCurrentlyConnectedDevices.contains(mAddress)) {
+ onDeviceDisappeared(mAddress);
+ }
+ }
+ }
+
+ private void unscheduleTriggerDeviceDisappearedRunnable(String address) {
+ Runnable r = mTriggerDeviceDisappearedRunnables.get(address);
+ if (r != null) {
+ Slog.d(LOG_TAG,
+ "unscheduling TriggerDeviceDisappearedRunnable(address = " + address + ")");
+ mMainHandler.removeCallbacks(r);
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index f4a8ccd..25ea12b 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -58,6 +58,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -69,7 +70,6 @@
import android.service.contentcapture.ActivityEvent.ActivityEventType;
import android.service.contentcapture.IDataShareCallback;
import android.service.contentcapture.IDataShareReadAdapter;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Pair;
@@ -174,6 +174,9 @@
@GuardedBy("mLock")
private final Set<String> mPackagesWithShareRequests = new HashSet<>();
+ private final RemoteCallbackList<IContentCaptureOptionsCallback> mCallbacks =
+ new RemoteCallbackList<>();
+
final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
new GlobalContentCaptureOptions();
@@ -497,16 +500,15 @@
}
void updateOptions(String packageName, ContentCaptureOptions options) {
- ArraySet<CallbackRecord> records;
- synchronized (mLock) {
- records = mContentCaptureManagerServiceStub.mCallbacks.get(packageName);
- if (records != null) {
- int N = records.size();
- for (int i = 0; i < N; i++) {
- records.valueAt(i).setContentCaptureOptions(options);
+ mCallbacks.broadcast((callback, pkg) -> {
+ if (pkg.equals(packageName)) {
+ try {
+ callback.setContentCaptureOptions(options);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to send setContentCaptureOptions(): " + e);
}
}
- }
+ });
}
private ActivityManagerInternal getAmInternal() {
@@ -616,8 +618,6 @@
}
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
- @GuardedBy("mLock")
- private final ArrayMap<String, ArraySet<CallbackRecord>> mCallbacks = new ArrayMap<>();
@Override
public void startSession(@NonNull IBinder activityToken,
@@ -778,39 +778,19 @@
IContentCaptureOptionsCallback callback) {
assertCalledByPackageOwner(packageName);
- CallbackRecord record = new CallbackRecord(callback, packageName);
- record.registerObserver();
-
- synchronized (mLock) {
- ArraySet<CallbackRecord> records = mCallbacks.get(packageName);
- if (records == null) {
- records = new ArraySet<>();
- }
- records.add(record);
- mCallbacks.put(packageName, records);
- }
+ mCallbacks.register(callback, packageName);
// Set options here in case it was updated before this was registered.
final int userId = UserHandle.getCallingUserId();
final ContentCaptureOptions options = mGlobalContentCaptureOptions.getOptions(userId,
packageName);
if (options != null) {
- record.setContentCaptureOptions(options);
- }
- }
-
- private void unregisterContentCaptureOptionsCallback(CallbackRecord record) {
- synchronized (mLock) {
- ArraySet<CallbackRecord> records = mCallbacks.get(record.mPackageName);
- if (records != null) {
- records.remove(record);
- }
-
- if (records == null || records.isEmpty()) {
- mCallbacks.remove(record.mPackageName);
+ try {
+ callback.setContentCaptureOptions(options);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to send setContentCaptureOptions(): " + e);
}
}
- record.unregisterObserver();
}
@Override
@@ -1277,39 +1257,4 @@
mDataShareRequest.getPackageName());
}
}
-
- private final class CallbackRecord implements IBinder.DeathRecipient {
- private final String mPackageName;
- private final IContentCaptureOptionsCallback mCallback;
-
- private CallbackRecord(IContentCaptureOptionsCallback callback, String packageName) {
- mCallback = callback;
- mPackageName = packageName;
- }
-
- private void setContentCaptureOptions(ContentCaptureOptions options) {
- try {
- mCallback.setContentCaptureOptions(options);
- } catch (RemoteException e) {
- Slog.w(TAG, "Unable to send setContentCaptureOptions(): " + e);
- }
- }
-
- private void registerObserver() {
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to register callback cleanup " + e);
- }
- }
-
- private void unregisterObserver() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- mContentCaptureManagerServiceStub.unregisterContentCaptureOptionsCallback(this);
- }
- }
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b00689b..ed2e625 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -81,10 +81,19 @@
out: ["services.core.protolog.json"],
}
+genrule {
+ name: "statslog-art-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module art" +
+ " --javaPackage com.android.internal.art --javaClass ArtStatsLog --worksource",
+ out: ["com/android/internal/art/ArtStatsLog.java"],
+}
+
java_library_static {
name: "services.core.unboosted",
defaults: ["platform_service_defaults"],
srcs: [
+ ":statslog-art-java-gen",
":services.core-sources",
":services.core.protologsrc",
":dumpstate_aidl",
@@ -216,7 +225,6 @@
"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/ConnectivityResources.java",
"java/com/android/server/connectivity/DnsManager.java",
"java/com/android/server/connectivity/KeepaliveTracker.java",
"java/com/android/server/connectivity/LingerMonitor.java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index c295778..b089014 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1141,4 +1141,12 @@
*/
public abstract boolean isPackageFrozen(
@NonNull String packageName, int callingUid, int userId);
+
+ /**
+ * Returns true if the given {@code packageName} has declared the
+ * {@code neverForLocation} flag in the {@code uses-permission} manifest tag
+ * where they request the given {@code permissionName}.
+ */
+ public abstract boolean isPackageUsesPermissionNeverForLocation(@NonNull String packageName,
+ @NonNull String permissionName);
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index fbfde43..1e608f5 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1595,6 +1595,8 @@
if (Objects.equals(newService, oldService)) return;
Slog.i(TAG, "health: new instance registered " + mInstanceName);
+ // #init() may be called with null callback. Skip null callbacks.
+ if (mCallback == null) return;
mCallback.onRegistration(oldService, newService, mInstanceName);
} catch (NoSuchElementException | RemoteException ex) {
Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
diff --git a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java
index 2dcf82f..611a37d 100644
--- a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java
+++ b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java
@@ -17,6 +17,9 @@
package com.android.server;
import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import java.util.ArrayList;
/**
* The BluetoothDeviceConfigListener handles system device config change callback and checks
@@ -30,10 +33,12 @@
class BluetoothDeviceConfigListener {
private static final String TAG = "BluetoothDeviceConfigListener";
- BluetoothManagerService mService;
+ private final BluetoothManagerService mService;
+ private final boolean mLogDebug;
- BluetoothDeviceConfigListener(BluetoothManagerService service) {
+ BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) {
mService = service;
+ mLogDebug = logDebug;
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_BLUETOOTH,
(Runnable r) -> r.run(),
@@ -47,6 +52,13 @@
if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) {
return;
}
+ if (mLogDebug) {
+ ArrayList<String> flags = new ArrayList<>();
+ for (String name : properties.getKeyset()) {
+ flags.add(name + "='" + properties.getString(name, "") + "'");
+ }
+ Slog.d(TAG, "onPropertiesChanged: " + String.join(",", flags));
+ }
boolean foundInit = false;
for (String name : properties.getKeyset()) {
if (name.startsWith("INIT_")) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index dc24ffd..f0c9ba9 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -454,6 +454,7 @@
if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)
&& state == BluetoothProfile.STATE_DISCONNECTED
&& !mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) {
+ Slog.i(TAG, "Device disconnected, reactivating pending flag changes");
onInitFlagsChanged();
}
}
@@ -820,6 +821,35 @@
return enabledProfiles;
}
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED,
+ 0) != 0;
+ }
+
+ // Monitor change of BLE scan only mode settings.
+ private void registerForProvisioningStateChange() {
+ ContentObserver contentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (!isDeviceProvisioned()) {
+ if (DBG) {
+ Slog.d(TAG, "DEVICE_PROVISIONED setting changed, but device is not "
+ + "provisioned");
+ }
+ return;
+ }
+ if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)) {
+ Slog.i(TAG, "Device provisioned, reactivating pending flag changes");
+ onInitFlagsChanged();
+ }
+ }
+ };
+
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false,
+ contentObserver);
+ }
+
// Monitor change of BLE scan only mode settings.
private void registerForBleScanModeChange() {
ContentObserver contentObserver = new ContentObserver(null) {
@@ -1385,7 +1415,8 @@
if (mBluetoothAirplaneModeListener != null) {
mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper);
}
- mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this);
+ registerForProvisioningStateChange();
+ mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG);
}
/**
@@ -2229,12 +2260,25 @@
}
mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
if (mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) {
+ Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by "
+ + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS
+ + " ms due to existing connections");
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_INIT_FLAGS_CHANGED,
+ DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS);
+ break;
+ }
+ if (!isDeviceProvisioned()) {
+ Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by "
+ + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS
+ + "ms because device is not provisioned");
mHandler.sendEmptyMessageDelayed(
MESSAGE_INIT_FLAGS_CHANGED,
DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS);
break;
}
if (mBluetooth != null && isEnabled()) {
+ Slog.i(TAG, "Restarting Bluetooth due to init flag change");
restartForReason(
BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b4fcaee..512cc72 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -17,6 +17,10 @@
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;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
@@ -28,15 +32,30 @@
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_CBS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY;
+import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_IMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
+import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -53,8 +72,9 @@
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.RULE_NONE;
-import static android.net.NetworkPolicyManager.uidRulesToString;
+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;
import static android.os.Process.INVALID_UID;
import static android.os.Process.VPN_UID;
@@ -86,6 +106,8 @@
import android.net.ConnectivityDiagnosticsManager.DataStallReport;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager.RestrictBackgroundStatus;
+import android.net.ConnectivitySettingsManager;
import android.net.DataStallReportParcelable;
import android.net.DnsResolverServiceManager;
import android.net.ICaptivePortal;
@@ -96,8 +118,7 @@
import android.net.INetworkActivityListener;
import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
-import android.net.INetworkPolicyListener;
-import android.net.IOnSetOemNetworkPreferenceListener;
+import android.net.IOnCompleteListener;
import android.net.IQosCallback;
import android.net.ISocketKeepaliveCallback;
import android.net.InetAddresses;
@@ -110,13 +131,14 @@
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMonitorManager;
import android.net.NetworkPolicyManager;
+import android.net.NetworkPolicyManager.NetworkPolicyCallback;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
+import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkStackClient;
@@ -143,6 +165,7 @@
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnTransportInfo;
+import android.net.ConnectivityResources;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
@@ -191,17 +214,16 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.LocationPermissionChecker;
import com.android.internal.util.MessageUtils;
import com.android.modules.utils.BasicShellCommandHandler;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
+import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkCapabilitiesUtils;
import com.android.net.module.util.PermissionUtils;
import com.android.server.connectivity.AutodestructReference;
-import com.android.server.connectivity.ConnectivityResources;
import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.KeepaliveTracker;
@@ -213,6 +235,7 @@
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.NetworkRanker;
import com.android.server.connectivity.PermissionMonitor;
+import com.android.server.connectivity.ProfileNetworkPreferences;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -263,7 +286,7 @@
/**
* Default URL to use for {@link #getCaptivePortalServerUrl()}. This should not be changed
* by OEMs for configuration purposes, as this value is overridden by
- * Settings.Global.CAPTIVE_PORTAL_HTTP_URL.
+ * ConnectivitySettingsManager.CAPTIVE_PORTAL_HTTP_URL.
* R.string.config_networkCaptivePortalServerUrl should be overridden instead for this purpose
* (preferably via runtime resource overlays).
*/
@@ -296,7 +319,7 @@
protected int mNascentDelayMs;
// How long to delay to removal of a pending intent based request.
- // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
+ // See ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
private final int mReleasePendingIntentDelayMs;
private MockableSystemProperties mSystemProperties;
@@ -309,12 +332,10 @@
private volatile boolean mLockdownEnabled;
/**
- * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal
- * handler thread, they don't need a lock.
+ * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
+ * internal handler thread, they don't need a lock.
*/
- private SparseIntArray mUidRules = new SparseIntArray();
- /** Flag indicating if background data is restricted. */
- private boolean mRestrictBackground;
+ private SparseIntArray mUidBlockedReasons = new SparseIntArray();
private final Context mContext;
private final ConnectivityResources mResources;
@@ -488,16 +509,6 @@
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
- /**
- * Used to handle onUidRulesChanged event from NetworkPolicyManagerService.
- */
- private static final int EVENT_UID_RULES_CHANGED = 39;
-
- /**
- * Used to handle onRestrictBackgroundChanged event from NetworkPolicyManagerService.
- */
- private static final int EVENT_DATA_SAVER_CHANGED = 40;
-
/**
* Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
* been tested.
@@ -557,8 +568,8 @@
private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
/**
- * used internally when setting the default networks for OemNetworkPreferences.
- * obj = OemNetworkPreferences
+ * Used internally when setting the default networks for OemNetworkPreferences.
+ * obj = Pair<OemNetworkPreferences, listener>
*/
private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
@@ -568,6 +579,19 @@
private static final int EVENT_REPORT_NETWORK_ACTIVITY = 49;
/**
+ * Used internally when setting a network preference for a user profile.
+ * obj = Pair<ProfileNetworkPreference, Listener>
+ */
+ private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;
+
+ /**
+ * Event to specify that reasons for why an uid is blocked changed.
+ * arg1 = uid
+ * arg2 = blockedReasons
+ */
+ private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -616,11 +640,8 @@
private UserManager mUserManager;
- private NetworkConfig[] mNetConfigs;
- private int mNetworksDefined;
-
// the set of network types that can only be enabled by system/sig apps
- private List mProtectedNetworks;
+ private List<Integer> mProtectedNetworks;
private Set<String> mWolSupportedInterfaces;
@@ -710,18 +731,63 @@
* They are therefore not thread-safe with respect to each other.
* - getNetworkForType() can be called at any time on binder threads. It is synchronized
* on mTypeLists to be thread-safe with respect to a concurrent remove call.
+ * - getRestoreTimerForType(type) is also synchronized on mTypeLists.
* - dump is thread-safe with respect to concurrent add and remove calls.
*/
private final ArrayList<NetworkAgentInfo> mTypeLists[];
@NonNull
private final ConnectivityService mService;
+ // Restore timers for requestNetworkForFeature (network type -> timer in ms). Types without
+ // an entry have no timer (equivalent to -1). Lazily loaded.
+ @NonNull
+ private ArrayMap<Integer, Integer> mRestoreTimers = new ArrayMap<>();
+
LegacyTypeTracker(@NonNull ConnectivityService service) {
mService = service;
mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
}
- public void addSupportedType(int type) {
+ public void loadSupportedTypes(@NonNull Context ctx, @NonNull TelephonyManager tm) {
+ final PackageManager pm = ctx.getPackageManager();
+ if (pm.hasSystemFeature(FEATURE_WIFI)) {
+ addSupportedType(TYPE_WIFI);
+ }
+ if (pm.hasSystemFeature(FEATURE_WIFI_DIRECT)) {
+ addSupportedType(TYPE_WIFI_P2P);
+ }
+ if (tm.isDataCapable()) {
+ // Telephony does not have granular support for these types: they are either all
+ // supported, or none is supported
+ addSupportedType(TYPE_MOBILE);
+ addSupportedType(TYPE_MOBILE_MMS);
+ addSupportedType(TYPE_MOBILE_SUPL);
+ addSupportedType(TYPE_MOBILE_DUN);
+ addSupportedType(TYPE_MOBILE_HIPRI);
+ addSupportedType(TYPE_MOBILE_FOTA);
+ addSupportedType(TYPE_MOBILE_IMS);
+ addSupportedType(TYPE_MOBILE_CBS);
+ addSupportedType(TYPE_MOBILE_IA);
+ addSupportedType(TYPE_MOBILE_EMERGENCY);
+ }
+ if (pm.hasSystemFeature(FEATURE_BLUETOOTH)) {
+ addSupportedType(TYPE_BLUETOOTH);
+ }
+ if (pm.hasSystemFeature(FEATURE_WATCH)) {
+ // TYPE_PROXY is only used on Wear
+ addSupportedType(TYPE_PROXY);
+ }
+ // Ethernet is often not specified in the configs, although many devices can use it via
+ // USB host adapters. Add it as long as the ethernet service is here.
+ if (ctx.getSystemService(Context.ETHERNET_SERVICE) != null) {
+ addSupportedType(TYPE_ETHERNET);
+ }
+
+ // Always add TYPE_VPN as a supported type
+ addSupportedType(TYPE_VPN);
+ }
+
+ private void addSupportedType(int type) {
if (mTypeLists[type] != null) {
throw new IllegalStateException(
"legacy list for type " + type + "already initialized");
@@ -742,6 +808,35 @@
return null;
}
+ public int getRestoreTimerForType(int type) {
+ synchronized (mTypeLists) {
+ if (mRestoreTimers == null) {
+ mRestoreTimers = loadRestoreTimers();
+ }
+ return mRestoreTimers.getOrDefault(type, -1);
+ }
+ }
+
+ private ArrayMap<Integer, Integer> loadRestoreTimers() {
+ final String[] configs = mService.mResources.get().getStringArray(
+ com.android.connectivity.resources.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, ",");
+ if (splits.length != 2) {
+ logwtf("Invalid restore timer token count: " + config);
+ continue;
+ }
+ try {
+ ret.put(Integer.parseInt(splits[0]), Integer.parseInt(splits[1]));
+ } catch (NumberFormatException e) {
+ logwtf("Invalid restore timer number format: " + config, e);
+ }
+ }
+ return ret;
+ }
+
private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
boolean isDefaultNetwork) {
if (DBG) {
@@ -1135,7 +1230,7 @@
new ConnectivityDiagnosticsHandler(mHandlerThread.getLooper());
mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
+ ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
// TODO: Consider making the timer customizable.
@@ -1154,74 +1249,22 @@
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mLocationPermissionChecker = new LocationPermissionChecker(mContext);
- // To ensure uid rules are synchronized with Network Policy, register for
+ // To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
// reading existing policy from disk.
- mPolicyManager.registerListener(mPolicyListener);
+ mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
final PowerManager powerManager = (PowerManager) context.getSystemService(
Context.POWER_SERVICE);
mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
-
- // TODO: What is the "correct" way to do determine if this is a wifi only device?
- boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false);
- log("wifiOnly=" + wifiOnly);
- String[] naStrings = context.getResources().getStringArray(
- com.android.internal.R.array.networkAttributes);
- for (String naString : naStrings) {
- try {
- NetworkConfig n = new NetworkConfig(naString);
- if (VDBG) log("naString=" + naString + " config=" + n);
- if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
- loge("Error in networkAttributes - ignoring attempt to define type " +
- n.type);
- continue;
- }
- if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
- log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
- n.type);
- continue;
- }
- if (mNetConfigs[n.type] != null) {
- loge("Error in networkAttributes - ignoring attempt to redefine type " +
- n.type);
- continue;
- }
- mLegacyTypeTracker.addSupportedType(n.type);
-
- mNetConfigs[n.type] = n;
- mNetworksDefined++;
- } catch(Exception e) {
- // ignore it - leave the entry null
- }
- }
-
- // Forcibly add TYPE_VPN as a supported type, if it has not already been added via config.
- if (mNetConfigs[TYPE_VPN] == null) {
- // mNetConfigs is used only for "restore time", which isn't applicable to VPNs, so we
- // don't need to add TYPE_VPN to mNetConfigs.
- mLegacyTypeTracker.addSupportedType(TYPE_VPN);
- mNetworksDefined++; // used only in the log() statement below.
- }
-
- // Do the same for Ethernet, since it's often not specified in the configs, although many
- // devices can use it via USB host adapters.
- if (mNetConfigs[TYPE_ETHERNET] == null
- && mContext.getSystemService(Context.ETHERNET_SERVICE) != null) {
- mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
- mNetworksDefined++;
- }
-
- if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
-
- mProtectedNetworks = new ArrayList<Integer>();
+ mLegacyTypeTracker.loadSupportedTypes(mContext, mTelephonyManager);
+ mProtectedNetworks = new ArrayList<>();
int[] protectedNetworks = context.getResources().getIntArray(
com.android.internal.R.array.config_protectedNetworks);
for (int p : protectedNetworks) {
- if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
+ if (mLegacyTypeTracker.isTypeSupported(p) && !mProtectedNetworks.contains(p)) {
mProtectedNetworks.add(p);
} else {
if (DBG) loge("Ignoring protectedNetwork " + p);
@@ -1259,10 +1302,10 @@
mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
+ ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
LingerMonitor.DEFAULT_NOTIFICATION_DAILY_LIMIT);
final long rateLimit = Settings.Global.getLong(mContext.getContentResolver(),
- Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
+ ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
@@ -1273,20 +1316,31 @@
mDnsManager = new DnsManager(mContext, mDnsResolver);
registerPrivateDnsSettingsCallbacks();
+ // This NAI is a sentinel used to offer no service to apps that are on a multi-layer
+ // request that doesn't allow fallback to the default network. It should never be visible
+ // to apps. As such, it's not in the list of NAIs and doesn't need many of the normal
+ // arguments like the handler or the DnsResolver.
+ // TODO : remove this ; it is probably better handled with a sentinel request.
mNoServiceNetwork = new NetworkAgentInfo(null,
new Network(NO_SERVICE_NET_ID),
new NetworkInfo(TYPE_NONE, 0, "", ""),
- new LinkProperties(), new NetworkCapabilities(), 0, mContext,
- null, new NetworkAgentConfig(), this, null,
- null, 0, INVALID_UID, mQosCallbackTracker, mDeps);
+ new LinkProperties(), new NetworkCapabilities(),
+ new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
+ new NetworkAgentConfig(), this, null, null, 0, INVALID_UID, mQosCallbackTracker,
+ mDeps);
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
+ return createDefaultNetworkCapabilitiesForUidRange(new UidRange(uid, uid));
+ }
+
+ private static NetworkCapabilities createDefaultNetworkCapabilitiesForUidRange(
+ @NonNull final UidRange uids) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
- netCap.setSingleUid(uid);
+ netCap.setUids(Collections.singleton(uids));
return netCap;
}
@@ -1369,10 +1423,10 @@
}
private void handleConfigureAlwaysOnNetworks() {
- handleAlwaysOnNetworkRequest(
- mDefaultMobileDataRequest, Settings.Global.MOBILE_DATA_ALWAYS_ON, true);
- handleAlwaysOnNetworkRequest(mDefaultWifiRequest, Settings.Global.WIFI_ALWAYS_REQUESTED,
- false);
+ handleAlwaysOnNetworkRequest(mDefaultMobileDataRequest,
+ ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON, true /* defaultValue */);
+ handleAlwaysOnNetworkRequest(mDefaultWifiRequest,
+ ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED, false /* defaultValue */);
handleAlwaysOnNetworkRequest(mDefaultVehicleRequest,
com.android.internal.R.bool.config_vehicleInternalNetworkAlwaysRequested);
}
@@ -1385,12 +1439,12 @@
// Watch for whether or not to keep mobile data always on.
mSettingsObserver.observe(
- Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON),
+ Settings.Global.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON),
EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
// Watch for whether or not to keep wifi always on.
mSettingsObserver.observe(
- Settings.Global.getUriFor(Settings.Global.WIFI_ALWAYS_REQUESTED),
+ Settings.Global.getUriFor(ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED),
EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
}
@@ -1944,6 +1998,18 @@
}
}
+ @Override
+ public @RestrictBackgroundStatus int getRestrictBackgroundStatusByCaller() {
+ enforceAccessPermission();
+ final int callerUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mPolicyManager.getRestrictBackgroundStatus(callerUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
// TODO: Consider delete this function or turn it into a no-op method.
@Override
public NetworkState[] getAllNetworkState() {
@@ -2179,53 +2245,17 @@
}
}
- private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() {
+ private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {
@Override
- public void onUidRulesChanged(int uid, int uidRules) {
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_RULES_CHANGED, uid, uidRules));
- }
- @Override
- public void onRestrictBackgroundChanged(boolean restrictBackground) {
- // caller is NPMS, since we only register with them
- if (LOGD_BLOCKED_NETWORKINFO) {
- log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
- }
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_DATA_SAVER_CHANGED, restrictBackground ? 1 : 0, 0));
+ public void onUidBlockedReasonChanged(int uid, int blockedReasons) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED,
+ uid, blockedReasons));
}
};
- void handleUidRulesChanged(int uid, int newRules) {
- // skip update when we've already applied rules
- final int oldRules = mUidRules.get(uid, RULE_NONE);
- if (oldRules == newRules) return;
-
- maybeNotifyNetworkBlockedForNewUidRules(uid, newRules);
-
- if (newRules == RULE_NONE) {
- mUidRules.delete(uid);
- } else {
- mUidRules.put(uid, newRules);
- }
- }
-
- void handleRestrictBackgroundChanged(boolean restrictBackground) {
- if (mRestrictBackground == restrictBackground) return;
-
- final List<UidRange> blockedRanges = mVpnBlockedUidRanges;
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
- final boolean curMetered = nai.networkCapabilities.isMetered();
- maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground,
- restrictBackground, blockedRanges, blockedRanges);
- }
-
- mRestrictBackground = restrictBackground;
- }
-
- private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered,
- boolean isBackgroundRestricted) {
- return mPolicyManager.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered,
- isBackgroundRestricted);
+ void handleUidBlockedReasonChanged(int uid, int blockedReasons) {
+ maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
+ mUidBlockedReasons.put(uid, blockedReasons);
}
private boolean checkAnyPermissionOf(String... permissions) {
@@ -2596,13 +2626,6 @@
} catch (RemoteException | ServiceSpecificException e) {
loge("Can't set TCP buffer sizes:" + e);
}
-
- final Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.TCP_DEFAULT_INIT_RWND,
- mSystemProperties.getInt("net.tcp.default_init_rwnd", 0));
- if (rwndValue != 0) {
- mSystemProperties.setTcpInitRwnd(rwndValue);
- }
}
@Override
@@ -2619,9 +2642,8 @@
// if the system property isn't set, use the value for the apn type
int ret = RESTORE_DEFAULT_NETWORK_DELAY;
- if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) &&
- (mNetConfigs[networkType] != null)) {
- ret = mNetConfigs[networkType].restoreTime;
+ if (mLegacyTypeTracker.isTypeSupported(networkType)) {
+ ret = mLegacyTypeTracker.getRestoreTimerForType(networkType);
}
return ret;
}
@@ -2707,19 +2729,16 @@
pw.decreaseIndent();
pw.println();
- pw.print("Restrict background: ");
- pw.println(mRestrictBackground);
- pw.println();
-
pw.println("Status for known UIDs:");
pw.increaseIndent();
- final int size = mUidRules.size();
+ final int size = mUidBlockedReasons.size();
for (int i = 0; i < size; i++) {
// Don't crash if the array is modified while dumping in bugreports.
try {
- final int uid = mUidRules.keyAt(i);
- final int uidRules = mUidRules.get(uid, RULE_NONE);
- pw.println("UID=" + uid + " rules=" + uidRulesToString(uidRules));
+ final int uid = mUidBlockedReasons.keyAt(i);
+ final int blockedReasons = mUidBlockedReasons.valueAt(i);
+ pw.println("UID=" + uid + " blockedReasons="
+ + blockedReasonsToString(blockedReasons));
} catch (ArrayIndexOutOfBoundsException e) {
pw.println(" ArrayIndexOutOfBoundsException");
} catch (ConcurrentModificationException e) {
@@ -2949,7 +2968,7 @@
break;
}
case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
- updateNetworkScore(nai, msg.arg1);
+ updateNetworkScore(nai, (NetworkScore) arg.second);
break;
}
case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
@@ -3060,7 +3079,8 @@
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
if (nai.lastCaptivePortalDetected &&
- Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
+ ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
+ == getCaptivePortalMode()) {
if (DBG) log("Avoiding captive portal network: " + nai.toShortString());
nai.onPreventAutomaticReconnect();
teardownUnneededNetwork(nai);
@@ -3171,8 +3191,8 @@
private int getCaptivePortalMode() {
return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.CAPTIVE_PORTAL_MODE,
- Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+ ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE,
+ ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
}
private boolean maybeHandleNetworkAgentInfoMessage(Message msg) {
@@ -3656,6 +3676,7 @@
mNetworkRequestInfoLogs.log("REGISTER " + nri);
for (final NetworkRequest req : nri.mRequests) {
mNetworkRequests.put(req, nri);
+ // TODO: Consider update signal strength for other types.
if (req.isListen()) {
for (final NetworkAgentInfo network : mNetworkAgentInfos) {
if (req.networkCapabilities.hasSignalStrength()
@@ -3748,18 +3769,19 @@
// listen requests won't keep up a network satisfying it. If this is not a multilayer
// request, return immediately. For multilayer requests, check to see if any of the
// multilayer requests may have a potential satisfier.
- if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) {
+ if (!nri.isMultilayerRequest() && (nri.mRequests.get(0).isListen()
+ || nri.mRequests.get(0).isListenForBest())) {
return false;
}
for (final NetworkRequest req : nri.mRequests) {
// This multilayer listen request is satisfied therefore no further requests need to be
// evaluated deeming this network not a potential satisfier.
- if (req.isListen() && nri.getActiveRequest() == req) {
+ if ((req.isListen() || req.isListenForBest()) && nri.getActiveRequest() == req) {
return false;
}
// As non-multilayer listen requests have already returned, the below would only happen
// for a multilayer request therefore continue to the next request if available.
- if (req.isListen()) {
+ if (req.isListen() || req.isListenForBest()) {
continue;
}
// If this Network is already the highest scoring Network for a request, or if
@@ -3799,8 +3821,7 @@
? mNetworkRequests.get(request) : getNriForAppRequest(request);
if (nri != null) {
- if (Process.SYSTEM_UID != callingUid && Process.NETWORK_STACK_UID != callingUid
- && nri.mUid != callingUid) {
+ if (Process.SYSTEM_UID != callingUid && nri.mUid != callingUid) {
log(String.format("UID %d attempted to %s for unowned request %s",
callingUid, requestedOperation, nri));
return null;
@@ -4395,7 +4416,13 @@
final NetworkPolicyManager netPolicyManager =
mContext.getSystemService(NetworkPolicyManager.class);
- final int networkPreference = netPolicyManager.getMultipathPreference(network);
+ final long token = Binder.clearCallingIdentity();
+ final int networkPreference;
+ try {
+ networkPreference = netPolicyManager.getMultipathPreference(network);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
if (networkPreference != 0) {
return networkPreference;
}
@@ -4425,7 +4452,8 @@
break;
}
case EVENT_PROXY_HAS_CHANGED: {
- handleApplyDefaultProxy((ProxyInfo)msg.obj);
+ final Pair<Network, ProxyInfo> arg = (Pair<Network, ProxyInfo>) msg.obj;
+ handleApplyDefaultProxy(arg.second);
break;
}
case EVENT_REGISTER_NETWORK_PROVIDER: {
@@ -4513,22 +4541,24 @@
handlePrivateDnsValidationUpdate(
(PrivateDnsValidationUpdate) msg.obj);
break;
- case EVENT_UID_RULES_CHANGED:
- handleUidRulesChanged(msg.arg1, msg.arg2);
- break;
- case EVENT_DATA_SAVER_CHANGED:
- handleRestrictBackgroundChanged(toBool(msg.arg1));
+ case EVENT_UID_BLOCKED_REASON_CHANGED:
+ handleUidBlockedReasonChanged(msg.arg1, msg.arg2);
break;
case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
break;
case EVENT_SET_OEM_NETWORK_PREFERENCE: {
- final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
- (Pair<OemNetworkPreferences,
- IOnSetOemNetworkPreferenceListener>) msg.obj;
+ final Pair<OemNetworkPreferences, IOnCompleteListener> arg =
+ (Pair<OemNetworkPreferences, IOnCompleteListener>) msg.obj;
handleSetOemNetworkPreference(arg.first, arg.second);
break;
}
+ case EVENT_SET_PROFILE_NETWORK_PREFERENCE: {
+ final Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener> arg =
+ (Pair<ProfileNetworkPreferences.Preference, IOnCompleteListener>)
+ msg.obj;
+ handleSetProfileNetworkPreference(arg.first, arg.second);
+ }
case EVENT_REPORT_NETWORK_ACTIVITY:
mNetworkActivityTracker.handleReportNetworkActivity();
break;
@@ -4827,6 +4857,10 @@
Log.wtf(TAG, s);
}
+ private static void logwtf(String s, Throwable t) {
+ Log.wtf(TAG, s, t);
+ }
+
private static void loge(String s) {
Log.e(TAG, s);
}
@@ -4981,8 +5015,8 @@
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
final boolean curMetered = nai.networkCapabilities.isMetered();
- maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground,
- mRestrictBackground, mVpnBlockedUidRanges, newVpnBlockedUidRanges);
+ maybeNotifyNetworkBlocked(nai, curMetered, curMetered,
+ mVpnBlockedUidRanges, newVpnBlockedUidRanges);
}
mVpnBlockedUidRanges = newVpnBlockedUidRanges;
@@ -5099,6 +5133,9 @@
private void onUserRemoved(UserHandle user) {
mPermissionMonitor.onUserRemoved(user);
+ // If there was a network preference for this user, remove it.
+ handleSetProfileNetworkPreference(new ProfileNetworkPreferences.Preference(user, null),
+ null /* listener */);
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
@@ -5542,8 +5579,10 @@
// request if the app changes network state. http://b/29964605
enforceMeteredApnPolicy(networkCapabilities);
break;
- case TRACK_BEST:
- throw new UnsupportedOperationException("Not implemented yet");
+ case LISTEN_FOR_BEST:
+ enforceAccessPermission();
+ networkCapabilities = new NetworkCapabilities(networkCapabilities);
+ break;
default:
throw new IllegalArgumentException("Unsupported request type " + reqType);
}
@@ -5551,11 +5590,17 @@
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
- // Set the UID range for this request to the single UID of the requester, or to an empty
- // set of UIDs if the caller has the appropriate permission and UIDs have not been set.
+ // Enforce FOREGROUND if the caller does not have permission to use background network.
+ if (reqType == LISTEN_FOR_BEST) {
+ restrictBackgroundRequestForCaller(networkCapabilities);
+ }
+
+ // Set the UID range for this request to the single UID of the requester, unless the
+ // requester has the permission to specify other UIDs.
// This will overwrite any allowed UIDs in the requested capabilities. Though there
// are no visible methods to set the UIDs, an app could use reflection to try and get
// networks for other apps so it's essential that the UIDs are overwritten.
+ // Also set the requester UID and package name in the request.
restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
callingUid, callingPackageName);
@@ -5889,10 +5934,16 @@
@GuardedBy("mBlockedAppUids")
private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
- // Current OEM network preferences.
+ // Current OEM network preferences. This object must only be written to on the handler thread.
+ // Since it is immutable and always non-null, other threads may read it if they only care
+ // about seeing a consistent object but not that it is current.
@NonNull
private OemNetworkPreferences mOemNetworkPreferences =
new OemNetworkPreferences.Builder().build();
+ // Current per-profile network preferences. This object follows the same threading rules as
+ // the OEM network preferences above.
+ @NonNull
+ private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();
// The always-on request for an Internet-capable network that apps without a specific default
// fall back to.
@@ -6082,20 +6133,6 @@
return nai == getDefaultNetwork();
}
- // TODO : remove this method. It's a stopgap measure to help sheperding a number of dependent
- // changes that would conflict throughout the automerger graph. Having this method temporarily
- // helps with the process of going through with all these dependent changes across the entire
- // tree.
- /**
- * Register a new agent. {@see #registerNetworkAgent} below.
- */
- public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo,
- LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkAgentConfig networkAgentConfig) {
- return registerNetworkAgent(na, networkInfo, linkProperties, networkCapabilities,
- currentScore, networkAgentConfig, NetworkProvider.ID_NONE);
- }
-
/**
* Register a new agent with ConnectivityService to handle a network.
*
@@ -6106,7 +6143,7 @@
* later : see {@link #updateLinkProperties}.
* @param networkCapabilities the initial capabilites of this network. They can be updated
* later : see {@link #updateCapabilities}.
- * @param currentScore the initial score of the network. See
+ * @param initialScore the initial score of the network. See
* {@link NetworkAgentInfo#getCurrentScore}.
* @param networkAgentConfig metadata about the network. This is never updated.
* @param providerId the ID of the provider owning this NetworkAgent.
@@ -6114,10 +6151,12 @@
*/
public Network registerNetworkAgent(INetworkAgent na, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) {
+ @NonNull NetworkScore initialScore, NetworkAgentConfig networkAgentConfig,
+ int providerId) {
Objects.requireNonNull(networkInfo, "networkInfo must not be null");
Objects.requireNonNull(linkProperties, "linkProperties must not be null");
Objects.requireNonNull(networkCapabilities, "networkCapabilities must not be null");
+ Objects.requireNonNull(initialScore, "initialScore must not be null");
Objects.requireNonNull(networkAgentConfig, "networkAgentConfig must not be null");
if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
enforceAnyPermissionOf(Manifest.permission.MANAGE_TEST_NETWORKS);
@@ -6129,7 +6168,7 @@
final long token = Binder.clearCallingIdentity();
try {
return registerNetworkAgentInternal(na, networkInfo, linkProperties,
- networkCapabilities, currentScore, networkAgentConfig, providerId, uid);
+ networkCapabilities, initialScore, networkAgentConfig, providerId, uid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -6137,7 +6176,8 @@
private Network registerNetworkAgentInternal(INetworkAgent na, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkAgentConfig networkAgentConfig, int providerId, int uid) {
+ NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
+ int uid) {
if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
// Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in
// the call to mixInCapabilities below anyway, but sanitizing here means the NAI never
@@ -6759,8 +6799,8 @@
final boolean meteredChanged = oldMetered != newMetered;
if (meteredChanged) {
- maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, mRestrictBackground,
- mRestrictBackground, mVpnBlockedUidRanges, mVpnBlockedUidRanges);
+ maybeNotifyNetworkBlocked(nai, oldMetered, newMetered,
+ mVpnBlockedUidRanges, mVpnBlockedUidRanges);
}
final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING)
@@ -7861,7 +7901,7 @@
}
}
- private void updateNetworkScore(@NonNull final NetworkAgentInfo nai, final int score) {
+ private void updateNetworkScore(@NonNull final NetworkAgentInfo nai, final NetworkScore score) {
if (VDBG || DDBG) log("updateNetworkScore for " + nai.toShortString() + " to " + score);
nai.setScore(score);
rematchAllNetworksAndRequests();
@@ -7883,8 +7923,8 @@
final boolean metered = nai.networkCapabilities.isMetered();
boolean blocked;
blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges);
- blocked |= isUidBlockedByRules(nri.mUid, mUidRules.get(nri.mUid),
- metered, mRestrictBackground);
+ blocked |= NetworkPolicyManager.isUidBlocked(
+ mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE), metered);
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
}
@@ -7902,16 +7942,14 @@
*
* @param nai The target NetworkAgentInfo.
* @param oldMetered True if the previous network capabilities is metered.
- * @param newRestrictBackground True if data saver is enabled.
*/
private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered,
- boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground,
- List<UidRange> oldBlockedUidRanges, List<UidRange> newBlockedUidRanges) {
+ boolean newMetered, List<UidRange> oldBlockedUidRanges,
+ List<UidRange> newBlockedUidRanges) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
- final int uidRules = mUidRules.get(nri.mUid);
final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked;
oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges);
@@ -7919,10 +7957,11 @@
? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges)
: oldVpnBlocked;
- oldBlocked = oldVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, oldMetered,
- oldRestrictBackground);
- newBlocked = newVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, newMetered,
- newRestrictBackground);
+ final int blockedReasons = mUidBlockedReasons.get(nri.mUid, BLOCKED_REASON_NONE);
+ oldBlocked = oldVpnBlocked || NetworkPolicyManager.isUidBlocked(
+ blockedReasons, oldMetered);
+ newBlocked = newVpnBlocked || NetworkPolicyManager.isUidBlocked(
+ blockedReasons, newMetered);
if (oldBlocked != newBlocked) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
@@ -7932,19 +7971,20 @@
}
/**
- * Notify apps with a given UID of the new blocked state according to new uid rules.
+ * Notify apps with a given UID of the new blocked state according to new uid state.
* @param uid The uid for which the rules changed.
- * @param newRules The new rules to apply.
+ * @param blockedReasons The reasons for why an uid is blocked.
*/
- private void maybeNotifyNetworkBlockedForNewUidRules(int uid, int newRules) {
+ private void maybeNotifyNetworkBlockedForNewState(int uid, int blockedReasons) {
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
final boolean oldBlocked, newBlocked;
- oldBlocked = vpnBlocked || isUidBlockedByRules(
- uid, mUidRules.get(uid), metered, mRestrictBackground);
- newBlocked = vpnBlocked || isUidBlockedByRules(
- uid, newRules, metered, mRestrictBackground);
+
+ oldBlocked = vpnBlocked || NetworkPolicyManager.isUidBlocked(
+ mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered);
+ newBlocked = vpnBlocked || NetworkPolicyManager.isUidBlocked(
+ blockedReasons, metered);
if (oldBlocked == newBlocked) {
continue;
}
@@ -8092,7 +8132,7 @@
}
settingUrl = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+ ConnectivitySettingsManager.CAPTIVE_PORTAL_HTTP_URL);
if (!TextUtils.isEmpty(settingUrl)) {
return settingUrl;
}
@@ -8174,11 +8214,11 @@
// restore private DNS settings to default mode (opportunistic)
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) {
Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OPPORTUNISTIC);
+ ConnectivitySettingsManager.PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OPPORTUNISTIC);
}
Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
+ ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null);
}
@Override
@@ -8883,13 +8923,13 @@
private int transportTypeToLegacyType(int type) {
switch (type) {
case NetworkCapabilities.TRANSPORT_CELLULAR:
- return ConnectivityManager.TYPE_MOBILE;
+ return TYPE_MOBILE;
case NetworkCapabilities.TRANSPORT_WIFI:
- return ConnectivityManager.TYPE_WIFI;
+ return TYPE_WIFI;
case NetworkCapabilities.TRANSPORT_BLUETOOTH:
- return ConnectivityManager.TYPE_BLUETOOTH;
+ return TYPE_BLUETOOTH;
case NetworkCapabilities.TRANSPORT_ETHERNET:
- return ConnectivityManager.TYPE_ETHERNET;
+ return TYPE_ETHERNET;
default:
loge("Unexpected transport in transportTypeToLegacyType: " + type);
}
@@ -8930,13 +8970,13 @@
if (networkAgent.networkCapabilities.hasTransport(
NetworkCapabilities.TRANSPORT_CELLULAR)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+ ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_MOBILE,
10);
type = NetworkCapabilities.TRANSPORT_CELLULAR;
} else if (networkAgent.networkCapabilities.hasTransport(
NetworkCapabilities.TRANSPORT_WIFI)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+ ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_WIFI,
15);
type = NetworkCapabilities.TRANSPORT_WIFI;
} else {
@@ -9104,6 +9144,143 @@
mQosCallbackTracker.unregisterCallback(callback);
}
+ // Network preference per-profile and OEM network preferences can't be set at the same
+ // time, because it is unclear what should happen if both preferences are active for
+ // one given UID. To make it possible, the stack would have to clarify what would happen
+ // in case both are active at the same time. The implementation may have to be adjusted
+ // to implement the resulting rules. For example, a priority could be defined between them,
+ // where the OEM preference would be considered less or more important than the enterprise
+ // preference ; this would entail implementing the priorities somehow, e.g. by doing
+ // UID arithmetic with UID ranges or passing a priority to netd so that the routing rules
+ // are set at the right level. Other solutions are possible, e.g. merging of the
+ // preferences for the relevant UIDs.
+ private static void throwConcurrentPreferenceException() {
+ throw new IllegalStateException("Can't set NetworkPreferenceForUser and "
+ + "set OemNetworkPreference at the same time");
+ }
+
+ /**
+ * Request that a user profile is put by default on a network matching a given preference.
+ *
+ * See the documentation for the individual preferences for a description of the supported
+ * behaviors.
+ *
+ * @param profile the profile concerned.
+ * @param preference the preference for this profile, as one of the PROFILE_NETWORK_PREFERENCE_*
+ * constants.
+ * @param listener an optional listener to listen for completion of the operation.
+ */
+ @Override
+ public void setProfileNetworkPreference(@NonNull final UserHandle profile,
+ @ConnectivityManager.ProfileNetworkPreference final int preference,
+ @Nullable final IOnCompleteListener listener) {
+ Objects.requireNonNull(profile);
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (DBG) {
+ log("setProfileNetworkPreference " + profile + " to " + preference);
+ }
+ if (profile.getIdentifier() < 0) {
+ throw new IllegalArgumentException("Must explicitly specify a user handle ("
+ + "UserHandle.CURRENT not supported)");
+ }
+ final UserManager um;
+ try {
+ um = mContext.createContextAsUser(profile, 0 /* flags */)
+ .getSystemService(UserManager.class);
+ } catch (IllegalStateException e) {
+ throw new IllegalArgumentException("Profile does not exist");
+ }
+ if (!um.isManagedProfile()) {
+ throw new IllegalArgumentException("Profile must be a managed profile");
+ }
+ // Strictly speaking, mOemNetworkPreferences should only be touched on the
+ // handler thread. However it is an immutable object, so reading the reference is
+ // safe - it's just possible the value is slightly outdated. For the final check,
+ // see #handleSetProfileNetworkPreference. But if this can be caught here it is a
+ // lot easier to understand, so opportunistically check it.
+ if (!mOemNetworkPreferences.isEmpty()) {
+ throwConcurrentPreferenceException();
+ }
+ final NetworkCapabilities nc;
+ switch (preference) {
+ case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
+ nc = null;
+ break;
+ case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+ final UidRange uids = UidRange.createForUser(profile);
+ nc = createDefaultNetworkCapabilitiesForUidRange(uids);
+ nc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid preference in setProfileNetworkPreference");
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
+ new Pair<>(new ProfileNetworkPreferences.Preference(profile, nc), listener)));
+ }
+
+ private void validateNetworkCapabilitiesOfProfileNetworkPreference(
+ @Nullable final NetworkCapabilities nc) {
+ if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
+ ensureRequestableCapabilities(nc);
+ }
+
+ private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
+ @NonNull final ProfileNetworkPreferences prefs) {
+ final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
+ for (final ProfileNetworkPreferences.Preference pref : prefs.preferences) {
+ // The NRI for a user should be comprised of two layers:
+ // - The request for the capabilities
+ // - The request for the default network, for fallback. Create an image of it to
+ // have the correct UIDs in it (also a request can only be part of one NRI, because
+ // of lookups in 1:1 associations like mNetworkRequests).
+ // Note that denying a fallback can be implemented simply by not adding the second
+ // request.
+ final ArrayList<NetworkRequest> nrs = new ArrayList<>();
+ nrs.add(createNetworkRequest(NetworkRequest.Type.REQUEST, pref.capabilities));
+ nrs.add(createDefaultRequest());
+ setNetworkRequestUids(nrs, pref.capabilities.getUids());
+ final NetworkRequestInfo nri = new NetworkRequestInfo(nrs);
+ result.add(nri);
+ }
+ return result;
+ }
+
+ private void handleSetProfileNetworkPreference(
+ @NonNull final ProfileNetworkPreferences.Preference preference,
+ @Nullable final IOnCompleteListener listener) {
+ // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
+ // particular because it's not clear what preference should win in case both apply
+ // to the same app.
+ // The binder call has already checked this, but as mOemNetworkPreferences is only
+ // touched on the handler thread, it's theoretically not impossible that it has changed
+ // since.
+ if (!mOemNetworkPreferences.isEmpty()) {
+ // This may happen on a device with an OEM preference set when a user is removed.
+ // In this case, it's safe to ignore. In particular this happens in the tests.
+ loge("handleSetProfileNetworkPreference, but OEM network preferences not empty");
+ return;
+ }
+
+ validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
+
+ mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
+ final ArraySet<NetworkRequestInfo> nris =
+ createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences);
+ replaceDefaultNetworkRequestsForPreference(nris);
+ // Finally, rematch.
+ rematchAllNetworksAndRequests();
+
+ if (null != listener) {
+ try {
+ listener.onComplete();
+ } catch (RemoteException e) {
+ loge("Listener for setProfileNetworkPreference has died");
+ }
+ }
+ }
+
private void enforceAutomotiveDevice() {
final boolean isAutomotiveDevice =
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
@@ -9122,17 +9299,26 @@
* Calling this will overwrite the existing preference.
*
* @param preference {@link OemNetworkPreferences} The application network preference to be set.
- * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used
+ * @param listener {@link ConnectivityManager.OnCompleteListener} Listener used
* to communicate completion of setOemNetworkPreference();
*/
@Override
public void setOemNetworkPreference(
@NonNull final OemNetworkPreferences preference,
- @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+ @Nullable final IOnCompleteListener listener) {
enforceAutomotiveDevice();
enforceOemNetworkPreferencesPermission();
+ if (!mProfileNetworkPreferences.isEmpty()) {
+ // Strictly speaking, mProfileNetworkPreferences should only be touched on the
+ // handler thread. However it is an immutable object, so reading the reference is
+ // safe - it's just possible the value is slightly outdated. For the final check,
+ // see #handleSetOemPreference. But if this can be caught here it is a
+ // lot easier to understand, so opportunistically check it.
+ throwConcurrentPreferenceException();
+ }
+
Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
validateOemNetworkPreferences(preference);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
@@ -9151,11 +9337,22 @@
private void handleSetOemNetworkPreference(
@NonNull final OemNetworkPreferences preference,
- @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+ @Nullable final IOnCompleteListener listener) {
Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
if (DBG) {
log("set OEM network preferences :" + preference.toString());
}
+ // setProfileNetworkPreference and setOemNetworkPreference are mutually exclusive, in
+ // particular because it's not clear what preference should win in case both apply
+ // to the same app.
+ // The binder call has already checked this, but as mOemNetworkPreferences is only
+ // touched on the handler thread, it's theoretically not impossible that it has changed
+ // since.
+ if (!mProfileNetworkPreferences.isEmpty()) {
+ logwtf("handleSetOemPreference, but per-profile network preferences not empty");
+ return;
+ }
+
final ArraySet<NetworkRequestInfo> nris =
new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
replaceDefaultNetworkRequestsForPreference(nris);
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index a353519..96ff900 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.util.Log;
@@ -51,4 +53,9 @@
publishBinderService(Context.CONTEXTHUB_SERVICE, mContextHubService);
}
}
+
+ @Override
+ public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+ mContextHubService.onUserChanged();
+ }
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index bab6deb..af791cb 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -162,7 +162,7 @@
GESTURE_CAMERA_DOUBLE_TAP_POWER(660),
@UiEvent(doc = "The user multi-tapped power quickly enough to signal an emergency.")
- GESTURE_PANIC_TAP_POWER(661);
+ GESTURE_EMERGENCY_TAP_POWER(661);
private final int mId;
@@ -508,7 +508,7 @@
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
launchEmergencyGesture = handleEmergencyGesture();
- mUiEventLogger.log(GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
+ mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
}
mMetricsLogger.histogram("power_consecutive_short_tap_count",
mPowerButtonSlowConsecutiveTaps);
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index e12586b..c983600 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -18,6 +18,7 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppOp* = file:/core/java/android/permission/OWNERS
+per-file *Battery* = file:/BATTERY_STATS_OWNERS
per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index d10cf4d..fa3771a 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.FileUtils;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -104,6 +105,9 @@
public class PersistentDataBlockService extends SystemService {
private static final String TAG = PersistentDataBlockService.class.getSimpleName();
+ private static final String GSI_SANDBOX = "/data/gsi_persistent_data";
+ private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
+
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private static final int HEADER_SIZE = 8;
// Magic number to mark block device as adhering to the format consumed by this service
@@ -128,12 +132,13 @@
private static final String FLASH_LOCK_UNLOCKED = "0";
private final Context mContext;
- private final String mDataBlockFile;
+ private final boolean mIsRunningDSU;
private final Object mLock = new Object();
private final CountDownLatch mInitDoneSignal = new CountDownLatch(1);
private int mAllowedUid = -1;
private long mBlockDeviceSize;
+ private String mDataBlockFile;
@GuardedBy("mLock")
private boolean mIsWritable = true;
@@ -142,6 +147,7 @@
super(context);
mContext = context;
mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
+ mIsRunningDSU = SystemProperties.getBoolean(GSI_RUNNING_PROP, false);
mBlockDeviceSize = -1; // Load lazily
}
@@ -286,14 +292,28 @@
return true;
}
+ private FileOutputStream getBlockOutputStream() throws IOException {
+ if (!mIsRunningDSU) {
+ return new FileOutputStream(new File(mDataBlockFile));
+ } else {
+ File sandbox = new File(GSI_SANDBOX);
+ File realpdb = new File(SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP));
+ if (!sandbox.exists()) {
+ FileUtils.copy(realpdb, sandbox);
+ mDataBlockFile = GSI_SANDBOX;
+ }
+ Slog.i(TAG, "PersistentDataBlock copy-on-write");
+ return new FileOutputStream(sandbox);
+ }
+ }
+
private boolean computeAndWriteDigestLocked() {
byte[] digest = computeDigestLocked(null);
if (digest != null) {
DataOutputStream outputStream;
try {
- outputStream = new DataOutputStream(
- new FileOutputStream(new File(mDataBlockFile)));
- } catch (FileNotFoundException e) {
+ outputStream = new DataOutputStream(getBlockOutputStream());
+ } catch (IOException e) {
Slog.e(TAG, "partition not available?", e);
return false;
}
@@ -358,8 +378,8 @@
private void formatPartitionLocked(boolean setOemUnlockEnabled) {
DataOutputStream outputStream;
try {
- outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
- } catch (FileNotFoundException e) {
+ outputStream = new DataOutputStream(getBlockOutputStream());
+ } catch (IOException e) {
Slog.e(TAG, "partition not available?", e);
return;
}
@@ -384,8 +404,8 @@
private void doSetOemUnlockEnabledLocked(boolean enabled) {
FileOutputStream outputStream;
try {
- outputStream = new FileOutputStream(new File(mDataBlockFile));
- } catch (FileNotFoundException e) {
+ outputStream = getBlockOutputStream();
+ } catch (IOException e) {
Slog.e(TAG, "partition not available", e);
return;
}
@@ -461,8 +481,8 @@
DataOutputStream outputStream;
try {
- outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
- } catch (FileNotFoundException e) {
+ outputStream = new DataOutputStream(getBlockOutputStream());
+ } catch (IOException e) {
Slog.e(TAG, "partition not available?", e);
return -1;
}
@@ -547,6 +567,17 @@
public void wipe() {
enforceOemUnlockWritePermission();
+ if (mIsRunningDSU) {
+ File sandbox = new File(GSI_SANDBOX);
+ if (sandbox.exists()) {
+ if (sandbox.delete()) {
+ mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
+ } else {
+ Slog.e(TAG, "Failed to wipe sandbox persistent data block");
+ }
+ }
+ return;
+ }
synchronized (mLock) {
int ret = nativeWipe(mDataBlockFile);
@@ -706,8 +737,8 @@
private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
FileOutputStream outputStream;
try {
- outputStream = new FileOutputStream(new File(mDataBlockFile));
- } catch (FileNotFoundException e) {
+ outputStream = getBlockOutputStream();
+ } catch (IOException e) {
Slog.e(TAG, "partition not available", e);
return;
}
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 871de0d..ebd32e8 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -521,13 +521,6 @@
return pinKeys;
}
- private static boolean shouldPinSplitApks() {
- // For now this is disabled by default bcause the pinlist support for split APKs are
- // missing in the toolchain. This flag should be removed once it is ready. b/174697187.
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
- "pin_split_apks", false);
- }
-
private synchronized ArraySet<Integer> getPinKeys() {
return mPinKeys;
}
@@ -685,7 +678,7 @@
List<String> apks = new ArrayList<>();
apks.add(appInfo.sourceDir);
- if (shouldPinSplitApks() && appInfo.splitSourceDirs != null) {
+ if (appInfo.splitSourceDirs != null) {
for (String splitApk : appInfo.splitSourceDirs) {
apks.add(splitApk);
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 740a1c1..cbce720 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.ACCESS_MTP;
import static android.Manifest.permission.INSTALL_PACKAGES;
+import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
@@ -962,7 +963,7 @@
}
int delay = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
- ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 0);
+ ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 5000);
Slog.v(TAG, "getAnrDelayMillis for " + packageName + ". " + delay + "ms");
return delay;
}
@@ -1791,7 +1792,7 @@
public StorageManagerService(Context context) {
sSelf = this;
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mContext = context;
mResolver = mContext.getContentResolver();
mCallbacks = new Callbacks(FgThread.get().getLooper());
@@ -4606,6 +4607,25 @@
}
@Override
+ public boolean hasExternalStorageAccess(int uid, String packageName) {
+ try {
+ if (mIPackageManager.checkUidPermission(
+ MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED) {
+ return true;
+ }
+
+ if (mIAppOpsService.checkOperation(
+ OP_MANAGE_EXTERNAL_STORAGE, uid, packageName) == MODE_ALLOWED) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.w("Failed to check MANAGE_EXTERNAL_STORAGE access for " + packageName, e);
+ }
+
+ return false;
+ }
+
+ @Override
public void addResetListener(StorageManagerInternal.ResetListener listener) {
synchronized (mResetListeners) {
mResetListeners.add(listener);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index a9904ba..7276c78 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -60,6 +60,7 @@
import android.telephony.CellSignalStrengthTdscdma;
import android.telephony.CellSignalStrengthWcdma;
import android.telephony.DisconnectCause;
+import android.telephony.LinkCapacityEstimate;
import android.telephony.LocationAccessPolicy;
import android.telephony.PhoneCapability;
import android.telephony.PhoneStateListener;
@@ -318,7 +319,10 @@
private int[] mDataEnabledReason;
- private Map<Integer, Long> mAllowedNetworkTypesList;
+ private int[] mAllowedNetworkTypeReason;
+ private long[] mAllowedNetworkTypeValue;
+
+ private List<List<LinkCapacityEstimate>> mLinkCapacityEstimateLists;
/**
* Per-phone map of precise data connection state. The key of the map is the pair of transport
@@ -350,6 +354,8 @@
TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
TelephonyCallback.EVENT_DATA_ENABLED_CHANGED);
+ REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
+ TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED);
}
private boolean isLocationPermissionRequired(Set<Integer> events) {
@@ -383,7 +389,8 @@
private boolean isPrivilegedPhoneStatePermissionRequired(Set<Integer> events) {
return events.contains(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED)
|| events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
- || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED);
+ || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
+ || events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED);
}
private static final int MSG_USER_SWITCHED = 1;
@@ -527,6 +534,8 @@
mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
mIsDataEnabled= copyOf(mIsDataEnabled, mNumPhones);
mDataEnabledReason = copyOf(mDataEnabledReason, mNumPhones);
+ mAllowedNetworkTypeReason = copyOf(mAllowedNetworkTypeReason, mNumPhones);
+ mAllowedNetworkTypeValue = copyOf(mAllowedNetworkTypeValue, mNumPhones);
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
@@ -535,6 +544,7 @@
cutListToSize(mPreciseDataConnectionStates, mNumPhones);
cutListToSize(mBarringInfo, mNumPhones);
cutListToSize(mPhysicalChannelConfigs, mNumPhones);
+ cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
return;
}
@@ -571,6 +581,9 @@
mIsDataEnabled[i] = false;
mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build());
+ mAllowedNetworkTypeReason[i] = -1;
+ mAllowedNetworkTypeValue[i] = -1;
+ mLinkCapacityEstimateLists.add(i, new ArrayList<>());
}
}
@@ -630,9 +643,12 @@
mBarringInfo = new ArrayList<>();
mTelephonyDisplayInfos = new TelephonyDisplayInfo[numPhones];
mPhysicalChannelConfigs = new ArrayList<>();
- mAllowedNetworkTypesList = new HashMap<>();
+ mAllowedNetworkTypeReason = new int[numPhones];
+ mAllowedNetworkTypeValue = new long[numPhones];
mIsDataEnabled = new boolean[numPhones];
mDataEnabledReason = new int[numPhones];
+ mLinkCapacityEstimateLists = new ArrayList<>();
+
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -665,6 +681,9 @@
mIsDataEnabled[i] = false;
mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build());
+ mAllowedNetworkTypeReason[i] = -1;
+ mAllowedNetworkTypeValue[i] = -1;
+ mLinkCapacityEstimateLists.add(i, new ArrayList<>());
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1173,9 +1192,12 @@
}
}
if (events.contains(
- TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)) {
+ TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) {
try {
- r.callback.onAllowedNetworkTypesChanged(mAllowedNetworkTypesList);
+ if (mLinkCapacityEstimateLists.get(phoneId) != null) {
+ r.callback.onLinkCapacityEstimateChanged(mLinkCapacityEstimateLists
+ .get(phoneId));
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2454,18 +2476,19 @@
*
* @param phoneId the phone id.
* @param subId the subId.
- * @param allowedNetworkTypesList Map associating all allowed network type reasons with reason's
- * allowed network type values.
+ * @param reason the allowed network type reason.
+ * @param allowedNetworkType the allowed network type value.
*/
- public void notifyAllowedNetworkTypesChanged(int phoneId, int subId,
- Map allowedNetworkTypesList) {
+ public void notifyAllowedNetworkTypesChanged(int phoneId, int subId, int reason,
+ long allowedNetworkType) {
if (!checkNotifyPermission("notifyAllowedNetworkTypesChanged()")) {
return;
}
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mAllowedNetworkTypesList = allowedNetworkTypesList;
+ mAllowedNetworkTypeReason[phoneId] = reason;
+ mAllowedNetworkTypeValue[phoneId] = allowedNetworkType;
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
@@ -2473,10 +2496,48 @@
&& idMatch(r.subId, subId, phoneId)) {
try {
if (VDBG) {
- log("notifyAllowedNetworkTypesChanged: AllowedNetworkTypesList= "
- + mAllowedNetworkTypesList.toString());
+ log("notifyAllowedNetworkTypesChanged: reason= " + reason
+ + ", allowed network type:"
+ + TelephonyManager.convertNetworkTypeBitmaskToString(
+ allowedNetworkType));
}
- r.callback.onAllowedNetworkTypesChanged(mAllowedNetworkTypesList);
+ r.callback.onAllowedNetworkTypesChanged(reason, allowedNetworkType);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ /**
+ * Notify that the link capacity estimate has changed.
+ * @param phoneId the phone id.
+ * @param subId the subscription id.
+ * @param linkCapacityEstimateList a list of {@link LinkCapacityEstimate}
+ */
+ public void notifyLinkCapacityEstimateChanged(int phoneId, int subId,
+ List<LinkCapacityEstimate> linkCapacityEstimateList) {
+ if (!checkNotifyPermission("notifyLinkCapacityEstimateChanged()")) {
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyLinkCapacityEstimateChanged: linkCapacityEstimateList ="
+ + linkCapacityEstimateList);
+ }
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ mLinkCapacityEstimateLists.set(phoneId, linkCapacityEstimateList);
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onLinkCapacityEstimateChanged(linkCapacityEstimateList);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2531,6 +2592,9 @@
pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]);
pw.println("mIsDataEnabled=" + mIsDataEnabled);
pw.println("mDataEnabledReason=" + mDataEnabledReason);
+ pw.println("mAllowedNetworkTypeReason=" + mAllowedNetworkTypeReason[i]);
+ pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]);
+ pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
pw.decreaseIndent();
}
pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index ee61067..f566277 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -90,7 +90,12 @@
mCm = mContext.getSystemService(ConnectivityManager.class);
mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
TEST_NETWORK_PROVIDER_NAME);
- mCm.registerNetworkProvider(mNetworkProvider);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCm.registerNetworkProvider(mNetworkProvider);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index cd3892d..051cd99 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -64,7 +64,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.LocationPermissionChecker;
+import com.android.net.module.util.LocationPermissionChecker;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
import com.android.server.vcn.VcnContext;
@@ -666,6 +666,10 @@
@NonNull IVcnUnderlyingNetworkPolicyListener listener) {
requireNonNull(listener, "listener was null");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.NETWORK_FACTORY,
+ "Must have permission NETWORK_FACTORY to unregister a policy listener");
+
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
PolicyListenerBinderDeath listenerBinderDeath =
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 7375523..c360190 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1076,7 +1076,7 @@
} catch (RuntimeException e) {
// The account manager only throws security exceptions, so let's
// log all others.
- if (!(e instanceof SecurityException)) {
+ if (!(e instanceof SecurityException || e instanceof IllegalArgumentException)) {
Slog.wtf(TAG, "Account Manager Crash", e);
}
throw e;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 68c4a73..3c445ae 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -24,6 +24,7 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
import static android.os.PowerWhitelistManager.REASON_ACTIVITY_STARTER;
import static android.os.PowerWhitelistManager.REASON_ALLOWLISTED_PACKAGE;
import static android.os.PowerWhitelistManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
@@ -1841,7 +1842,7 @@
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ SystemClock.uptimeMillis());
}
}
if (alreadyStartedOp) {
@@ -1863,7 +1864,7 @@
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ SystemClock.uptimeMillis());
}
mAm.mAppOpsService.finishOperation(
AppOpsManager.getToken(mAm.mAppOpsService),
@@ -3765,6 +3766,7 @@
}
}
+ final long now = SystemClock.uptimeMillis();
// Check to see if the service had been started as foreground, but being
// brought down before actually showing a notification. That is not allowed.
if (r.fgRequired) {
@@ -3774,8 +3776,7 @@
r.fgWaiting = false;
ServiceState stracker = r.getTracker();
if (stracker != null) {
- stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), now);
}
mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
@@ -3834,8 +3835,7 @@
decActiveForegroundAppLocked(smap, r);
ServiceState stracker = r.getTracker();
if (stracker != null) {
- stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
- r.lastActivity);
+ stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), now);
}
mAm.mAppOpsService.finishOperation(
AppOpsManager.getToken(mAm.mAppOpsService),
@@ -3902,7 +3902,6 @@
}
int memFactor = mAm.mProcessStats.getMemFactorLocked();
- long now = SystemClock.uptimeMillis();
if (r.tracker != null) {
r.tracker.setStarted(false, memFactor, now);
r.tracker.setBound(false, memFactor, now);
@@ -5411,16 +5410,28 @@
}
}
+ boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
+ if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
+ return true;
+ }
+ final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
+ callingPackage, callingPid, callingUid, null /* serviceRecord */,
+ false /* allowBackgroundActivityStarts */);
+ final @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundLocked(
+ allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */);
+ return allowStartFgs != REASON_DENIED;
+ }
+
/**
* Should allow while-in-use permissions in FGS or not.
* A typical BG started FGS is not allowed to have while-in-use permissions.
* @param callingPackage caller app's package name.
* @param callingUid caller app's uid.
- * @param r the service to start.
+ * @param targetService the service to start.
* @return {@link ReasonCode}
*/
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
- int callingPid, int callingUid, ServiceRecord r,
+ int callingPid, int callingUid, @Nullable ServiceRecord targetService,
boolean allowBackgroundActivityStarts) {
int ret = REASON_DENIED;
@@ -5482,8 +5493,8 @@
}
if (ret == REASON_DENIED) {
- if (r.app != null) {
- ActiveInstrumentation instr = r.app.getActiveInstrumentation();
+ if (targetService != null && targetService.app != null) {
+ ActiveInstrumentation instr = targetService.app.getActiveInstrumentation();
if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
}
@@ -5529,16 +5540,44 @@
private @ReasonCode int shouldAllowFgsStartForegroundLocked(
@ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) {
- int ret = allowWhileInUse;
FgsStartTempAllowList.TempFgsAllowListEntry tempAllowListReason =
r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
+ int ret = shouldAllowFgsStartForegroundLocked(allowWhileInUse, callingPid, callingUid,
+ callingPackage, r);
- final StringBuilder sb = new StringBuilder(64);
final int uidState = mAm.getUidStateLocked(callingUid);
+ final String debugInfo =
+ "[callingPackage: " + callingPackage
+ + "; callingUid: " + callingUid
+ + "; uidState: " + ProcessList.makeProcStateString(uidState)
+ + "; intent: " + intent
+ + "; code:" + reasonCodeToString(ret)
+ + "; tempAllowListReason:<"
+ + (tempAllowListReason == null ? null :
+ (tempAllowListReason.mReason
+ + ",reasonCode:"
+ + reasonCodeToString(tempAllowListReason.mReasonCode)
+ + ",duration:" + tempAllowListReason.mDuration
+ + ",callingUid:" + tempAllowListReason.mCallingUid))
+ + ">"
+ + "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+ + "]";
+ if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
+ r.mLoggedInfoAllowStartForeground = false;
+ r.mInfoAllowStartForeground = debugInfo;
+ }
+ return ret;
+ }
+
+ private @ReasonCode int shouldAllowFgsStartForegroundLocked(@ReasonCode int allowWhileInUse,
+ int callingPid, int callingUid, String callingPackage,
+ @Nullable ServiceRecord targetService) {
+ int ret = allowWhileInUse;
+
if (ret == REASON_DENIED) {
+ final int uidState = mAm.getUidStateLocked(callingUid);
// Is the calling UID at PROCESS_STATE_TOP or above?
if (uidState <= PROCESS_STATE_TOP) {
- sb.append("uidState=").append(uidState);
ret = getReasonCodeFromProcState(uidState);
}
}
@@ -5559,6 +5598,14 @@
&& instr.mHasBackgroundForegroundServiceStartsPermission) {
return REASON_INSTR_BACKGROUND_FGS_PERMISSION;
}
+ final long lastInvisibleTime = app.mState.getLastInvisibleTime();
+ if (lastInvisibleTime > 0 && lastInvisibleTime < Long.MAX_VALUE) {
+ final long sinceLastInvisible = SystemClock.elapsedRealtime()
+ - lastInvisibleTime;
+ if (sinceLastInvisible < mAm.mConstants.mFgToBgFgsGraceDuration) {
+ return REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
+ }
+ }
}
}
return null;
@@ -5610,8 +5657,10 @@
// NOTE this should always be the last check.
if (ret == REASON_DENIED) {
- if (isPackageExemptedFromFgsRestriction(r.appInfo.packageName, r.appInfo.uid)
- || isPackageExemptedFromFgsRestriction(callingPackage, callingUid)) {
+ if (isPackageExemptedFromFgsRestriction(callingPackage, callingUid)) {
+ ret = REASON_EXEMPTED_PACKAGE;
+ } else if (targetService != null && isPackageExemptedFromFgsRestriction(
+ targetService.appInfo.packageName, targetService.appInfo.uid)) {
ret = REASON_EXEMPTED_PACKAGE;
}
}
@@ -5624,28 +5673,6 @@
}
}
- final String debugInfo =
- "[callingPackage: " + callingPackage
- + "; callingUid: " + callingUid
- + "; uidState: " + ProcessList.makeProcStateString(uidState)
- + "; intent: " + intent
- + "; code:" + reasonCodeToString(ret)
- + "; tempAllowListReason:<" +
- (tempAllowListReason == null ? null :
- (tempAllowListReason.mReason
- + ",reasonCode:"
- + reasonCodeToString(tempAllowListReason.mReasonCode)
- + ",duration:" + tempAllowListReason.mDuration
- + ",callingUid:" + tempAllowListReason.mCallingUid))
- + ">"
- + "; extra:" + sb.toString()
- + "; targetSdkVersion:" + r.appInfo.targetSdkVersion
- + "]";
- if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
- r.mLoggedInfoAllowStartForeground = false;
- r.mInfoAllowStartForeground = debugInfo;
- }
-
return ret;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ba8f190..5859cea 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -95,6 +95,7 @@
"process_crash_count_reset_interval";
static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit";
static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
+ static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -133,6 +134,7 @@
private static final int DEFAULT_PROCESS_CRASH_COUNT_RESET_INTERVAL = 12 * 60 * 60 * 1000;
private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12;
private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000;
+ private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
// Flag stored in the DeviceConfig API.
@@ -388,6 +390,12 @@
*/
volatile long mBootTimeTempAllowlistDuration = DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION;
+ /**
+ * The grace period in milliseconds to allow a process to start FGS from background after
+ * switching from foreground to background; currently it's only applicable to its activities.
+ */
+ volatile long mFgToBgFgsGraceDuration = DEFAULT_FG_TO_BG_FGS_GRACE_DURATION;
+
private final ActivityManagerService mService;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -575,6 +583,9 @@
case KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION:
updateBootTimeTempAllowListDuration();
break;
+ case KEY_FG_TO_BG_FGS_GRACE_DURATION:
+ updateFgToBgFgsGraceDuration();
+ break;
default:
break;
}
@@ -851,6 +862,13 @@
DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION);
}
+ private void updateFgToBgFgsGraceDuration() {
+ mFgToBgFgsGraceDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_FG_TO_BG_FGS_GRACE_DURATION,
+ DEFAULT_FG_TO_BG_FGS_GRACE_DURATION);
+ }
+
private void updateImperceptibleKillExemptions() {
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
@@ -1051,6 +1069,8 @@
pw.println(MAX_PHANTOM_PROCESSES);
pw.print(" "); pw.print(KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION); pw.print("=");
pw.println(mBootTimeTempAllowlistDuration);
+ pw.print(" "); pw.print(KEY_FG_TO_BG_FGS_GRACE_DURATION); pw.print("=");
+ pw.println(mFgToBgFgsGraceDuration);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
new file mode 100644
index 0000000..cd4180e
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.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.am;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+/**
+ * Interface for in-process calls into
+ * {@link android.content.Context#ACTIVITY_SERVICE ActivityManager system service}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ActivityManagerLocal {
+ /**
+ * Checks whether an app will be able to start a foreground service or not.
+ *
+ * @param pid The process id belonging to the app to be checked.
+ * @param uid The UID of the app to be checked.
+ * @param packageName The package name of the app to be checked.
+ * @return whether the app will be able to start a foreground service or not.
+ */
+ boolean canStartForegroundService(int pid, int uid, @NonNull String packageName);
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 06a1abb..0e8644a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -345,6 +345,7 @@
import com.android.server.DisplayThread;
import com.android.server.IntentResolver;
import com.android.server.IoThread;
+import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.NetworkManagementInternal;
@@ -2327,6 +2328,8 @@
mAppOpsService.publish();
Slog.d("AppOps", "AppOpsService published");
LocalServices.addService(ActivityManagerInternal.class, mInternal);
+ LocalManagerRegistry.addManager(ActivityManagerLocal.class,
+ (ActivityManagerLocal) mInternal);
mActivityTaskManager.onActivityManagerInternalAdded();
mPendingIntentController.onActivityManagerInternalAdded();
mAppProfiler.onActivityManagerInternalAdded();
@@ -15086,7 +15089,8 @@
}
@VisibleForTesting
- public final class LocalService extends ActivityManagerInternal {
+ public final class LocalService extends ActivityManagerInternal
+ implements ActivityManagerLocal {
@Override
public String checkContentProviderAccess(String authority, int userId) {
return mCpHelper.checkContentProviderAccess(authority, userId);
@@ -16008,6 +16012,13 @@
public void unregisterAnrController(AnrController controller) {
mActivityTaskManager.unregisterAnrController(controller);
}
+
+ @Override
+ public boolean canStartForegroundService(int pid, int uid, @NonNull String packageName) {
+ synchronized (ActivityManagerService.this) {
+ return mServices.canStartForegroundServiceLocked(pid, uid, packageName);
+ }
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
@@ -16136,7 +16147,12 @@
}
}
- public void waitForBroadcastIdle(PrintWriter pw) {
+ @Override
+ public void waitForBroadcastIdle() {
+ waitForBroadcastIdle(/* printWriter= */ null);
+ }
+
+ public void waitForBroadcastIdle(@Nullable PrintWriter pw) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
while (true) {
boolean idle = true;
@@ -16144,9 +16160,11 @@
for (BroadcastQueue queue : mBroadcastQueues) {
if (!queue.isIdle()) {
final String msg = "Waiting for queue " + queue + " to become idle...";
- pw.println(msg);
- pw.println(queue.describeState());
- pw.flush();
+ if (pw != null) {
+ pw.println(msg);
+ pw.println(queue.describeState());
+ pw.flush();
+ }
Slog.v(TAG, msg);
queue.cancelDeferrals();
idle = false;
@@ -16156,8 +16174,10 @@
if (idle) {
final String msg = "All broadcast queues are idle!";
- pw.println(msg);
- pw.flush();
+ if (pw != null) {
+ pw.println(msg);
+ pw.flush();
+ }
Slog.v(TAG, msg);
return;
} else {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index e2086b0..e74c936 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -589,9 +589,9 @@
Slog.w(TAG, "exception reading modem stats: " + e.getCause());
}
- final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDelta;
+ final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas;
if (mMeasuredEnergySnapshot == null || futureECRs == null) {
- measuredEnergyDelta = null;
+ measuredEnergyDeltas = null;
} else {
final int voltageMv;
synchronized (mStats) {
@@ -610,7 +610,7 @@
ecrs = null;
}
- measuredEnergyDelta = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs, voltageMv);
+ measuredEnergyDeltas = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs, voltageMv);
}
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -633,10 +633,10 @@
}
final long[] cpuClusterChargeUC;
- if (measuredEnergyDelta == null) {
+ if (measuredEnergyDeltas == null) {
cpuClusterChargeUC = null;
} else {
- cpuClusterChargeUC = measuredEnergyDelta.cpuClusterChargeUC;
+ cpuClusterChargeUC = measuredEnergyDeltas.cpuClusterChargeUC;
}
mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff, cpuClusterChargeUC);
}
@@ -650,9 +650,9 @@
mStats.updateRpmStatsLocked(elapsedRealtimeUs);
}
- // Inform mStats about each applicable measured energy.
- if (measuredEnergyDelta != null) {
- final long displayChargeUC = measuredEnergyDelta.displayChargeUC;
+ // Inform mStats about each applicable measured energy (unless addressed elsewhere).
+ if (measuredEnergyDeltas != null) {
+ final long displayChargeUC = measuredEnergyDeltas.displayChargeUC;
if (displayChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) {
// If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState,
@@ -660,19 +660,23 @@
}
}
// Inform mStats about each applicable custom energy bucket.
- if (measuredEnergyDelta != null
- && measuredEnergyDelta.otherTotalChargeUC != null) {
+ if (measuredEnergyDeltas != null
+ && measuredEnergyDeltas.otherTotalChargeUC != null) {
// Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
- for (int ord = 0; ord < measuredEnergyDelta.otherTotalChargeUC.length; ord++) {
- long totalEnergy = measuredEnergyDelta.otherTotalChargeUC[ord];
- SparseLongArray uidEnergies = measuredEnergyDelta.otherUidChargesUC[ord];
+ for (int ord = 0; ord < measuredEnergyDeltas.otherTotalChargeUC.length; ord++) {
+ long totalEnergy = measuredEnergyDeltas.otherTotalChargeUC[ord];
+ SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidChargesUC[ord];
mStats.updateCustomMeasuredEnergyStatsLocked(ord, totalEnergy, uidEnergies);
}
}
if (bluetoothInfo != null) {
if (bluetoothInfo.isValid()) {
- mStats.updateBluetoothStateLocked(bluetoothInfo, elapsedRealtime, uptime);
+ final long btChargeUC = measuredEnergyDeltas != null
+ ? measuredEnergyDeltas.bluetoothChargeUC
+ : MeasuredEnergySnapshot.UNAVAILABLE;
+ mStats.updateBluetoothStateLocked(bluetoothInfo,
+ btChargeUC, elapsedRealtime, uptime);
} else {
Slog.w(TAG, "bluetooth info is invalid: " + bluetoothInfo);
}
@@ -684,10 +688,10 @@
if (wifiInfo != null) {
if (wifiInfo.isValid()) {
- // TODO: wifiEnergyDelta = measuredEnergyDelta.consumerTypeEnergyUJ
- // .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE)
- mStats.updateWifiState(extractDeltaLocked(wifiInfo)
- /*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime);
+ final long wifiChargeUC = measuredEnergyDeltas != null ?
+ measuredEnergyDeltas.wifiChargeUC : MeasuredEnergySnapshot.UNAVAILABLE;
+ mStats.updateWifiState(
+ extractDeltaLocked(wifiInfo), wifiChargeUC, elapsedRealtime, uptime);
} else {
Slog.w(TAG, "wifi info is invalid: " + wifiInfo);
}
@@ -820,13 +824,19 @@
for (int idx = 0; idx < size; idx++) {
final EnergyConsumer consumer = idToConsumer.valueAt(idx);
switch (consumer.type) {
+ case EnergyConsumerType.BLUETOOTH:
+ buckets[MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH] = true;
+ break;
+ case EnergyConsumerType.CPU_CLUSTER:
+ buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+ break;
case EnergyConsumerType.DISPLAY:
buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true;
buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE] = true;
buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_OTHER] = true;
break;
- case EnergyConsumerType.CPU_CLUSTER:
- buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+ case EnergyConsumerType.WIFI:
+ buckets[MeasuredEnergyStats.POWER_BUCKET_WIFI] = true;
break;
}
}
@@ -864,13 +874,18 @@
}
final IntArray energyConsumerIds = new IntArray();
+ if ((flags & UPDATE_BT) != 0) {
+ addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.BLUETOOTH);
+ }
if ((flags & UPDATE_CPU) != 0) {
addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CPU_CLUSTER);
}
if ((flags & UPDATE_DISPLAY) != 0) {
addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
}
- // TODO: Wifi, Bluetooth, etc., go here
+ if ((flags & UPDATE_WIFI) != 0) {
+ addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.WIFI);
+ }
if (energyConsumerIds.size() == 0) {
return null;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6b9fc07..c3f97ad 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
import android.annotation.NonNull;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -1927,7 +1928,7 @@
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
- mStats.updateWifiState(info, elapsedRealtime, uptime);
+ mStats.updateWifiState(info, POWER_DATA_UNAVAILABLE, elapsedRealtime, uptime);
});
}
}
@@ -1945,7 +1946,8 @@
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
- mStats.updateBluetoothStateLocked(info, elapsedRealtime, uptime);
+ mStats.updateBluetoothStateLocked(
+ info, POWER_DATA_UNAVAILABLE, elapsedRealtime, uptime);
}
});
}
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index 9b2ca13..4c9ab63 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -41,7 +41,7 @@
private static final int MILLIVOLTS_PER_VOLT = 1000;
- public static final long UNAVAILABLE = -1L;
+ public static final long UNAVAILABLE = android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
/** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
private final SparseArray<EnergyConsumer> mEnergyConsumers;
@@ -109,12 +109,18 @@
/** Class for returning the relevant data calculated from the measured energy delta */
static class MeasuredEnergyDeltaData {
- /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */
- public long displayChargeUC = UNAVAILABLE;
+ /** The chargeUC for {@link EnergyConsumerType#BLUETOOTH}. */
+ public long bluetoothChargeUC = UNAVAILABLE;
/** The chargeUC for {@link EnergyConsumerType#CPU_CLUSTER}s. */
public long[] cpuClusterChargeUC = null;
+ /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */
+ public long displayChargeUC = UNAVAILABLE;
+
+ /** The chargeUC for {@link EnergyConsumerType#WIFI}. */
+ public long wifiChargeUC = UNAVAILABLE;
+
/** Map of {@link EnergyConsumerType#OTHER} ordinals to their total chargeUC. */
public @Nullable long[] otherTotalChargeUC = null;
@@ -196,8 +202,8 @@
final long deltaChargeUC = calculateChargeConsumedUC(deltaUJ, avgVoltageMV);
switch (type) {
- case EnergyConsumerType.DISPLAY:
- output.displayChargeUC = deltaChargeUC;
+ case EnergyConsumerType.BLUETOOTH:
+ output.bluetoothChargeUC = deltaChargeUC;
break;
case EnergyConsumerType.CPU_CLUSTER:
@@ -207,6 +213,14 @@
output.cpuClusterChargeUC[ordinal] = deltaChargeUC;
break;
+ case EnergyConsumerType.DISPLAY:
+ output.displayChargeUC = deltaChargeUC;
+ break;
+
+ case EnergyConsumerType.WIFI:
+ output.wifiChargeUC = deltaChargeUC;
+ break;
+
case EnergyConsumerType.OTHER:
if (output.otherTotalChargeUC == null) {
output.otherTotalChargeUC = new long[getNumOtherOrdinals()];
@@ -215,6 +229,7 @@
output.otherTotalChargeUC[ordinal] = deltaChargeUC;
output.otherUidChargesUC[ordinal] = otherUidCharges;
break;
+
default:
Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 939d35f..5ae65ef 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1367,6 +1367,7 @@
ProcessRecord app;
int adj;
boolean foregroundActivities;
+ boolean mHasVisibleActivities;
int procState;
int schedGroup;
int appUid;
@@ -1375,10 +1376,12 @@
ProcessStateRecord mState;
void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
- int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
+ boolean hasVisibleActivities, int procState, int schedGroup, int appUid,
+ int logUid, int processStateCurTop) {
this.app = app;
this.adj = adj;
this.foregroundActivities = foregroundActivities;
+ this.mHasVisibleActivities = hasVisibleActivities;
this.procState = procState;
this.schedGroup = schedGroup;
this.appUid = appUid;
@@ -1411,6 +1414,7 @@
mState.setCached(false);
mState.setEmpty(false);
foregroundActivities = true;
+ mHasVisibleActivities = true;
}
@Override
@@ -1436,6 +1440,7 @@
mState.setCached(false);
mState.setEmpty(false);
foregroundActivities = true;
+ mHasVisibleActivities = false;
}
@Override
@@ -1468,6 +1473,7 @@
mState.setCached(false);
mState.setEmpty(false);
foregroundActivities = true;
+ mHasVisibleActivities = false;
}
@Override
@@ -1480,6 +1486,7 @@
"Raise procstate to cached activity: " + app);
}
}
+ mHasVisibleActivities = false;
}
}
@@ -1591,12 +1598,14 @@
int capability = 0;
boolean foregroundActivities = false;
+ boolean hasVisibleActivities = false;
if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP && app == topApp) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
state.setAdjType("top-activity");
foregroundActivities = true;
+ hasVisibleActivities = true;
procState = PROCESS_STATE_CUR_TOP;
state.bumpAllowStartFgsState(PROCESS_STATE_TOP);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1672,11 +1681,12 @@
// Examine all activities if not already foreground.
if (!foregroundActivities && state.getCachedHasActivities()) {
state.computeOomAdjFromActivitiesIfNecessary(mTmpComputeOomAdjWindowCallback,
- adj, foregroundActivities, procState, schedGroup, appUid, logUid,
- PROCESS_STATE_CUR_TOP);
+ adj, foregroundActivities, hasVisibleActivities, procState, schedGroup,
+ appUid, logUid, PROCESS_STATE_CUR_TOP);
adj = state.getCachedAdj();
foregroundActivities = state.getCachedForegroundActivities();
+ hasVisibleActivities = state.getCachedHasVisibleActivities();
procState = state.getCachedProcState();
schedGroup = state.getCachedSchedGroup();
}
@@ -2450,6 +2460,7 @@
state.setCurrentSchedulingGroup(schedGroup);
state.setCurProcState(procState);
state.setCurRawProcState(procState);
+ state.updateLastInvisibleTime(hasVisibleActivities);
state.setHasForegroundActivities(foregroundActivities);
state.setCompletedAdjSeq(mAdjSeq);
state.setAllowStartFgs();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ed8d696..442cdd9 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -786,7 +786,7 @@
mAppDataIsolationEnabled =
SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mAppDataIsolationAllowlistedApps = new ArrayList<>(
SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
@@ -1598,7 +1598,8 @@
}
}
- private int[] computeGidsForProcess(int mountExternal, int uid, int[] permGids) {
+ private int[] computeGidsForProcess(int mountExternal, int uid, int[] permGids,
+ boolean externalStorageAccess) {
ArrayList<Integer> gidList = new ArrayList<>(permGids.length + 5);
final int sharedAppGid = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
@@ -1644,6 +1645,11 @@
// PublicVolumes: /mnt/media_rw/<volume>
gidList.add(Process.MEDIA_RW_GID);
}
+ if (externalStorageAccess) {
+ // Apps with MANAGE_EXTERNAL_STORAGE PERMISSION need the external_storage gid to access
+ // USB OTG (unreliable) volumes on /mnt/media_rw/<vol name>
+ gidList.add(Process.EXTERNAL_STORAGE_GID);
+ }
int[] gidArray = new int[gidList.size()];
for (int i = 0; i < gidArray.length; i++) {
@@ -1805,6 +1811,7 @@
int uid = app.uid;
int[] gids = null;
int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+ boolean externalStorageAccess = false;
if (!app.isolated) {
int[] permGids = null;
try {
@@ -1816,6 +1823,8 @@
StorageManagerInternal.class);
mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
app.info.packageName);
+ externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid,
+ app.info.packageName);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -1835,7 +1844,7 @@
}
}
- gids = computeGidsForProcess(mountExternal, uid, permGids);
+ gids = computeGidsForProcess(mountExternal, uid, permGids, externalStorageAccess);
}
app.setMountMode(mountExternal);
checkSlow(startTime, "startProcess: building args");
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 6d783fc..d97d343 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -43,6 +43,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ProcessRecord.TAG;
+import android.annotation.ElapsedRealtimeLong;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.os.Binder;
@@ -386,6 +387,16 @@
@GuardedBy("mService")
private boolean mReachable;
+ /**
+ * The most recent time when the last visible activity within this process became invisible.
+ *
+ * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is
+ * any visible activities within this process at this moment.</p>
+ */
+ @GuardedBy("mService")
+ @ElapsedRealtimeLong
+ private long mLastInvisibleTime;
+
// Below are the cached task info for OomAdjuster only
private static final int VALUE_INVALID = -1;
private static final int VALUE_FALSE = 0;
@@ -1040,18 +1051,19 @@
@GuardedBy("mService")
void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
- int adj, boolean foregroundActivities, int procState, int schedGroup, int appUid,
- int logUid, int processCurTop) {
+ int adj, boolean foregroundActivities, boolean hasVisibleActivities, int procState,
+ int schedGroup, int appUid, int logUid, int processCurTop) {
if (mCachedAdj != ProcessList.INVALID_ADJ) {
return;
}
- callback.initialize(mApp, adj, foregroundActivities, procState, schedGroup, appUid, logUid,
- processCurTop);
+ callback.initialize(mApp, adj, foregroundActivities, hasVisibleActivities, procState,
+ schedGroup, appUid, logUid, processCurTop);
final int minLayer = Math.min(ProcessList.VISIBLE_APP_LAYER_MAX,
mApp.getWindowProcessController().computeOomAdjFromActivities(callback));
mCachedAdj = callback.adj;
mCachedForegroundActivities = callback.foregroundActivities;
+ mCachedHasVisibleActivities = callback.mHasVisibleActivities ? VALUE_TRUE : VALUE_FALSE;
mCachedProcState = callback.procState;
mCachedSchedGroup = callback.schedGroup;
@@ -1263,6 +1275,21 @@
return mAllowStartFgs;
}
+ @GuardedBy("mService")
+ void updateLastInvisibleTime(boolean hasVisibleActivities) {
+ if (hasVisibleActivities) {
+ mLastInvisibleTime = Long.MAX_VALUE;
+ } else if (mLastInvisibleTime == Long.MAX_VALUE) {
+ mLastInvisibleTime = SystemClock.elapsedRealtime();
+ }
+ }
+
+ @GuardedBy("mService")
+ @ElapsedRealtimeLong
+ long getLastInvisibleTime() {
+ return mLastInvisibleTime;
+ }
+
@GuardedBy({"mService", "mProcLock"})
void dump(PrintWriter pw, String prefix, long nowUptime) {
if (mReportedInteraction || mFgInteractionTime != 0) {
@@ -1340,6 +1367,15 @@
TimeUtils.formatDuration(mLastTopTime, nowUptime, pw);
pw.println();
}
+ if (mLastInvisibleTime > 0 && mLastInvisibleTime < Long.MAX_VALUE) {
+ pw.print(prefix); pw.print("lastInvisibleTime=");
+ final long elapsedRealtimeNow = SystemClock.elapsedRealtime();
+ final long currentTimeNow = System.currentTimeMillis();
+ final long lastInvisibleCurrentTime =
+ currentTimeNow - elapsedRealtimeNow + mLastInvisibleTime;
+ TimeUtils.dumpTimeWithDelta(pw, lastInvisibleCurrentTime, currentTimeNow);
+ pw.println();
+ }
if (mHasStartedServices) {
pw.print(prefix); pw.print("hasStartedServices="); pw.println(mHasStartedServices);
}
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 3e32380..2982545 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -137,6 +137,11 @@
boolean readPersistentDataLocked() {
mGameModes.clear();
+ if (!mSettingsFile.exists()) {
+ Slog.v(GameManagerService.TAG, "Settings file doesn't exists, skip reading");
+ return false;
+ }
+
try {
final FileInputStream str = mSettingsFile.openRead();
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 2c0a589..ad5a65c 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -173,7 +173,9 @@
if (!checkHibernationEnabled("isHibernatingForUser")) {
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 "
@@ -202,6 +204,9 @@
if (!checkHibernationEnabled("isHibernatingGlobally")) {
return false;
}
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_APP_HIBERNATION,
+ "Caller does not have MANAGE_APP_HIBERNATION permission.");
synchronized (mLock) {
GlobalLevelState state = mGlobalHibernationStates.get(packageName);
if (state == null) {
@@ -223,6 +228,9 @@
if (!checkHibernationEnabled("setHibernatingForUser")) {
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 "
@@ -263,6 +271,9 @@
if (!checkHibernationEnabled("setHibernatingGlobally")) {
return;
}
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_APP_HIBERNATION,
+ "Caller does not have MANAGE_APP_HIBERNATION permission.");
synchronized (mLock) {
GlobalLevelState state = mGlobalHibernationStates.get(packageName);
if (state == null) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6614e06..109ffe3 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2098,26 +2098,28 @@
ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
-
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
- boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
- boolean isCallerPermissionController;
- try {
- isCallerPermissionController = pm.getPackageUid(
- mContext.getPackageManager().getPermissionControllerPackageName(), 0)
- == Binder.getCallingUid();
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- return;
- }
+ boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+ if (!isSelfRequest) {
+ boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+ boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+ boolean isCallerPermissionController;
+ try {
+ isCallerPermissionController = pm.getPackageUid(
+ mContext.getPackageManager().getPermissionControllerPackageName(), 0)
+ == Binder.getCallingUid();
+ } catch (PackageManager.NameNotFoundException doesNotHappen) {
+ return;
+ }
- if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) {
- mHandler.post(() -> callback.sendResult(new Bundle()));
- return;
- }
+ if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) {
+ mHandler.post(() -> callback.sendResult(new Bundle()));
+ return;
+ }
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+ }
final String[] opNamesArray = (opNames != null)
? opNames.toArray(new String[opNames.size()]) : null;
@@ -3089,14 +3091,10 @@
return AppOpsManager.MODE_IGNORED;
}
- // This is a workaround for R QPR, new API change is not allowed. We only allow the current
- // voice recognizer is also the voice interactor to noteproxy op.
- final boolean isTrustVoiceServiceProxy = AppOpsManager.isTrustedVoiceServiceProxy(mContext,
- proxyPackageName, code, UserHandle.getUserId(proxyUid));
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
final boolean isProxyTrusted = mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
- == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame;
+ == PackageManager.PERMISSION_GRANTED || isSelfBlame;
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
@@ -3574,14 +3572,10 @@
return AppOpsManager.MODE_IGNORED;
}
- // This is a workaround for R QPR, new API change is not allowed. We only allow the current
- // voice recognizer is also the voice interactor to noteproxy op.
- final boolean isTrustVoiceServiceProxy = AppOpsManager.isTrustedVoiceServiceProxy(mContext,
- proxyPackageName, code, UserHandle.getUserId(proxyUid));
final boolean isSelfBlame = Binder.getCallingUid() == proxiedUid;
final boolean isProxyTrusted = mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
- == PackageManager.PERMISSION_GRANTED || isTrustVoiceServiceProxy || isSelfBlame;
+ == PackageManager.PERMISSION_GRANTED || isSelfBlame;
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index ed62abc..2b0157c 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -49,8 +49,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
-import libcore.util.EmptyArray;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -85,6 +83,8 @@
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 TAG_HISTORY = "h";
private static final String ATTR_VERSION = "v";
private static final int CURRENT_VERSION = 1;
@@ -107,6 +107,8 @@
private static final String ATTR_UID_STATE = "us";
private static final String ATTR_FLAGS = "f";
+ private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED;
+
// Lock for read/write access to on disk state
private final Object mOnDiskLock = new Object();
@@ -119,6 +121,9 @@
@GuardedBy("mInMemoryLock")
private DiscreteOps mDiscreteOps;
+ @GuardedBy("mOnDiskLock")
+ private DiscreteOps mCachedOps = null;
+
DiscreteRegistry(Object inMemoryLock) {
mInMemoryLock = inMemoryLock;
mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"),
@@ -173,23 +178,25 @@
}
}
}
- }
- DiscreteOps discreteOps;
- synchronized (mInMemoryLock) {
- discreteOps = mDiscreteOps;
- mDiscreteOps = new DiscreteOps();
- }
- if (discreteOps.isEmpty()) {
- return;
- }
- long currentTimeStamp = Instant.now().toEpochMilli();
- try {
- final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX);
- discreteOps.writeToFile(file);
- } catch (Throwable t) {
- Slog.e(TAG,
- "Error writing timeline state: " + t.getMessage() + " "
- + Arrays.toString(t.getStackTrace()));
+ DiscreteOps discreteOps;
+ synchronized (mInMemoryLock) {
+ discreteOps = mDiscreteOps;
+ mDiscreteOps = new DiscreteOps();
+ mCachedOps = null;
+ }
+ if (discreteOps.isEmpty()) {
+ return;
+ }
+ long currentTimeStamp = Instant.now().toEpochMilli();
+ try {
+ final File file = new File(mDiscreteAccessDir,
+ currentTimeStamp + TIMELINE_FILE_SUFFIX);
+ discreteOps.writeToFile(file);
+ } catch (Throwable t) {
+ Slog.e(TAG,
+ "Error writing timeline state: " + t.getMessage() + " "
+ + Arrays.toString(t.getStackTrace()));
+ }
}
}
@@ -197,25 +204,33 @@
long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
@Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
@Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
- writeAndClearAccessHistory();
- DiscreteOps discreteOps = new DiscreteOps();
- readDiscreteOpsFromDisk(discreteOps, beginTimeMillis, endTimeMillis, filter, uidFilter,
- packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
+ DiscreteOps discreteOps = getAndCacheDiscreteOps();
+ discreteOps.filter(beginTimeMillis, endTimeMillis, filter, uidFilter, packageNameFilter,
+ opNamesFilter, attributionTagFilter, flagsFilter);
discreteOps.applyToHistoricalOps(result);
return;
}
- private void readDiscreteOpsFromDisk(DiscreteOps discreteOps, long beginTimeMillis,
- long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
- @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
- @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+ private DiscreteOps getAndCacheDiscreteOps() {
+ DiscreteOps discreteOps = new DiscreteOps();
+
synchronized (mOnDiskLock) {
- long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
- ChronoUnit.MILLIS).toEpochMilli();
- if (historyBeginTimeMillis > endTimeMillis) {
- return;
+ synchronized (mInMemoryLock) {
+ discreteOps.merge(mDiscreteOps);
}
- beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis);
+ if (mCachedOps == null) {
+ mCachedOps = new DiscreteOps();
+ readDiscreteOpsFromDisk(mCachedOps);
+ }
+ discreteOps.merge(mCachedOps);
+ }
+ return discreteOps;
+ }
+
+ private void readDiscreteOpsFromDisk(DiscreteOps discreteOps) {
+ synchronized (mOnDiskLock) {
+ long beginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
+ ChronoUnit.MILLIS).toEpochMilli();
final File[] files = mDiscreteAccessDir.listFiles();
if (files != null && files.length > 0) {
@@ -229,8 +244,7 @@
if (timestamp < beginTimeMillis) {
continue;
}
- discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter,
- packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
+ discreteOps.readFromFile(f, beginTimeMillis);
}
}
}
@@ -251,15 +265,11 @@
@AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
@NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
int nDiscreteOps) {
- DiscreteOps discreteOps = new DiscreteOps();
- synchronized (mOnDiskLock) {
- writeAndClearAccessHistory();
- String[] opNamesFilter = dumpOp == OP_NONE ? EmptyArray.STRING
- : new String[]{AppOpsManager.opToPublicName(dumpOp)};
- readDiscreteOpsFromDisk(discreteOps, 0, Instant.now().toEpochMilli(), filter,
- uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter,
- OP_FLAGS_ALL);
- }
+ DiscreteOps discreteOps = getAndCacheDiscreteOps();
+ String[] opNamesFilter = dumpOp == OP_NONE ? null
+ : new String[]{AppOpsManager.opToPublicName(dumpOp)};
+ discreteOps.filter(0, Instant.now().toEpochMilli(), filter, uidFilter, packageNameFilter,
+ opNamesFilter, attributionTagFilter, OP_FLAGS_ALL);
discreteOps.dump(pw, sdf, date, prefix, nDiscreteOps);
}
@@ -270,7 +280,7 @@
if (!isDiscreteUid(uid)) {
return false;
}
- if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) {
+ if ((flags & (OP_FLAGS_DISCRETE)) == 0) {
return false;
}
return true;
@@ -298,6 +308,19 @@
mUids = new ArrayMap<>();
}
+ boolean isEmpty() {
+ return mUids.isEmpty();
+ }
+
+ void merge(DiscreteOps other) {
+ int nUids = other.mUids.size();
+ for (int i = 0; i < nUids; i++) {
+ int uid = other.mUids.keyAt(i);
+ DiscreteUidOps uidOps = other.mUids.valueAt(i);
+ getOrCreateDiscreteUidOps(uid).merge(uidOps);
+ }
+ }
+
void addDiscreteAccess(int op, int uid, @NonNull String packageName,
@Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
@AppOpsManager.UidState int uidState, long accessTime, long accessDuration) {
@@ -305,6 +328,25 @@
uidState, accessTime, accessDuration);
}
+ private void filter(long beginTimeMillis, long endTimeMillis,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
+ @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+ @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+ if ((filter & FILTER_BY_UID) != 0) {
+ ArrayMap<Integer, DiscreteUidOps> uids = new ArrayMap<>();
+ uids.put(uidFilter, getOrCreateDiscreteUidOps(uidFilter));
+ mUids = uids;
+ }
+ int nUids = mUids.size();
+ for (int i = nUids - 1; i >= 0; i--) {
+ mUids.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, packageNameFilter,
+ opNamesFilter, attributionTagFilter, flagsFilter);
+ if (mUids.valueAt(i).isEmpty()) {
+ mUids.removeAt(i);
+ }
+ }
+ }
+
private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) {
int nUids = mUids.size();
for (int i = 0; i < nUids; i++) {
@@ -353,14 +395,7 @@
return result;
}
- boolean isEmpty() {
- return mUids.isEmpty();
- }
-
- private void readFromFile(File f, long beginTimeMillis, long endTimeMillis,
- @AppOpsManager.HistoricalOpsRequestFilter int filter,
- int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
- @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+ private void readFromFile(File f, long beginTimeMillis) {
try {
FileInputStream stream = new FileInputStream(f);
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
@@ -377,12 +412,7 @@
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_UID.equals(parser.getName())) {
int uid = parser.getAttributeInt(null, ATTR_UID, -1);
- if ((filter & FILTER_BY_UID) != 0 && uid != uidFilter) {
- continue;
- }
- getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis,
- endTimeMillis, filter, packageNameFilter, opNamesFilter,
- attributionTagFilter, flagsFilter);
+ getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis);
}
}
} catch (Throwable t) {
@@ -400,6 +430,38 @@
mPackages = new ArrayMap<>();
}
+ boolean isEmpty() {
+ return mPackages.isEmpty();
+ }
+
+ void merge(DiscreteUidOps other) {
+ int nPackages = other.mPackages.size();
+ for (int i = 0; i < nPackages; i++) {
+ String packageName = other.mPackages.keyAt(i);
+ DiscretePackageOps p = other.mPackages.valueAt(i);
+ getOrCreateDiscretePackageOps(packageName).merge(p);
+ }
+ }
+
+ private void filter(long beginTimeMillis, long endTimeMillis,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter,
+ @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+ @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+ ArrayMap<String, DiscretePackageOps> packages = new ArrayMap<>();
+ packages.put(packageNameFilter, getOrCreateDiscretePackageOps(packageNameFilter));
+ mPackages = packages;
+ }
+ int nPackages = mPackages.size();
+ for (int i = nPackages - 1; i >= 0; i--) {
+ mPackages.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, opNamesFilter,
+ attributionTagFilter, flagsFilter);
+ if (mPackages.valueAt(i).isEmpty()) {
+ mPackages.removeAt(i);
+ }
+ }
+ }
+
void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
long accessTime, long accessDuration) {
@@ -445,22 +507,12 @@
}
}
- void deserialize(TypedXmlPullParser parser, long beginTimeMillis,
- long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter,
- @Nullable String packageNameFilter,
- @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
- @AppOpsManager.OpFlags int flagsFilter) throws Exception {
+ void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_PACKAGE.equals(parser.getName())) {
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
- if ((filter & FILTER_BY_PACKAGE_NAME) != 0
- && !packageName.equals(packageNameFilter)) {
- continue;
- }
- getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis,
- endTimeMillis, filter, opNamesFilter, attributionTagFilter,
- flagsFilter);
+ getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis);
}
}
}
@@ -473,6 +525,10 @@
mPackageOps = new ArrayMap<>();
}
+ boolean isEmpty() {
+ return mPackageOps.isEmpty();
+ }
+
void addDiscreteAccess(int op, @Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
long accessTime, long accessDuration) {
@@ -480,6 +536,35 @@
accessDuration);
}
+ void merge(DiscretePackageOps other) {
+ int nOps = other.mPackageOps.size();
+ for (int i = 0; i < nOps; i++) {
+ int opId = other.mPackageOps.keyAt(i);
+ DiscreteOp op = other.mPackageOps.valueAt(i);
+ getOrCreateDiscreteOp(opId).merge(op);
+ }
+ }
+
+ private void filter(long beginTimeMillis, long endTimeMillis,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter,
+ @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
+ @AppOpsManager.OpFlags int flagsFilter) {
+ int nOps = mPackageOps.size();
+ for (int i = nOps - 1; i >= 0; i--) {
+ int opId = mPackageOps.keyAt(i);
+ if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter,
+ AppOpsManager.opToPublicName(opId))) {
+ mPackageOps.removeAt(i);
+ continue;
+ }
+ mPackageOps.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter,
+ attributionTagFilter, flagsFilter);
+ if (mPackageOps.valueAt(i).isEmpty()) {
+ mPackageOps.removeAt(i);
+ }
+ }
+ }
+
private DiscreteOp getOrCreateDiscreteOp(int op) {
DiscreteOp result = mPackageOps.get(op);
if (result == null) {
@@ -519,20 +604,12 @@
}
}
- void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
- @AppOpsManager.HistoricalOpsRequestFilter int filter,
- @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
- @AppOpsManager.OpFlags int flagsFilter) throws Exception {
+ void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_OP.equals(parser.getName())) {
int op = parser.getAttributeInt(null, ATTR_OP_ID);
- if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter,
- AppOpsManager.opToPublicName(op))) {
- continue;
- }
- getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis, endTimeMillis,
- filter, attributionTagFilter, flagsFilter);
+ getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis);
}
}
}
@@ -545,31 +622,66 @@
mAttributedOps = new ArrayMap<>();
}
+ boolean isEmpty() {
+ return mAttributedOps.isEmpty();
+ }
+
+ void merge(DiscreteOp other) {
+ int nTags = other.mAttributedOps.size();
+ for (int i = 0; i < nTags; i++) {
+ String tag = other.mAttributedOps.keyAt(i);
+ List<DiscreteOpEvent> otherEvents = other.mAttributedOps.valueAt(i);
+ List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(tag);
+ mAttributedOps.put(tag, stableListMerge(events, otherEvents));
+ }
+ }
+
+ private void filter(long beginTimeMillis, long endTimeMillis,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter,
+ @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
+ if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0) {
+ ArrayMap<String, List<DiscreteOpEvent>> attributedOps = new ArrayMap<>();
+ attributedOps.put(attributionTagFilter,
+ getOrCreateDiscreteOpEventsList(attributionTagFilter));
+ mAttributedOps = attributedOps;
+ }
+
+ int nTags = mAttributedOps.size();
+ for (int i = nTags - 1; i >= 0; i--) {
+ String tag = mAttributedOps.keyAt(i);
+ List<DiscreteOpEvent> list = mAttributedOps.valueAt(i);
+ list = filterEventsList(list, beginTimeMillis, endTimeMillis, flagsFilter);
+ mAttributedOps.put(tag, list);
+ if (list.size() == 0) {
+ mAttributedOps.removeAt(i);
+ }
+ }
+ }
+
void addDiscreteAccess(@Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
long accessTime, long accessDuration) {
List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList(
attributionTag);
- accessTime = Instant.ofEpochMilli(accessTime).truncatedTo(
- ChronoUnit.MINUTES).toEpochMilli();
+ accessTime = accessTime / TIMELINE_QUANTIZATION * TIMELINE_QUANTIZATION;
int nAttributedOps = attributedOps.size();
- for (int i = nAttributedOps - 1; i >= 0; i--) {
- DiscreteOpEvent previousOp = attributedOps.get(i);
- if (i == nAttributedOps - 1 && previousOp.mNoteTime == accessTime
- && accessDuration > -1) {
- // existing event with updated duration
- attributedOps.remove(i);
- break;
- }
+ int i = nAttributedOps;
+ for (; i > 0; i--) {
+ DiscreteOpEvent previousOp = attributedOps.get(i - 1);
if (previousOp.mNoteTime < accessTime) {
break;
}
if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) {
- return;
+ if (accessDuration != previousOp.mNoteDuration
+ && accessDuration > TIMELINE_QUANTIZATION) {
+ break;
+ } else {
+ return;
+ }
}
}
- attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags));
+ attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags));
}
private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) {
@@ -633,18 +745,11 @@
}
}
- void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
- @AppOpsManager.HistoricalOpsRequestFilter int filter,
- @Nullable String attributionTagFilter,
- @AppOpsManager.OpFlags int flagsFilter) throws Exception {
+ void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_TAG.equals(parser.getName())) {
String attributionTag = parser.getAttributeValue(null, ATTR_TAG);
- if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !attributionTag.equals(
- attributionTagFilter)) {
- continue;
- }
List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(
attributionTag);
int innerDepth = parser.getDepth();
@@ -655,11 +760,7 @@
-1);
int uidState = parser.getAttributeInt(null, ATTR_UID_STATE);
int opFlags = parser.getAttributeInt(null, ATTR_FLAGS);
- if ((flagsFilter & opFlags) == 0) {
- continue;
- }
- if ((noteTime + noteDuration < beginTimeMillis
- && noteTime > endTimeMillis)) {
+ if (noteTime + noteDuration < beginTimeMillis) {
continue;
}
DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration,
@@ -715,5 +816,41 @@
out.attributeInt(null, ATTR_FLAGS, mOpFlag);
}
}
+
+ private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a,
+ List<DiscreteOpEvent> b) {
+ int nA = a.size();
+ int nB = b.size();
+ int i = 0;
+ int k = 0;
+ List<DiscreteOpEvent> result = new ArrayList<>(nA + nB);
+ while (i < nA || k < nB) {
+ if (i == nA) {
+ result.add(b.get(k++));
+ } else if (k == nB) {
+ result.add(a.get(i++));
+ } else if (a.get(i).mNoteTime < b.get(k).mNoteTime) {
+ result.add(a.get(i++));
+ } else {
+ result.add(b.get(k++));
+ }
+ }
+ return result;
+ }
+
+ private static List<DiscreteOpEvent> filterEventsList(List<DiscreteOpEvent> list,
+ long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flagsFilter) {
+ int n = list.size();
+ List<DiscreteOpEvent> result = new ArrayList<>(n);
+ for (int i = 0; i < n; i++) {
+ DiscreteOpEvent event = list.get(i);
+ if ((event.mOpFlag & flagsFilter) != 0
+ && event.mNoteTime + event.mNoteDuration > beginTimeMillis
+ && event.mNoteTime < endTimeMillis) {
+ result.add(event);
+ }
+ }
+ return result;
+ }
}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 22d628b..4435c47 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -532,7 +532,7 @@
System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
attributionTag, uidState, flags, increment);
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, op, attributionTag,
- flags, uidState, increment, eventStartTime);
+ flags, uidState, eventStartTime, increment);
}
}
}
@@ -795,7 +795,7 @@
private static boolean isApiEnabled() {
return Binder.getCallingUid() == Process.myUid()
|| DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_PERMISSIONS_HUB_ENABLED, false);
+ PROPERTY_PERMISSIONS_HUB_ENABLED, true);
}
private static final class Persistence {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index e4fc895..88dca0c 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -395,6 +395,9 @@
AudioSystem.STREAM_VOICE_CALL);
List<AudioDeviceAttributes> devices = AudioSystem.getDevicesForAttributes(attr);
if (devices.isEmpty()) {
+ if (mAudioService.isPlatformVoice()) {
+ Log.w(TAG, "getCommunicationDevice(): no device for phone strategy");
+ }
return null;
}
device = devices.get(0);
@@ -746,7 +749,7 @@
@GuardedBy("mDeviceStateLock")
private void dispatchCommunicationDevice() {
AudioDeviceInfo device = getCommunicationDevice();
- int portId = (getCommunicationDevice() == null) ? 0 : device.getId();
+ int portId = (device == null) ? 0 : device.getId();
if (portId == mCurCommunicationPortId) {
return;
}
@@ -1022,9 +1025,9 @@
pw.println("\n" + prefix + "mPreferredCommunicationDevice: "
+ mPreferredCommunicationDevice);
+ AudioDeviceInfo device = getCommunicationDevice();
pw.println(prefix + "Selected Communication Device: "
- + ((getCommunicationDevice() == null) ? "None"
- : new AudioDeviceAttributes(getCommunicationDevice())));
+ + ((device == null) ? "None" : new AudioDeviceAttributes(device)));
pw.println(prefix + "mCommunicationStrategyId: "
+ mCommunicationStrategyId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 68f10a5..2e6cfdc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -61,6 +61,8 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
@@ -246,7 +248,7 @@
// indicates whether the system maps all streams to a single stream.
private final boolean mIsSingleVolume;
- private boolean isPlatformVoice() {
+ /*package*/ boolean isPlatformVoice() {
return mPlatformType == AudioSystem.PLATFORM_VOICE;
}
@@ -520,6 +522,7 @@
/** Interface for UserManagerService. */
private final UserManagerInternal mUserManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
+ private final SensorPrivacyManagerInternal mSensorPrivacyManagerInternal;
private final UserRestrictionsListener mUserRestrictionsListener =
new AudioServiceUserRestrictionsListener();
@@ -720,9 +723,12 @@
private String mEnabledSurroundFormats;
private boolean mSurroundModeChanged;
+ private boolean mSupportsMicPrivacyToggle;
+
private boolean mMicMuteFromSwitch;
private boolean mMicMuteFromApi;
private boolean mMicMuteFromRestrictions;
+ private boolean mMicMuteFromPrivacyToggle;
// caches the value returned by AudioSystem.isMicrophoneMuted()
private boolean mMicMuteFromSystemCached;
@@ -822,6 +828,8 @@
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mSensorPrivacyManagerInternal =
+ LocalServices.getService(SensorPrivacyManagerInternal.class);
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
@@ -831,6 +839,9 @@
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
+ mSupportsMicPrivacyToggle = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_MICROPHONE_TOGGLE);
+
// Initialize volume
// Priority 1 - Android Property
// Priority 2 - Audio Policy Service
@@ -1106,6 +1117,16 @@
}
}
+ if (mSupportsMicPrivacyToggle) {
+ mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
+ SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
+ if (userId == getCurrentUserId()) {
+ mMicMuteFromPrivacyToggle = enabled;
+ setMicrophoneMuteNoCallerCheck(getCurrentUserId());
+ }
+ });
+ }
+
mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
sendMsg(mAudioHandler,
@@ -2237,7 +2258,7 @@
/** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
@NonNull AudioAttributes attributes) {
- enforceModifyAudioRoutingPermission();
+ enforceQueryStateOrModifyRoutingPermission();
return getDevicesForAttributesInt(attributes);
}
@@ -2879,6 +2900,16 @@
}
}
+ private void enforceQueryStateOrModifyRoutingPermission() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Missing MODIFY_AUDIO_ROUTING or QUERY_AUDIO_STATE permissions");
+ }
+ }
+
/** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
String callingPackage) {
@@ -3840,11 +3871,12 @@
* @return true if microphone is reported as muted by primary HAL
*/
public boolean isMicrophoneMuted() {
- return mMicMuteFromSystemCached;
+ return mMicMuteFromSystemCached && !mMicMuteFromPrivacyToggle;
}
private boolean isMicrophoneSupposedToBeMuted() {
- return mMicMuteFromSwitch || mMicMuteFromRestrictions || mMicMuteFromApi;
+ return mMicMuteFromSwitch || mMicMuteFromRestrictions || mMicMuteFromApi
+ || mMicMuteFromPrivacyToggle;
}
private void setMicrophoneMuteNoCallerCheck(int userId) {
@@ -5792,7 +5824,7 @@
public @AudioManager.DeviceVolumeBehavior
int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) {
// verify permissions
- enforceModifyAudioRoutingPermission();
+ enforceQueryStateOrModifyRoutingPermission();
// translate Java device type to native device type (for the devices masks for full / fixed)
final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
@@ -7474,6 +7506,13 @@
// the current audio focus owner is no longer valid
mMediaFocusControl.discardAudioFocusOwner();
+ if (mSupportsMicPrivacyToggle) {
+ mMicMuteFromPrivacyToggle = mSensorPrivacyManagerInternal
+ .isSensorPrivacyEnabled(getCurrentUserId(),
+ SensorPrivacyManager.Sensors.MICROPHONE);
+ setMicrophoneMuteNoCallerCheck(getCurrentUserId());
+ }
+
// load volume settings for new user
readAudioSettings(true /*userSwitch*/);
// preserve STREAM_MUSIC volume from one user to the next.
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 285f318..7296679 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -214,6 +214,13 @@
return;
}
+ if (promptInfo.containsTestConfigurations()) {
+ if (getContext().checkCallingOrSelfPermission(TEST_BIOMETRIC)
+ != PackageManager.PERMISSION_GRANTED) {
+ checkInternalPermission();
+ }
+ }
+
// Only allow internal clients to enable non-public options.
if (promptInfo.containsPrivateApiConfigurations()) {
checkInternalPermission();
@@ -340,6 +347,20 @@
}
@Override
+ public void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId,
+ int userId, byte[] hardwareAuthToken) throws RemoteException {
+ checkInternalPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mBiometricService.resetLockoutTimeBound(token, opPackageName, fromSensorId, userId,
+ hardwareAuthToken);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public CharSequence getButtonLabel(
int userId,
String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index a888209..63e7b4b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -754,7 +754,7 @@
}
}
- @Override
+ @Override // Binder call
public void invalidateAuthenticatorIds(int userId, int fromSensorId,
IInvalidationCallback callback) {
checkInternalPermission();
@@ -790,6 +790,45 @@
}
@Override // Binder call
+ public void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId,
+ int userId, byte[] hardwareAuthToken) {
+ checkInternalPermission();
+
+ // Check originating strength
+ if (!Utils.isAtLeastStrength(getSensorForId(fromSensorId).getCurrentStrength(),
+ Authenticators.BIOMETRIC_STRONG)) {
+ Slog.w(TAG, "Sensor: " + fromSensorId + " is does not meet the required strength to"
+ + " request resetLockout");
+ return;
+ }
+
+ // Request resetLockout for applicable sensors
+ for (BiometricSensor sensor : mSensors) {
+ if (sensor.id == fromSensorId) {
+ continue;
+ }
+ try {
+ final SensorPropertiesInternal props = sensor.impl
+ .getSensorProperties(getContext().getOpPackageName());
+ final boolean supportsChallengelessHat =
+ props.resetLockoutRequiresHardwareAuthToken
+ && !props.resetLockoutRequiresChallenge;
+ final boolean doesNotRequireHat = !props.resetLockoutRequiresHardwareAuthToken;
+
+ if (supportsChallengelessHat || doesNotRequireHat) {
+ Slog.d(TAG, "resetLockout from: " + fromSensorId
+ + ", for: " + sensor.id
+ + ", userId: " + userId);
+ sensor.impl.resetLockout(token, opPackageName, userId,
+ hardwareAuthToken);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+ }
+
+ @Override // Binder call
public int getCurrentStrength(int sensorId) {
checkInternalPermission();
@@ -1294,6 +1333,16 @@
}
}
+ @Nullable
+ private BiometricSensor getSensorForId(int sensorId) {
+ for (BiometricSensor sensor : mSensors) {
+ if (sensor.id == sensorId) {
+ return sensor;
+ }
+ }
+ return null;
+ }
+
private void dumpInternal(PrintWriter pw) {
pw.println("Sensors:");
for (BiometricSensor sensor : mSensors) {
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 6851d71..ef43bc5 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -23,6 +23,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -112,7 +113,8 @@
@AuthenticatorStatus int status = getStatusForBiometricAuthenticator(
devicePolicyManager, settingObserver, sensor, userId, opPackageName,
- checkDevicePolicyManager, requestedStrength, promptInfo.getSensorId());
+ checkDevicePolicyManager, requestedStrength,
+ promptInfo.getAllowedSensorIds());
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
@@ -142,9 +144,10 @@
DevicePolicyManager devicePolicyManager,
BiometricService.SettingObserver settingObserver,
BiometricSensor sensor, int userId, String opPackageName,
- boolean checkDevicePolicyManager, int requestedStrength, int requestedSensorId) {
+ boolean checkDevicePolicyManager, int requestedStrength,
+ @NonNull List<Integer> requestedSensorIds) {
- if (requestedSensorId != BiometricManager.SENSOR_ID_ANY && sensor.id != requestedSensorId) {
+ if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index d9e21a8..f4cb387 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -176,7 +176,8 @@
* @param requestedStrength the strength that it must meet
* @return true only if the sensor is at least as strong as the requested strength
*/
- public static boolean isAtLeastStrength(int sensorStrength, int requestedStrength) {
+ public static boolean isAtLeastStrength(@Authenticators.Types int sensorStrength,
+ @Authenticators.Types int requestedStrength) {
// Clear out any bits that are not reserved for biometric
sensorStrength &= Authenticators.BIOMETRIC_MIN_STRENGTH;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index b31a54b..9617bb0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -26,6 +26,7 @@
import android.content.pm.ApplicationInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
@@ -50,6 +51,7 @@
private final boolean mIsStrongBiometric;
private final boolean mRequireConfirmation;
private final ActivityTaskManager mActivityTaskManager;
+ private final BiometricManager mBiometricManager;
@Nullable private final TaskStackListener mTaskStackListener;
private final LockoutTracker mLockoutTracker;
private final boolean mIsRestricted;
@@ -73,6 +75,7 @@
mOperationId = operationId;
mRequireConfirmation = requireConfirmation;
mActivityTaskManager = ActivityTaskManager.getInstance();
+ mBiometricManager = context.getSystemService(BiometricManager.class);
mTaskStackListener = taskStackListener;
mLockoutTracker = lockoutTracker;
mIsRestricted = restricted;
@@ -207,6 +210,13 @@
for (int i = 0; i < hardwareAuthToken.size(); i++) {
byteToken[i] = hardwareAuthToken.get(i);
}
+
+ if (mIsStrongBiometric) {
+ mBiometricManager.resetLockoutTimeBound(getToken(),
+ getContext().getOpPackageName(),
+ getSensorId(), getTargetUserId(), byteToken);
+ }
+
if (isBiometricPrompt() && listener != null) {
// BiometricService will add the token to keystore
listener.onAuthenticationSucceeded(getSensorId(), identifier, byteToken,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 06b049b..2926260 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -104,4 +104,11 @@
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return mFaceService.getAuthenticatorId(mSensorId, callingUserId);
}
+
+ @Override
+ public void resetLockout(IBinder token, String opPackageName, int userId,
+ byte[] hardwareAuthToken) throws RemoteException {
+ mFaceService.resetLockout(token, mSensorId, userId, hardwareAuthToken,
+ opPackageName);
+ }
}
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 6dbd590..a74e2da 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
@@ -393,14 +393,13 @@
final IFaceServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
- if (provider == null) {
- Slog.w(TAG, "Null provider for removeAll");
- return;
+ for (ServiceProvider provider : mServiceProviders) {
+ List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
+ for (FaceSensorPropertiesInternal prop : props) {
+ provider.scheduleRemoveAll(prop.sensorId, token, userId, receiver,
+ opPackageName);
+ }
}
-
- provider.second.scheduleRemoveAll(provider.first, token, userId, receiver,
- opPackageName);
}
@Override // Binder call
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 1d8f210..ebf13e0 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
@@ -23,6 +23,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
@@ -134,10 +135,21 @@
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (android.hardware.biometrics.common.ComponentInfo info
+ : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+
final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
prop.commonProps.sensorId, prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser, false /* supportsFaceDetection */,
- prop.halControlsPreview);
+ prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+ false /* supportsFaceDetection */, prop.halControlsPreview,
+ false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp);
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 71bac57..ce728bc 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
@@ -62,6 +62,7 @@
// Nothing to do here
}
+ @Override
public void start(@NonNull Callback callback) {
super.start(callback);
startHalOperation();
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 3434acb..c7d2f0f 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
@@ -495,6 +495,15 @@
Slog.w(mTag, "setTestHalEnabled: " + enabled);
if (enabled != mTestHalEnabled) {
// The framework should retrieve a new session from the HAL.
+ try {
+ 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 */);
+ }
+ } catch (RemoteException e) {
+ Slog.e(mTag, "RemoteException", e);
+ }
mCurrentSession = null;
}
mTestHalEnabled = enabled;
@@ -519,6 +528,11 @@
proto.end(userToken);
}
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
+ mSensorProperties.resetLockoutRequiresHardwareAuthToken);
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
+ mSensorProperties.resetLockoutRequiresChallenge);
+
proto.end(sensorToken);
}
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 1b9bd7f..55e9a83 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
@@ -27,11 +27,13 @@
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.face.Face;
+import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
@@ -332,7 +334,9 @@
@NonNull BiometricScheduler scheduler) {
mSensorProperties = new FaceSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength),
- maxTemplatesAllowed, false /* supportsFaceDetect */, supportsSelfIllumination);
+ maxTemplatesAllowed, new ArrayList<ComponentInfoInternal>() /* componentInfo */,
+ FaceSensorProperties.TYPE_UNKNOWN, false /* supportsFaceDetect */,
+ supportsSelfIllumination, true /* resetLockoutRequiresChallenge */);
mContext = context;
mSensorId = sensorId;
mScheduler = scheduler;
@@ -797,6 +801,11 @@
proto.end(userToken);
}
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
+ mSensorProperties.resetLockoutRequiresHardwareAuthToken);
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
+ mSensorProperties.resetLockoutRequiresChallenge);
+
proto.end(sensorToken);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index 4cdb68d..84aa6d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
@@ -23,11 +23,11 @@
import android.hardware.biometrics.face.V1_0.OptionalBool;
import android.hardware.biometrics.face.V1_0.OptionalUint64;
import android.hardware.biometrics.face.V1_0.Status;
-import android.os.NativeHandle;
import android.os.RemoteException;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.Arrays;
public class TestHal extends IBiometricsFace.Stub {
private static final String TAG = "face.hidl.TestHal";
@@ -107,8 +107,12 @@
}
@Override
- public int remove(int faceId) {
+ public int remove(int faceId) throws RemoteException {
Slog.w(TAG, "remove");
+ if (mCallback != null) {
+ mCallback.onRemoved(0 /* deviceId */, new ArrayList<Integer>(Arrays.asList(faceId)),
+ 0 /* userId */);
+ }
return 0;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 32e9409..9e82ffc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -105,4 +105,11 @@
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return mFingerprintService.getAuthenticatorId(mSensorId, callingUserId);
}
+
+ @Override
+ public void resetLockout(IBinder token, String opPackageName, int userId,
+ byte[] hardwareAuthToken) throws RemoteException {
+ mFingerprintService.resetLockout(token, mSensorId, userId, hardwareAuthToken,
+ opPackageName);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 396dd5f..aa5afb7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -315,7 +315,8 @@
Slog.e(TAG, "Remote exception in negative button onClick()", e);
}
})
- .setSensorId(props.sensorId)
+ .setAllowedSensorIds(new ArrayList<>(
+ Collections.singletonList(props.sensorId)))
.build();
final BiometricPrompt.AuthenticationCallback promptCallback =
@@ -506,13 +507,13 @@
final IFingerprintServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
- if (provider == null) {
- Slog.w(TAG, "Null provider for removeAll");
- return;
+ for (ServiceProvider provider : mServiceProviders) {
+ List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties();
+ for (FingerprintSensorPropertiesInternal prop : props) {
+ provider.scheduleRemoveAll(prop.sensorId, token, receiver, userId,
+ opPackageName);
+ }
}
- provider.second.scheduleRemoveAll(provider.first, token, receiver, userId,
- opPackageName);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index dfec2e3..0d50499 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -67,7 +67,13 @@
@NonNull
List<FingerprintSensorPropertiesInternal> getSensorProperties();
- @NonNull
+ /**
+ * Returns the internal properties of the specified sensor, if owned by this provider.
+ *
+ * @param sensorId The ID of a fingerprint sensor, or -1 for any sensor.
+ * @return An object representing the internal properties of the specified sensor.
+ */
+ @Nullable
FingerprintSensorPropertiesInternal getSensorProperties(int sensorId);
void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken);
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 598cc89..2c85dc9 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
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.fingerprint.FingerprintManager.SENSOR_ID_ANY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -23,6 +25,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
@@ -136,10 +139,21 @@
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (android.hardware.biometrics.common.ComponentInfo info
+ : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+
final FingerprintSensorPropertiesInternal internalProp =
new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
prop.commonProps.sensorStrength,
prop.commonProps.maxEnrollmentsPerUser,
+ componentInfo,
prop.sensorType,
true /* resetLockoutRequiresHardwareAuthToken */);
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
@@ -240,10 +254,17 @@
return props;
}
- @NonNull
+ @Nullable
@Override
public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId) {
- return mSensors.get(sensorId).getSensorProperties();
+ if (mSensors.size() == 0) {
+ return null;
+ } else if (sensorId == SENSOR_ID_ANY) {
+ return mSensors.valueAt(0).getSensorProperties();
+ } else {
+ final Sensor sensor = mSensors.get(sensorId);
+ return sensor != null ? sensor.getSensorProperties() : null;
+ }
}
private void scheduleLoadAuthenticatorIds(int sensorId) {
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 ddcfcad..adffba2 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
@@ -63,6 +63,12 @@
}
@Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
protected void startHalOperation() {
try {
getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken);
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 a98e7db..b9dee7d 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
@@ -475,6 +475,15 @@
Slog.w(mTag, "setTestHalEnabled: " + enabled);
if (enabled != mTestHalEnabled) {
// The framework should retrieve a new session from the HAL.
+ try {
+ 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 */);
+ }
+ } catch (RemoteException e) {
+ Slog.e(mTag, "RemoteException", e);
+ }
mCurrentSession = null;
}
mTestHalEnabled = enabled;
@@ -499,6 +508,11 @@
proto.end(userToken);
}
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
+ mSensorProperties.resetLockoutRequiresHardwareAuthToken);
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
+ mSensorProperties.resetLockoutRequiresChallenge);
+
proto.end(sensorToken);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 6e22a79..f112549 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -28,6 +28,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
@@ -347,14 +348,16 @@
final @FingerprintSensorProperties.SensorType int sensorType =
mIsUdfps ? FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
: FingerprintSensorProperties.TYPE_REAR;
- // resetLockout is controlled by the framework, so hardwareAuthToken is not required
+ // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT
+ // cannot be checked
final boolean resetLockoutRequiresHardwareAuthToken = false;
final int maxEnrollmentsPerUser = mContext.getResources()
.getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
mSensorProperties = new FingerprintSensorPropertiesInternal(context, sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
- sensorType, resetLockoutRequiresHardwareAuthToken);
+ new ArrayList<ComponentInfoInternal>() /* componentInfo */, sensorType,
+ resetLockoutRequiresHardwareAuthToken);
}
public static Fingerprint21 newInstance(@NonNull Context context, int sensorId, int strength,
@@ -515,7 +518,7 @@
return properties;
}
- @NonNull
+ @Nullable
@Override
public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId) {
return mSensorProperties;
@@ -526,7 +529,9 @@
// Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
// thread.
mHandler.post(() -> {
- mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
+ final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
+ userId, mContext.getOpPackageName(), sensorId, mLockoutTracker);
+ mScheduler.scheduleClientMonitor(client);
});
}
@@ -758,6 +763,11 @@
proto.end(userToken);
}
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
+ mSensorProperties.resetLockoutRequiresHardwareAuthToken);
+ proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
+ mSensorProperties.resetLockoutRequiresChallenge);
+
proto.end(sensorToken);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 2394a70..90c4b4a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -421,6 +422,7 @@
.getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
mSensorProperties = new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxTemplatesAllowed,
+ new ArrayList<ComponentInfoInternal>() /* componentInfo */,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
resetLockoutRequiresHardwareAuthToken);
mMockHalResultController = controller;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
new file mode 100644
index 0000000..a39f4f8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
@@ -0,0 +1,54 @@
+/*
+ * 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.hidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+
+import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+
+/**
+ * Clears lockout, which is handled in the framework (and not the HAL) for the
+ * IBiometricsFingerprint@2.1 interface.
+ */
+public class FingerprintResetLockoutClient extends BaseClientMonitor {
+
+ @NonNull final LockoutFrameworkImpl mLockoutTracker;
+
+ public FingerprintResetLockoutClient(@NonNull Context context, int userId,
+ @NonNull String owner, int sensorId, @NonNull LockoutFrameworkImpl lockoutTracker) {
+ super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
+ sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mLockoutTracker = lockoutTracker;
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+ getTargetUserId());
+ callback.onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public int getProtoEnum() {
+ return BiometricsProto.CM_RESET_LOCKOUT;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
index 14fdb50..129f6a6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -93,8 +93,11 @@
}
@Override
- public int remove(int gid, int fid) {
+ public int remove(int gid, int fid) throws RemoteException {
Slog.w(TAG, "Remove");
+ if (mCallback != null) {
+ mCallback.onRemoved(0 /* deviceId */, fid, gid, 0 /* remaining */);
+ }
return 0;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index f44e069..95915b7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -95,4 +95,9 @@
public long getAuthenticatorId(int callingUserId) throws RemoteException {
return 0;
}
+
+ @Override
+ public void resetLockout(IBinder token, String opPackageName, int userId,
+ byte[] hardwareAuthToken) throws RemoteException {
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 6712c54..71565301 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -43,6 +44,8 @@
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IUserManager;
import android.os.Parcel;
@@ -55,10 +58,15 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.autofill.AutofillManagerInternal;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
import android.widget.Toast;
import com.android.internal.R;
@@ -72,6 +80,7 @@
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -87,21 +96,20 @@
private static final String PIPE_NAME = "pipe:clipboard";
private static final String PIPE_DEVICE = "/dev/qemu_pipe";
+ private static byte[] createOpenHandshake() {
+ // String.getBytes doesn't include the null terminator,
+ // but the QEMU pipe device requires the pipe service name
+ // to be null-terminated.
+
+ final byte[] bits = Arrays.copyOf(PIPE_NAME.getBytes(), PIPE_NAME.length() + 1);
+ bits[PIPE_NAME.length()] = 0;
+ return bits;
+ }
+
private void openPipe() {
try {
- // String.getBytes doesn't include the null terminator,
- // but the QEMU pipe device requires the pipe service name
- // to be null-terminated.
- byte[] b = new byte[PIPE_NAME.length() + 1];
- b[PIPE_NAME.length()] = 0;
- System.arraycopy(
- PIPE_NAME.getBytes(),
- 0,
- b,
- 0,
- PIPE_NAME.length());
mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
- mPipe.write(b);
+ mPipe.write(createOpenHandshake());
} catch (IOException e) {
try {
if (mPipe != null) mPipe.close();
@@ -165,11 +173,13 @@
private static final String TAG = "ClipboardService";
private static final boolean IS_EMULATOR =
- SystemProperties.getBoolean("ro.kernel.qemu", false);
+ SystemProperties.getBoolean("ro.boot.qemu", false);
// DeviceConfig properties
private static final String PROPERTY_SHOW_ACCESS_NOTIFICATIONS = "show_access_notifications";
private static final boolean DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true;
+ private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
+ private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;
private final ActivityManagerInternal mAmInternal;
private final IUriGrantsManager mUgm;
@@ -180,8 +190,10 @@
private final AppOpsManager mAppOps;
private final ContentCaptureManagerInternal mContentCaptureInternal;
private final AutofillManagerInternal mAutofillInternal;
+ private final TextClassificationManager mTextClassificationManager;
private final IBinder mPermissionOwner;
private final HostClipboardMonitor mHostClipboardMonitor;
+ private final Handler mWorkerHandler;
@GuardedBy("mLock")
private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
@@ -189,6 +201,9 @@
@GuardedBy("mLock")
private boolean mShowAccessNotifications = DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
+ @GuardedBy("mLock")
+ private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;
+
private final Object mLock = new Object();
/**
@@ -206,6 +221,8 @@
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class);
mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class);
+ mTextClassificationManager = (TextClassificationManager)
+ getContext().getSystemService(Context.TEXT_CLASSIFICATION_SERVICE);
final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
mPermissionOwner = permOwner;
if (IS_EMULATOR) {
@@ -232,6 +249,10 @@
updateConfig();
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
getContext().getMainExecutor(), properties -> updateConfig());
+
+ HandlerThread workerThread = new HandlerThread(TAG);
+ workerThread.start();
+ mWorkerHandler = workerThread.getThreadHandler();
}
@Override
@@ -250,6 +271,8 @@
synchronized (mLock) {
mShowAccessNotifications = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
PROPERTY_SHOW_ACCESS_NOTIFICATIONS, DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
+ mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
+ PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
}
}
@@ -592,6 +615,10 @@
}
}
+ if (clip != null) {
+ startClassificationLocked(clip);
+ }
+
// Update this user
final int userId = UserHandle.getUserId(uid);
setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);
@@ -691,6 +718,68 @@
}
}
+ @GuardedBy("mLock")
+ private void startClassificationLocked(@NonNull ClipData clip) {
+ TextClassifier classifier;
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ classifier = mTextClassificationManager.createTextClassificationSession(
+ new TextClassificationContext.Builder(
+ getContext().getPackageName(),
+ TextClassifier.WIDGET_TYPE_CLIPBOARD
+ ).build()
+ );
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ if (clip.getItemCount() == 0) {
+ clip.getDescription().setClassificationStatus(
+ ClipDescription.CLASSIFICATION_NOT_PERFORMED);
+ return;
+ }
+ CharSequence text = clip.getItemAt(0).getText();
+ if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength
+ || text.length() > classifier.getMaxGenerateLinksTextLength()) {
+ clip.getDescription().setClassificationStatus(
+ ClipDescription.CLASSIFICATION_NOT_PERFORMED);
+ return;
+ }
+
+ mWorkerHandler.post(() -> doClassification(text, clip, classifier));
+ }
+
+ @WorkerThread
+ private void doClassification(
+ CharSequence text, ClipData clip, TextClassifier classifier) {
+ TextLinks.Request request = new TextLinks.Request.Builder(text).build();
+ TextLinks links;
+ try {
+ links = classifier.generateLinks(request);
+ } finally {
+ classifier.destroy();
+ }
+
+ // Find the highest confidence for each entity in the text.
+ ArrayMap<String, Float> confidences = new ArrayMap<>();
+ for (TextLinks.TextLink link : links.getLinks()) {
+ for (int i = 0; i < link.getEntityCount(); i++) {
+ String entity = link.getEntity(i);
+ float conf = link.getConfidenceScore(entity);
+ if (conf > confidences.getOrDefault(entity, 0f)) {
+ confidences.put(entity, conf);
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ clip.getDescription().setConfidenceScores(confidences);
+ if (!links.getLinks().isEmpty()) {
+ clip.getItemAt(0).setTextLinks(links);
+ }
+ }
+ }
+
private boolean isDeviceLocked(@UserIdInt int userId) {
final long token = Binder.clearCallingIdentity();
try {
@@ -922,6 +1011,10 @@
if (!mShowAccessNotifications) {
return;
}
+ if (Settings.Secure.getInt(getContext().getContentResolver(),
+ Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, 1) == 0) {
+ return;
+ }
// Don't notify if the app accessing the clipboard is the same as the current owner.
if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
return;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index 4f6b530..ffeb77d 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -16,23 +16,23 @@
package com.android.server.connectivity;
-import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MAX_SAMPLES;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
+import static android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
-import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
-import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
-import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
-import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
-import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.net.ConnectivityManager;
import android.net.IDnsResolver;
import android.net.InetAddresses;
import android.net.LinkProperties;
@@ -127,13 +127,17 @@
private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
- public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
- final String mode = getPrivateDnsMode(cr);
+ /**
+ * Get PrivateDnsConfig.
+ */
+ public static PrivateDnsConfig getPrivateDnsConfig(Context context) {
+ final String mode = ConnectivityManager.getPrivateDnsMode(context);
final boolean useTls = !TextUtils.isEmpty(mode) && !PRIVATE_DNS_MODE_OFF.equals(mode);
if (PRIVATE_DNS_MODE_PROVIDER_HOSTNAME.equals(mode)) {
- final String specifier = getStringSetting(cr, PRIVATE_DNS_SPECIFIER);
+ final String specifier = getStringSetting(context.getContentResolver(),
+ PRIVATE_DNS_SPECIFIER);
return new PrivateDnsConfig(specifier, null);
}
@@ -268,7 +272,7 @@
}
public PrivateDnsConfig getPrivateDnsConfig() {
- return getPrivateDnsConfig(mContentResolver);
+ return getPrivateDnsConfig(mContext);
}
public void removeNetwork(Network network) {
@@ -479,13 +483,6 @@
return result;
}
- private static String getPrivateDnsMode(ContentResolver cr) {
- String mode = getStringSetting(cr, PRIVATE_DNS_MODE);
- if (TextUtils.isEmpty(mode)) mode = getStringSetting(cr, PRIVATE_DNS_DEFAULT_MODE);
- if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
- return mode;
- }
-
private static String getStringSetting(ContentResolver cr, String which) {
return Settings.Global.getString(cr, which);
}
diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java
new file mode 100644
index 0000000..ac5988a
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/FullScore.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.connectivity;
+
+import android.annotation.NonNull;
+import android.net.NetworkScore;
+
+/**
+ * This class represents how desirable a network is.
+ *
+ * FullScore is very similar to NetworkScore, but it contains the bits that are managed
+ * by ConnectivityService. This provides static guarantee that all users must know whether
+ * they are handling a score that had the CS-managed bits set.
+ */
+public class FullScore {
+ // This will be removed soon. Do *NOT* depend on it for any new code that is not part of
+ // a migration.
+ private final int mLegacyInt;
+
+ // Agent-managed policies are in NetworkScore. They start from 1.
+ // CS-managed policies
+ // This network is validated. CS-managed because the source of truth is in NetworkCapabilities.
+ public static final int POLICY_IS_VALIDATED = 63;
+
+ // Bitmask of all the policies applied to this score.
+ private final long mPolicies;
+
+ FullScore(final int legacyInt, final long policies) {
+ mLegacyInt = legacyInt;
+ mPolicies = policies;
+ }
+
+ /**
+ * Make a FullScore from a NetworkScore
+ */
+ public static FullScore withPolicy(@NonNull final NetworkScore originalScore,
+ final boolean isValidated) {
+ return new FullScore(originalScore.getLegacyInt(),
+ isValidated ? 1L << POLICY_IS_VALIDATED : 0L);
+ }
+
+ /**
+ * For backward compatibility, get the legacy int.
+ * This will be removed before S is published.
+ */
+ public int getLegacyInt() {
+ return mLegacyInt;
+ }
+
+ @Override
+ public String toString() {
+ return "Score(" + mLegacyInt + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
index ef76734..a25b89a 100644
--- a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
+++ b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
@@ -17,7 +17,6 @@
package com.android.server.connectivity;
import android.os.SystemProperties;
-import android.sysprop.NetworkProperties;
public class MockableSystemProperties {
@@ -32,10 +31,4 @@
public boolean getBoolean(String key, boolean def) {
return SystemProperties.getBoolean(key, def);
}
- /**
- * Set net.tcp_def_init_rwnd to the tcp initial receive window size.
- */
- public void setTcpInitRwnd(int value) {
- NetworkProperties.tcp_init_rwnd(value);
- }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 803cc9d..372601f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.transportNamesOf;
import android.annotation.NonNull;
@@ -35,6 +36,7 @@
import android.net.NetworkInfo;
import android.net.NetworkMonitorManager;
import android.net.NetworkRequest;
+import android.net.NetworkScore;
import android.net.NetworkStateSnapshot;
import android.net.QosCallbackException;
import android.net.QosFilter;
@@ -302,8 +304,9 @@
// validated).
private boolean mInactive;
- // This represents the quality of the network with no clear scale.
- private int mScore;
+ // This represents the quality of the network. As opposed to NetworkScore, FullScore includes
+ // the ConnectivityService-managed bits.
+ private FullScore mScore;
// The list of NetworkRequests being satisfied by this Network.
private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
@@ -338,7 +341,8 @@
private final QosCallbackTracker mQosCallbackTracker;
public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info,
- @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, int score, Context context,
+ @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc,
+ @NonNull NetworkScore score, Context context,
Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid,
QosCallbackTracker qosCallbackTracker, ConnectivityService.Dependencies deps) {
@@ -354,7 +358,7 @@
networkInfo = info;
linkProperties = lp;
networkCapabilities = nc;
- mScore = score;
+ mScore = mixInScore(score, nc);
clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
mConnService = connService;
mContext = context;
@@ -603,9 +607,9 @@
}
@Override
- public void sendScore(int score) {
- mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_SCORE_CHANGED, score, 0,
- new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
+ public void sendScore(@NonNull final NetworkScore score) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_SCORE_CHANGED,
+ new Pair<>(NetworkAgentInfo.this, score)).sendToTarget();
}
@Override
@@ -717,6 +721,7 @@
break;
case LISTEN:
+ case LISTEN_FOR_BEST:
case TRACK_DEFAULT:
case TRACK_SYSTEM_DEFAULT:
break;
@@ -857,7 +862,7 @@
return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
}
- int score = mScore;
+ int score = mScore.getLegacyInt();
if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) {
score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
}
@@ -886,8 +891,13 @@
return getCurrentScore(true);
}
- public void setScore(final int score) {
- mScore = score;
+ public void setScore(final NetworkScore score) {
+ mScore = mixInScore(score, networkCapabilities);
+ }
+
+ private static FullScore mixInScore(@NonNull final NetworkScore score,
+ @NonNull final NetworkCapabilities caps) {
+ return FullScore.withPolicy(score, caps.hasCapability(NET_CAPABILITY_VALIDATED));
}
/**
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 5e6b9f3..2e51be3 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -36,7 +36,7 @@
import android.util.Pair;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.TrafficStatsConstants;
+import com.android.net.module.util.NetworkStackConstants;
import libcore.io.IoUtils;
@@ -446,7 +446,7 @@
int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
throws ErrnoException, IOException {
final int oldTag = TrafficStats.getAndSetThreadStatsTag(
- TrafficStatsConstants.TAG_SYSTEM_PROBE);
+ NetworkStackConstants.TAG_SYSTEM_PROBE);
try {
mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
} finally {
@@ -745,7 +745,7 @@
if (ensureMeasurementNecessary()) return;
// No need to restore the tag, since this thread is only used for this measurement.
- TrafficStats.getAndSetThreadStatsTag(TrafficStatsConstants.TAG_SYSTEM_PROBE);
+ TrafficStats.getAndSetThreadStatsTag(NetworkStackConstants.TAG_SYSTEM_PROBE);
try (SSLSocket sslSocket = setupSSLSocket()) {
sendDoTProbe(sslSocket);
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 508739f..181a10d 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -156,7 +156,7 @@
final String tag = tagFor(id);
final int eventId = notifyType.eventId;
final int transportType;
- final String name;
+ final CharSequence name;
if (nai != null) {
transportType = approximateTransportType(nai);
final String extraInfo = nai.networkInfo.getExtraInfo();
diff --git a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java b/services/core/java/com/android/server/connectivity/PacProxyService.java
similarity index 75%
rename from services/core/java/com/android/server/connectivity/PacProxyInstaller.java
rename to services/core/java/com/android/server/connectivity/PacProxyService.java
index aadaf4d..d23b488 100644
--- a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
+++ b/services/core/java/com/android/server/connectivity/PacProxyService.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -26,12 +28,16 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.net.IPacProxyInstalledListener;
+import android.net.IPacProxyManager;
import android.net.ProxyInfo;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -44,6 +50,7 @@
import com.android.net.IProxyCallback;
import com.android.net.IProxyPortListener;
import com.android.net.IProxyService;
+import com.android.net.module.util.PermissionUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -53,7 +60,7 @@
/**
* @hide
*/
-public class PacProxyInstaller {
+public class PacProxyService extends IPacProxyManager.Stub {
private static final String PAC_PACKAGE = "com.android.pacprocessor";
private static final String PAC_SERVICE = "com.android.pacprocessor.PacService";
private static final String PAC_SERVICE_NAME = "com.android.net.IProxyService";
@@ -61,7 +68,7 @@
private static final String PROXY_PACKAGE = "com.android.proxyhandler";
private static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService";
- private static final String TAG = "PacProxyInstaller";
+ private static final String TAG = "PacProxyService";
private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH";
@@ -71,10 +78,6 @@
private static final int DELAY_LONG = 4;
private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
- // Return values for #setCurrentProxyScriptUrl
- public static final boolean DONT_SEND_BROADCAST = false;
- public static final boolean DO_SEND_BROADCAST = true;
-
private String mCurrentPac;
@GuardedBy("mProxyLock")
private volatile Uri mPacUrl = Uri.EMPTY;
@@ -93,8 +96,8 @@
private volatile boolean mHasSentBroadcast;
private volatile boolean mHasDownloaded;
- private Handler mConnectivityHandler;
- private final int mProxyMessage;
+ private final RemoteCallbackList<IPacProxyInstalledListener>
+ mCallbacks = new RemoteCallbackList<>();
/**
* Used for locking when setting mProxyService and all references to mCurrentPac.
@@ -102,6 +105,13 @@
private final Object mProxyLock = new Object();
/**
+ * Lock ensuring consistency between the values of mHasSentBroadcast, mHasDownloaded, the
+ * last URL and port, and the broadcast message being sent with the correct arguments.
+ * TODO : this should probably protect all instances of these variables
+ */
+ private final Object mBroadcastStateLock = new Object();
+
+ /**
* Runnable to download PAC script.
* The behavior relies on the assumption it always runs on mNetThread to guarantee that the
* latest data fetched from mPacUrl is stored in mProxyService.
@@ -146,10 +156,10 @@
}
}
- public PacProxyInstaller(Context context, Handler handler, int proxyMessage) {
+ public PacProxyService(@NonNull Context context) {
mContext = context;
mLastPort = -1;
- final HandlerThread netThread = new HandlerThread("android.pacproxyinstaller",
+ final HandlerThread netThread = new HandlerThread("android.pacproxyservice",
android.os.Process.THREAD_PRIORITY_DEFAULT);
netThread.start();
mNetThreadHandler = new Handler(netThread.getLooper());
@@ -158,8 +168,6 @@
context, 0, new Intent(ACTION_PAC_REFRESH), PendingIntent.FLAG_IMMUTABLE);
context.registerReceiver(new PacRefreshIntentReceiver(),
new IntentFilter(ACTION_PAC_REFRESH));
- mConnectivityHandler = handler;
- mProxyMessage = proxyMessage;
}
private AlarmManager getAlarmManager() {
@@ -169,38 +177,52 @@
return mAlarmManager;
}
+ @Override
+ public void addListener(IPacProxyInstalledListener listener) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ mCallbacks.register(listener);
+ }
+
+ @Override
+ public void removeListener(IPacProxyInstalledListener listener) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ mCallbacks.unregister(listener);
+ }
+
/**
* Updates the PAC Proxy Installer with current Proxy information. This is called by
- * the ProxyTracker directly before a broadcast takes place to allow
- * the PacProxyInstaller to indicate that the broadcast should not be sent and the
- * PacProxyInstaller will trigger a new broadcast when it is ready.
+ * the ProxyTracker through PacProxyManager before a broadcast takes place to allow
+ * the PacProxyService to indicate that the broadcast should not be sent and the
+ * PacProxyService will trigger a new broadcast when it is ready.
*
* @param proxy Proxy information that is about to be broadcast.
- * @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST
*/
- public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
- if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
- if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
- // Allow to send broadcast, nothing to do.
- return DO_SEND_BROADCAST;
- }
- mPacUrl = proxy.getPacFileUrl();
- mCurrentDelay = DELAY_1;
- mHasSentBroadcast = false;
- mHasDownloaded = false;
- getAlarmManager().cancel(mPacRefreshIntent);
- bind();
- return DONT_SEND_BROADCAST;
- } else {
- getAlarmManager().cancel(mPacRefreshIntent);
- synchronized (mProxyLock) {
- mPacUrl = Uri.EMPTY;
- mCurrentPac = null;
- if (mProxyService != null) {
- unbind();
+ @Override
+ public void setCurrentProxyScriptUrl(@Nullable ProxyInfo proxy) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+
+ synchronized (mBroadcastStateLock) {
+ if (proxy != null && !Uri.EMPTY.equals(proxy.getPacFileUrl())) {
+ if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) return;
+ mPacUrl = proxy.getPacFileUrl();
+ mCurrentDelay = DELAY_1;
+ mHasSentBroadcast = false;
+ mHasDownloaded = false;
+ getAlarmManager().cancel(mPacRefreshIntent);
+ bind();
+ } else {
+ getAlarmManager().cancel(mPacRefreshIntent);
+ synchronized (mProxyLock) {
+ mPacUrl = Uri.EMPTY;
+ mCurrentPac = null;
+ if (mProxyService != null) {
+ unbind();
+ }
}
}
- return DO_SEND_BROADCAST;
}
}
@@ -275,6 +297,7 @@
getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent);
}
+ @GuardedBy("mProxyLock")
private void setCurrentProxyScript(String script) {
if (mProxyService == null) {
Log.e(TAG, "setCurrentProxyScript: no proxy service");
@@ -347,6 +370,9 @@
public void setProxyPort(int port) {
if (mLastPort != -1) {
// Always need to send if port changed
+ // TODO: Here lacks synchronization because this write cannot
+ // guarantee that it's visible from sendProxyIfNeeded() when
+ // it's called by a Runnable which is post by mNetThread.
mHasSentBroadcast = false;
}
mLastPort = port;
@@ -365,8 +391,9 @@
}
}
};
- mContext.bindService(intent, mProxyConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
+ mContext.bindService(intent,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
+ new HandlerExecutor(mNetThreadHandler), mProxyConnection);
}
private void unbind() {
@@ -383,16 +410,28 @@
}
private void sendPacBroadcast(ProxyInfo proxy) {
- mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
+ final int length = mCallbacks.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final IPacProxyInstalledListener listener = mCallbacks.getBroadcastItem(i);
+ if (listener != null) {
+ try {
+ listener.onPacProxyInstalled(null /* network */, proxy);
+ } catch (RemoteException ignored) { }
+ }
+ }
+ mCallbacks.finishBroadcast();
}
- private synchronized void sendProxyIfNeeded() {
- if (!mHasDownloaded || (mLastPort == -1)) {
- return;
- }
- if (!mHasSentBroadcast) {
- sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
- mHasSentBroadcast = true;
+ // This method must be called on mNetThreadHandler.
+ private void sendProxyIfNeeded() {
+ synchronized (mBroadcastStateLock) {
+ if (!mHasDownloaded || (mLastPort == -1)) {
+ return;
+ }
+ if (!mHasSentBroadcast) {
+ sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
+ mHasSentBroadcast = true;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java
new file mode 100644
index 0000000..dd2815d
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/ProfileNetworkPreferences.java
@@ -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.server.connectivity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.os.UserHandle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A data class containing all the per-profile network preferences.
+ *
+ * A given profile can only have one preference.
+ */
+public class ProfileNetworkPreferences {
+ /**
+ * A single preference, as it applies to a given user profile.
+ */
+ public static class Preference {
+ @NonNull public final UserHandle user;
+ // Capabilities are only null when sending an object to remove the setting for a user
+ @Nullable public final NetworkCapabilities capabilities;
+
+ public Preference(@NonNull final UserHandle user,
+ @Nullable final NetworkCapabilities capabilities) {
+ this.user = user;
+ this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
+ }
+
+ /** toString */
+ public String toString() {
+ return "[ProfileNetworkPreference user=" + user + " caps=" + capabilities + "]";
+ }
+ }
+
+ @NonNull public final List<Preference> preferences;
+
+ public ProfileNetworkPreferences() {
+ preferences = Collections.EMPTY_LIST;
+ }
+
+ private ProfileNetworkPreferences(@NonNull final List<Preference> list) {
+ preferences = Collections.unmodifiableList(list);
+ }
+
+ /**
+ * Returns a new object consisting of this object plus the passed preference.
+ *
+ * If a preference already exists for the same user, it will be replaced by the passed
+ * preference. Passing a Preference object containing a null capabilities object is equivalent
+ * to (and indeed, implemented as) removing the preference for this user.
+ */
+ public ProfileNetworkPreferences plus(@NonNull final Preference pref) {
+ final ArrayList<Preference> newPrefs = new ArrayList<>();
+ for (final Preference existingPref : preferences) {
+ if (!existingPref.user.equals(pref.user)) {
+ newPrefs.add(existingPref);
+ }
+ }
+ if (null != pref.capabilities) {
+ newPrefs.add(pref);
+ }
+ return new ProfileNetworkPreferences(newPrefs);
+ }
+
+ public boolean isEmpty() {
+ return preferences.isEmpty();
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index d83ff83..f883307 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -16,10 +16,10 @@
package com.android.server.connectivity;
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST;
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_HOST;
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_PAC;
-import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_PORT;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_EXCLUSION_LIST;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_HOST;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_PAC;
+import static android.net.ConnectivitySettingsManager.GLOBAL_HTTP_PROXY_PORT;
import static android.provider.Settings.Global.HTTP_PROXY;
import android.annotation.NonNull;
@@ -27,15 +27,19 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.net.Network;
+import android.net.PacProxyManager;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.net.module.util.ProxyUtils;
@@ -67,7 +71,7 @@
// is not set. Individual networks have their own settings that override this. This member
// is set through setDefaultProxy, which is called when the default network changes proxies
// in its LinkProperties, or when ConnectivityService switches to a new default network, or
- // when PacProxyInstaller resolves the proxy.
+ // when PacProxyService resolves the proxy.
@Nullable
@GuardedBy("mProxyLock")
private volatile ProxyInfo mDefaultProxy = null;
@@ -77,16 +81,31 @@
private final Handler mConnectivityServiceHandler;
- // The object responsible for Proxy Auto Configuration (PAC).
- @NonNull
- private final PacProxyInstaller mPacProxyInstaller;
+ private final PacProxyManager mPacProxyManager;
+
+ private class PacProxyInstalledListener implements PacProxyManager.PacProxyInstalledListener {
+ private final int mEvent;
+
+ PacProxyInstalledListener(int event) {
+ mEvent = event;
+ }
+
+ public void onPacProxyInstalled(@Nullable Network network, @NonNull ProxyInfo proxy) {
+ mConnectivityServiceHandler
+ .sendMessage(mConnectivityServiceHandler
+ .obtainMessage(mEvent, new Pair<>(network, proxy)));
+ }
+ }
public ProxyTracker(@NonNull final Context context,
@NonNull final Handler connectivityServiceInternalHandler, final int pacChangedEvent) {
mContext = context;
mConnectivityServiceHandler = connectivityServiceInternalHandler;
- mPacProxyInstaller = new PacProxyInstaller(
- context, connectivityServiceInternalHandler, pacChangedEvent);
+ mPacProxyManager = context.getSystemService(PacProxyManager.class);
+
+ PacProxyInstalledListener listener = new PacProxyInstalledListener(pacChangedEvent);
+ mPacProxyManager.addPacProxyInstalledListener(
+ new HandlerExecutor(mConnectivityServiceHandler), listener);
}
// Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
@@ -182,7 +201,7 @@
if (!TextUtils.isEmpty(pacFileUrl)) {
mConnectivityServiceHandler.post(
- () -> mPacProxyInstaller.setCurrentProxyScriptUrl(proxyProperties));
+ () -> mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));
}
}
}
@@ -226,9 +245,9 @@
final ProxyInfo defaultProxy = getDefaultProxy();
final ProxyInfo proxyInfo = null != defaultProxy ?
defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList());
+ mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);
- if (mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo)
- == PacProxyInstaller.DONT_SEND_BROADCAST) {
+ if (!shouldSendBroadcast(proxyInfo)) {
return;
}
if (DBG) Log.d(TAG, "sending Proxy Broadcast for " + proxyInfo);
@@ -244,6 +263,10 @@
}
}
+ private boolean shouldSendBroadcast(ProxyInfo proxy) {
+ return Uri.EMPTY.equals(proxy.getPacFileUrl()) || proxy.getPort() > 0;
+ }
+
/**
* Sets the global proxy in memory. Also writes the values to the global settings of the device.
*
@@ -308,10 +331,10 @@
return;
}
- // This call could be coming from the PacProxyInstaller, containing the port of the
+ // This call could be coming from the PacProxyService, containing the port of the
// local proxy. If this new proxy matches the global proxy then copy this proxy to the
// global (to get the correct local port), and send a broadcast.
- // TODO: Switch PacProxyInstaller to have its own message to send back rather than
+ // TODO: Switch PacProxyService to have its own message to send back rather than
// reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
if ((mGlobalProxy != null) && (proxyInfo != null)
&& (!Uri.EMPTY.equals(proxyInfo.getPacFileUrl()))
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 2e61ae1..dee5c3f 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -69,6 +69,7 @@
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
+import android.net.NetworkScore;
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.UidRangeParcel;
@@ -1260,7 +1261,9 @@
}
mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
- mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
+ mNetworkCapabilities, lp,
+ new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
+ networkAgentConfig, mNetworkProvider) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 217f1cd..a8b0994 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -42,6 +42,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.SystemService;
import com.android.server.policy.DeviceStatePolicyImpl;
@@ -447,6 +448,9 @@
}
}
+ FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_STATE_CHANGED,
+ newState.getIdentifier(), !mCommittedState.isPresent());
+
mCommittedState = Optional.of(newState);
mPendingState = Optional.empty();
updatePendingStateLocked();
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a2b9b96..91b96dc 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -46,7 +46,6 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import java.io.PrintWriter;
@@ -220,12 +219,14 @@
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) {
+ HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context,
+ HighBrightnessModeController hbmController) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
- ambientBrightnessThresholds, screenBrightnessThresholds, display, context
+ ambientBrightnessThresholds, screenBrightnessThresholds, display, context,
+ hbmController
);
}
@@ -236,7 +237,8 @@
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) {
+ HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context,
+ HighBrightnessModeController hbmController) {
mInjector = injector;
mContext = context;
mCallbacks = callbacks;
@@ -273,20 +275,7 @@
mPendingForegroundAppPackageName = null;
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
-
- final DisplayDeviceConfig ddConfig =
- display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
- HighBrightnessModeData hbmData =
- ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
-
- final Runnable hbmChangeCallback = () -> {
- updateAutoBrightness(true /*sendUpdate*/, false /*userInitiatedChange*/);
- // TODO: b/175937645 - Callback to DisplayManagerService to indicate a change to the HBM
- // allowance has been made so that the brightness limits can be calculated
- // appropriately.
- };
- mHbmController = new HighBrightnessModeController(mHandler, brightnessMin, brightnessMax,
- hbmData, hbmChangeCallback);
+ mHbmController = hbmController;
}
/**
@@ -327,6 +316,7 @@
public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
float brightness, boolean userChangedBrightness, float adjustment,
boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
+ mHbmController.setAutoBrightnessEnabled(enable);
// While dozing, the application processor may be suspended which will prevent us from
// receiving new information from the light sensor. On some devices, we may be able to
// switch to a wake-up light sensor instead but for now we will simply disable the sensor
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 06010f5..251b579 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -110,6 +110,7 @@
private static final String ATTR_TIMESTAMP = "timestamp";
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_USER = "user";
+ private static final String ATTR_UNIQUE_DISPLAY_ID = "uniqueDisplayId";
private static final String ATTR_LUX = "lux";
private static final String ATTR_LUX_TIMESTAMPS = "luxTimestamps";
private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
@@ -217,6 +218,9 @@
}
private void backgroundStart(float initialBrightness) {
+ if (DEBUG) {
+ Slog.d(TAG, "Background start");
+ }
readEvents();
readAmbientBrightnessStats();
@@ -311,7 +315,7 @@
*/
public void notifyBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean isUserSetBrightness,
- boolean isDefaultBrightnessConfig) {
+ boolean isDefaultBrightnessConfig, String uniqueDisplayId) {
if (DEBUG) {
Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)",
brightness, userInitiated));
@@ -319,13 +323,13 @@
Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
- mInjector.currentTimeMillis()));
+ mInjector.currentTimeMillis(), uniqueDisplayId));
m.sendToTarget();
}
private void handleBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean isUserSetBrightness,
- boolean isDefaultBrightnessConfig, long timestamp) {
+ boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId) {
BrightnessChangeEvent.Builder builder;
synchronized (mDataCollectionLock) {
@@ -350,6 +354,7 @@
builder.setPowerBrightnessFactor(powerBrightnessFactor);
builder.setUserBrightnessPoint(isUserSetBrightness);
builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
+ builder.setUniqueDisplayId(uniqueDisplayId);
final int readingCount = mLastSensorReadings.size();
if (readingCount == 0) {
@@ -562,6 +567,7 @@
out.attributeLong(null, ATTR_TIMESTAMP, toWrite[i].timeStamp);
out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName);
out.attributeInt(null, ATTR_USER, userSerialNo);
+ out.attribute(null, ATTR_UNIQUE_DISPLAY_ID, toWrite[i].uniqueDisplayId);
out.attributeFloat(null, ATTR_BATTERY_LEVEL, toWrite[i].batteryLevel);
out.attributeBoolean(null, ATTR_NIGHT_MODE, toWrite[i].nightMode);
out.attributeInt(null, ATTR_COLOR_TEMPERATURE,
@@ -646,6 +652,8 @@
builder.setPackageName(parser.getAttributeValue(null, ATTR_PACKAGE_NAME));
builder.setUserId(mInjector.getUserId(mUserManager,
parser.getAttributeInt(null, ATTR_USER)));
+ builder.setUniqueDisplayId(
+ parser.getAttributeValue(null, ATTR_UNIQUE_DISPLAY_ID));
builder.setBatteryLevel(parser.getAttributeFloat(null, ATTR_BATTERY_LEVEL));
builder.setNightMode(parser.getAttributeBoolean(null, ATTR_NIGHT_MODE));
builder.setColorTemperature(
@@ -980,7 +988,8 @@
boolean userInitiatedChange = (msg.arg1 == 1);
handleBrightnessChanged(values.brightness, userInitiatedChange,
values.powerBrightnessFactor, values.isUserSetBrightness,
- values.isDefaultBrightnessConfig, values.timestamp);
+ values.isDefaultBrightnessConfig, values.timestamp,
+ values.uniqueDisplayId);
break;
case MSG_START_SENSOR_LISTENER:
startSensorListener();
@@ -1007,20 +1016,22 @@
}
private static class BrightnessChangeValues {
- final float brightness;
- final float powerBrightnessFactor;
- final boolean isUserSetBrightness;
- final boolean isDefaultBrightnessConfig;
- final long timestamp;
+ public final float brightness;
+ public final float powerBrightnessFactor;
+ public final boolean isUserSetBrightness;
+ public final boolean isDefaultBrightnessConfig;
+ public final long timestamp;
+ public final String uniqueDisplayId;
BrightnessChangeValues(float brightness, float powerBrightnessFactor,
boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
- long timestamp) {
+ long timestamp, String uniqueDisplayId) {
this.brightness = brightness;
this.powerBrightnessFactor = powerBrightnessFactor;
this.isUserSetBrightness = isUserSetBrightness;
this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
this.timestamp = timestamp;
+ this.uniqueDisplayId = uniqueDisplayId;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 96a7416..82ca820 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -376,6 +376,8 @@
private final ColorSpace mWideColorSpace;
private SensorManager mSensorManager;
+ private BrightnessTracker mBrightnessTracker;
+
// Whether minimal post processing is allowed by the user.
@GuardedBy("mSyncRoot")
@@ -1162,7 +1164,7 @@
DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
- dpc.onDisplayChanged();
+ dpc.onDisplayChangedLocked();
}
}
@@ -1851,7 +1853,10 @@
for (int i = 0; i < displayPowerControllerCount; i++) {
mDisplayPowerControllers.valueAt(i).dump(pw);
}
-
+ if (mBrightnessTracker != null) {
+ pw.println();
+ mBrightnessTracker.dump(pw);
+ }
pw.println();
mPersistentDataStore.dump(pw);
}
@@ -1937,9 +1942,12 @@
// initPowerManagement has not yet been called.
return;
}
+ if (mBrightnessTracker == null) {
+ mBrightnessTracker = new BrightnessTracker(mContext, null);
+ }
final DisplayPowerController displayPowerController = new DisplayPowerController(
mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager,
- mDisplayBlanker, display);
+ mDisplayBlanker, display, mBrightnessTracker);
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7b107b85..7110d3e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -174,6 +174,9 @@
// The ID of the LogicalDisplay tied to this DisplayPowerController.
private final int mDisplayId;
+ // The unique ID of the primary display device currently tied to this logical display
+ private String mUniqueDisplayId;
+
// Tracker for brightness changes.
@Nullable
private final BrightnessTracker mBrightnessTracker;
@@ -350,12 +353,15 @@
private final ColorDisplayServiceInternal mCdsi;
private final float[] mNitsRange;
+ private final HighBrightnessModeController mHbmController;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
// The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
private float mInitialAutoBrightness;
+
// The controller for the automatic brightness level.
private AutomaticBrightnessController mAutomaticBrightnessController;
@@ -413,16 +419,15 @@
*/
public DisplayPowerController(Context context,
DisplayPowerCallbacks callbacks, Handler handler,
- SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay) {
+ SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
+ BrightnessTracker brightnessTracker) {
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
mHandler = new DisplayControllerHandler(handler.getLooper());
if (mDisplayId == Display.DEFAULT_DISPLAY) {
- mBrightnessTracker = new BrightnessTracker(context, null);
mBatteryStats = BatteryStatsService.getService();
} else {
- mBrightnessTracker = null;
mBatteryStats = null;
}
@@ -432,6 +437,8 @@
mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
mBlanker = blanker;
mContext = context;
+ mBrightnessTracker = brightnessTracker;
+
PowerManager pm = context.getSystemService(PowerManager.class);
@@ -476,6 +483,8 @@
mSkipScreenOnBrightnessRamp = resources.getBoolean(
com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
+ mHbmController = createHbmController();
+
if (mUseSoftwareAutoBrightnessConfig) {
final float dozeScaleFactor = resources.getFraction(
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
@@ -535,7 +544,7 @@
PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
- screenBrightnessThresholds, logicalDisplay, context);
+ screenBrightnessThresholds, logicalDisplay, context, mHbmController);
} else {
mUseSoftwareAutoBrightnessConfig = false;
}
@@ -750,8 +759,10 @@
* when displays get swapped on foldable devices. For example, different brightness properties
* of each display need to be properly reflected in AutomaticBrightnessController.
*/
- public void onDisplayChanged() {
+ public void onDisplayChangedLocked() {
// TODO: b/175821789 - Support high brightness on multiple (folding) displays
+
+ mUniqueDisplayId = mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
}
/**
@@ -774,10 +785,6 @@
mAutomaticBrightnessController.stop();
}
- if (mBrightnessTracker != null) {
- mBrightnessTracker.stop();
- }
-
mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
}
}
@@ -1081,6 +1088,8 @@
mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
}
+ boolean updateScreenBrightnessSetting = false;
+
// Apply auto-brightness.
boolean slowChange = false;
if (Float.isNaN(brightnessState)) {
@@ -1097,11 +1106,7 @@
if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
slowChange = true; // slowly adapt to auto-brightness
}
- // Tell the rest of the system about the new brightness. Note that we do this
- // before applying the low power or dim transformations so that the slider
- // accurately represents the full possible range, even if they range changes what
- // it means in absolute terms.
- putScreenBrightnessSetting(brightnessState);
+ updateScreenBrightnessSetting = true;
mAppliedAutoBrightness = true;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
} else {
@@ -1119,6 +1124,7 @@
mAppliedAutoBrightness = false;
brightnessAdjustmentFlags = 0;
}
+
// Use default brightness when dozing unless overridden.
if ((Float.isNaN(brightnessState))
&& Display.isDozeState(state)) {
@@ -1129,9 +1135,24 @@
// Apply manual brightness.
if (Float.isNaN(brightnessState)) {
brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
+ if (brightnessState != mCurrentScreenBrightnessSetting) {
+ // The manually chosen screen brightness is outside of the currently allowed
+ // range (i.e., high-brightness-mode), make sure we tell the rest of the system
+ // by updating the setting.
+ updateScreenBrightnessSetting = true;
+ }
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
}
+ if (updateScreenBrightnessSetting) {
+ // Tell the rest of the system about the new brightness in case we had to change it
+ // for things like auto-brightness or high-brightness-mode. Note that we do this
+ // before applying the low power or dim transformations so that the slider
+ // accurately represents the full possible range, even if they range changes what
+ // it means in absolute terms.
+ putScreenBrightnessSetting(brightnessState);
+ }
+
// Apply dimming by at least some minimum amount when user activity
// timeout is about to expire.
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1209,9 +1230,10 @@
// animate to. To avoid this, we check the value first.
// If the brightnessState is off (-1.0f) we still want to animate to the minimum
// brightness (0.0f) to accommodate for LED displays, which can appear bright to the
- // user even when the display is all black.
- float animateValue = brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT
- ? PowerManager.BRIGHTNESS_MIN : brightnessState;
+ // user even when the display is all black. We also clamp here in case some
+ // transformations to the brightness have pushed it outside of the currently
+ // allowed range.
+ float animateValue = clampScreenBrightness(brightnessState);
final float currentBrightness = mPowerState.getScreenBrightness();
if (isValidBrightnessValue(animateValue)
&& !BrightnessSynchronizer.floatEquals(animateValue, currentBrightness)) {
@@ -1354,6 +1376,15 @@
msg.sendToTarget();
}
+ private HighBrightnessModeController createHbmController() {
+ final DisplayDeviceConfig ddConfig =
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
+ final DisplayDeviceConfig.HighBrightnessModeData hbmData =
+ ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
+ return new HighBrightnessModeController(mHandler, PowerManager.BRIGHTNESS_MIN,
+ PowerManager.BRIGHTNESS_MAX, hbmData, () -> sendUpdatePowerStateLocked());
+ }
+
private void blockScreenOn() {
if (mPendingScreenOnUnblocker == null) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
@@ -1469,10 +1500,10 @@
private float clampScreenBrightness(float value) {
if (Float.isNaN(value)) {
- return PowerManager.BRIGHTNESS_MIN;
+ value = PowerManager.BRIGHTNESS_MIN;
}
- return MathUtils.constrain(
- value, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+ return MathUtils.constrain(value,
+ mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
}
// Checks whether the brightness is within the valid brightness range, not including the off or
@@ -1869,7 +1900,7 @@
: 1.0f;
mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
powerFactor, hadUserDataPoint,
- mAutomaticBrightnessController.isDefaultConfig());
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
}
}
@@ -2037,11 +2068,6 @@
mAutomaticBrightnessController.dump(pw);
}
- if (mBrightnessTracker != null) {
- pw.println();
- mBrightnessTracker.dump(pw);
- }
-
pw.println();
if (mDisplayWhiteBalanceController != null) {
mDisplayWhiteBalanceController.dump(pw);
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 12b810f..2e5561d 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -37,7 +37,7 @@
class HighBrightnessModeController {
private static final String TAG = "HighBrightnessModeController";
- private static final boolean DEBUG_HBM = false;
+ private static final boolean DEBUG = false;
private final float mBrightnessMin;
private final float mBrightnessMax;
@@ -48,6 +48,7 @@
private boolean mIsInAllowedAmbientRange = false;
private boolean mIsTimeAvailable = false;
+ private boolean mIsAutoBrightnessEnabled = false;
private float mAutoBrightness;
/**
@@ -84,6 +85,17 @@
};
}
+ void setAutoBrightnessEnabled(boolean isEnabled) {
+ if (isEnabled == mIsAutoBrightnessEnabled) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "setAutoBrightness( " + isEnabled + " )");
+ }
+ mIsAutoBrightnessEnabled = isEnabled;
+ mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
+ }
+
float getCurrentBrightnessMin() {
return mBrightnessMin;
}
@@ -102,7 +114,7 @@
}
void onAmbientLuxChange(float ambientLux) {
- if (!deviceSupportsHbm()) {
+ if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
return;
}
@@ -132,7 +144,7 @@
mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime));
mRunningStartTimeMillis = -1;
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "New HBM event: " + mEvents.getFirst());
}
}
@@ -142,7 +154,7 @@
}
private boolean isCurrentlyAllowed() {
- return mIsTimeAvailable && mIsInAllowedAmbientRange;
+ return mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange;
}
private boolean deviceSupportsHbm() {
@@ -167,7 +179,7 @@
timeAlreadyUsed = currentTime - mRunningStartTimeMillis;
}
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed);
}
@@ -187,7 +199,7 @@
timeAlreadyUsed += event.endTimeMillis - startTimeMillis;
}
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed);
}
@@ -220,7 +232,7 @@
nextTimeout = timeWhenMinIsGainedBack;
}
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "HBM recalculated. IsAllowedWithoutRestrictions: "
+ isAllowedWithoutRestrictions
+ ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index aaec89a..2546118 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -853,14 +853,6 @@
// Do not lock when calling these SurfaceControl methods because they are sync
// operations that may block for a while when setting display power mode.
mSurfaceControlProxy.setDesiredDisplayModeSpecs(displayToken, modeSpecs);
-
- final int sfActiveModeId = mSurfaceControlProxy
- .getDynamicDisplayInfo(displayToken).activeDisplayModeId;
- synchronized (getSyncRoot()) {
- if (updateActiveModeLocked(sfActiveModeId)) {
- updateDeviceInfoLocked();
- }
- }
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index 947ee24..f6828d1 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -100,18 +100,22 @@
// Wake-up on <Set Stream Path> was not mandatory before CEC 2.0.
// The message is re-sent at the end of the action for devices that don't support 2.0.
sendSetStreamPath();
- int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
- HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(
- getTargetAddress());
- if (targetDevice != null) {
- targetPowerStatus = targetDevice.getDevicePowerStatus();
- }
- if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ if (!mIsCec20) {
queryDevicePowerStatus();
- } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
+ } else {
+ int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
+ HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork()
+ .getCecDeviceInfo(getTargetAddress());
+ if (targetDevice != null) {
+ targetPowerStatus = targetDevice.getDevicePowerStatus();
+ }
+ if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ queryDevicePowerStatus();
+ } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ return true;
+ }
}
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e6e2f96..03a8338 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -431,6 +431,13 @@
private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
+ @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes) {
+ super(context);
+ mLocalDevices = deviceTypes;
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mHdmiCecConfig = new HdmiCecConfig(context);
+ }
+
public HdmiControlService(Context context) {
super(context);
List<Integer> deviceTypes = HdmiProperties.device_type();
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 9d2db94..979e7a4 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -89,7 +89,7 @@
mSource = source();
sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
- boolean targetOnBefore = getTargetDevicePowerStatus(mSource, mTargetAddress,
+ boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress,
HdmiControlManager.POWER_STATUS_UNKNOWN) == HdmiControlManager.POWER_STATUS_ON;
broadcastActiveSource();
// If the device is not an audio system itself, request the connected audio system to
@@ -98,18 +98,23 @@
sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(),
Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true));
}
- int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress,
- HdmiControlManager.POWER_STATUS_UNKNOWN);
- if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+
+ if (!mIsCec20) {
queryDevicePowerStatus();
- } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
- if (!targetOnBefore) {
- // Suppress 2nd <Active Source> message if the target device was already on when
- // the 1st one was sent.
- broadcastActiveSource();
+ } else {
+ int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress,
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ queryDevicePowerStatus();
+ } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
+ if (!is20TargetOnBefore) {
+ // Suppress 2nd <Active Source> message if the target device was already on when
+ // the 1st one was sent.
+ broadcastActiveSource();
+ }
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ return true;
}
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
}
mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index cbe6e69..0925027 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2606,6 +2606,11 @@
}
// Native callback
+ private void notifyDropWindow(IBinder token, float x, float y) {
+ mWindowManagerCallbacks.notifyDropWindow(token, x, y);
+ }
+
+ // Native callback
private void notifyUntrustedTouch(String packageName) {
// TODO(b/169067926): Remove toast after gathering feedback on dogfood.
if (!UNTRUSTED_TOUCHES_TOAST || ArrayUtils.contains(
@@ -3035,6 +3040,11 @@
* Called when the focused window has changed.
*/
void notifyFocusChanged(IBinder oldToken, IBinder newToken);
+
+ /**
+ * Called when the drag over window has changed.
+ */
+ void notifyDropWindow(IBinder token, float x, float y);
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 672ed3d..ffb532e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2381,9 +2381,12 @@
showCurrentInputLocked(mCurFocusedWindow, getAppShowFlags(), null,
SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
+ final InputMethodInfo curInputMethodInfo = mMethodMap.get(mCurId);
+ final boolean suppressesSpellChecker =
+ curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.session, (session.channel != null ? session.channel.dup() : null),
- mCurId, mCurSeq, mCurActivityViewToScreenMatrix);
+ mCurId, mCurSeq, mCurActivityViewToScreenMatrix, suppressesSpellChecker);
}
@Nullable
@@ -2425,7 +2428,7 @@
// party code.
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
- null, null, mCurMethodId, mCurSeq, null);
+ null, null, mCurMethodId, mCurSeq, null, false);
}
if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
@@ -2501,7 +2504,7 @@
requestClientSessionLocked(cs);
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
- null, null, mCurId, mCurSeq, null);
+ null, null, mCurId, mCurSeq, null, false);
} else if (SystemClock.uptimeMillis()
< (mLastBindTime+TIME_TO_RECONNECT)) {
// In this case we have connected to the service, but
@@ -2513,7 +2516,7 @@
// to see if we can get back in touch with the service.
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
- null, null, mCurId, mCurSeq, null);
+ null, null, mCurId, mCurSeq, null, false);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
@@ -2553,7 +2556,7 @@
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
- null, null, mCurId, mCurSeq, null);
+ null, null, mCurId, mCurSeq, null, false);
}
mCurIntent = null;
Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
@@ -2621,9 +2624,8 @@
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
// Dispatch display id for InputMethodService to update context display.
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO(
- MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken,
- mMethodMap.get(mCurMethodId).getConfigChanges()));
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
+ MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken));
scheduleNotifyImeUidToAudioService(mCurMethodUid);
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
@@ -2921,8 +2923,7 @@
dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0);
break;
}
- mWindowManagerInternal.updateInputMethodWindowStatus(token,
- (vis & InputMethodService.IME_VISIBLE) != 0, dismissImeOnBackKeyPressed);
+ mWindowManagerInternal.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed);
}
@BinderThread
@@ -3510,7 +3511,7 @@
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
- null, null, null, -1, null);
+ null, null, null, -1, null, false);
}
mCurFocusedWindow = windowToken;
@@ -4479,8 +4480,7 @@
}
final IBinder token = (IBinder) args.arg2;
((IInputMethod) args.arg1).initializeInternal(token, msg.arg1,
- new InputMethodPrivilegedOperationsImpl(this, token),
- (int) args.arg3);
+ new InputMethodPrivilegedOperationsImpl(this, token));
} catch (RemoteException e) {
}
args.recycle();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index b13c307..8e84002 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -21,7 +21,6 @@
import android.content.pm.PackageManager;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.Log;
import android.util.Printer;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
@@ -55,7 +54,7 @@
public final boolean mIsSystemLocale;
public final boolean mIsSystemLanguage;
- public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
+ ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
mImeName = imeName;
mSubtypeName = subtypeName;
@@ -72,8 +71,8 @@
// TODO: Use Locale#getLanguage or Locale#toLanguageTag
final String systemLanguage = parseLanguageFromLocaleString(systemLocale);
final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
- mIsSystemLanguage = systemLanguage.length() >= 2 &&
- systemLanguage.equals(subtypeLanguage);
+ mIsSystemLanguage = systemLanguage.length() >= 2
+ && systemLanguage.equals(subtypeLanguage);
}
}
}
@@ -159,7 +158,7 @@
return true;
}
if (o instanceof ImeSubtypeListItem) {
- final ImeSubtypeListItem that = (ImeSubtypeListItem)o;
+ final ImeSubtypeListItem that = (ImeSubtypeListItem) o;
return Objects.equals(this.mImi, that.mImi) && this.mSubtypeId == that.mSubtypeId;
}
return false;
@@ -173,7 +172,7 @@
private final String mSystemLocaleStr;
private final InputMethodSettings mSettings;
- public InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
+ InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
mContext = context;
mSettings = settings;
mPm = context.getPackageManager();
@@ -245,7 +244,7 @@
private static class StaticRotationList {
private final List<ImeSubtypeListItem> mImeSubtypeList;
- public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) {
+ StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) {
mImeSubtypeList = imeSubtypeList;
}
@@ -258,8 +257,8 @@
*/
private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) {
final int currentSubtypeId = calculateSubtypeId(imi, subtype);
- final int N = mImeSubtypeList.size();
- for (int i = 0; i < N; ++i) {
+ final int numSubtypes = mImeSubtypeList.size();
+ for (int i = 0; i < numSubtypes; ++i) {
final ImeSubtypeListItem isli = mImeSubtypeList.get(i);
// Skip until the current IME/subtype is found.
if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) {
@@ -281,10 +280,10 @@
if (currentIndex < 0) {
return null;
}
- final int N = mImeSubtypeList.size();
- for (int offset = 1; offset < N; ++offset) {
+ final int numSubtypes = mImeSubtypeList.size();
+ for (int offset = 1; offset < numSubtypes; ++offset) {
// Start searching the next IME/subtype from the next of the current index.
- final int candidateIndex = (currentIndex + offset) % N;
+ final int candidateIndex = (currentIndex + offset) % numSubtypes;
final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex);
// Skip if searching inside the current IME only, but the candidate is not
// the current IME.
@@ -297,8 +296,8 @@
}
protected void dump(final Printer pw, final String prefix) {
- final int N = mImeSubtypeList.size();
- for (int i = 0; i < N; ++i) {
+ final int numSubtypes = mImeSubtypeList.size();
+ for (int i = 0; i < numSubtypes; ++i) {
final int rank = i;
final ImeSubtypeListItem item = mImeSubtypeList.get(i);
pw.println(prefix + "rank=" + rank + " item=" + item);
@@ -314,8 +313,8 @@
private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) {
mImeSubtypeList = imeSubtypeListItems;
mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()];
- final int N = mImeSubtypeList.size();
- for (int i = 0; i < N; i++) {
+ final int numSubtypes = mImeSubtypeList.size();
+ for (int i = 0; i < numSubtypes; i++) {
mUsageHistoryOfSubtypeListItemIndex[i] = i;
}
}
@@ -329,13 +328,13 @@
*/
private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) {
final int currentSubtypeId = calculateSubtypeId(imi, subtype);
- final int N = mUsageHistoryOfSubtypeListItemIndex.length;
- for (int usageRank = 0; usageRank < N; usageRank++) {
+ final int numItems = mUsageHistoryOfSubtypeListItemIndex.length;
+ for (int usageRank = 0; usageRank < numItems; usageRank++) {
final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
final ImeSubtypeListItem subtypeListItem =
mImeSubtypeList.get(subtypeListItemIndex);
- if (subtypeListItem.mImi.equals(imi) &&
- subtypeListItem.mSubtypeId == currentSubtypeId) {
+ if (subtypeListItem.mImi.equals(imi)
+ && subtypeListItem.mSubtypeId == currentSubtypeId) {
return usageRank;
}
}
@@ -364,9 +363,9 @@
}
return null;
}
- final int N = mUsageHistoryOfSubtypeListItemIndex.length;
- for (int i = 1; i < N; i++) {
- final int subtypeListItemRank = (currentUsageRank + i) % N;
+ final int numItems = mUsageHistoryOfSubtypeListItemIndex.length;
+ for (int i = 1; i < numItems; i++) {
+ final int subtypeListItemRank = (currentUsageRank + i) % numItems;
final int subtypeListItemIndex =
mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
final ImeSubtypeListItem subtypeListItem =
@@ -400,9 +399,10 @@
final List<ImeSubtypeListItem> switchingAwareImeSubtypes =
filterImeSubtypeList(sortedEnabledItems,
true /* supportsSwitchingToNextInputMethod */);
- if (currentInstance != null &&
- currentInstance.mSwitchingAwareRotationList != null &&
- Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList,
+ if (currentInstance != null
+ && currentInstance.mSwitchingAwareRotationList != null
+ && Objects.equals(
+ currentInstance.mSwitchingAwareRotationList.mImeSubtypeList,
switchingAwareImeSubtypes)) {
// Can reuse the current instance.
switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList;
@@ -416,9 +416,9 @@
{
final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList(
sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */);
- if (currentInstance != null &&
- currentInstance.mSwitchingUnawareRotationList != null &&
- Objects.equals(
+ if (currentInstance != null
+ && currentInstance.mSwitchingUnawareRotationList != null
+ && Objects.equals(
currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList,
switchingUnawareImeSubtypes)) {
// Can reuse the current instance.
@@ -466,11 +466,11 @@
final List<ImeSubtypeListItem> items,
final boolean supportsSwitchingToNextInputMethod) {
final ArrayList<ImeSubtypeListItem> result = new ArrayList<>();
- final int ALL_ITEMS_COUNT = items.size();
- for (int i = 0; i < ALL_ITEMS_COUNT; i++) {
+ final int numItems = items.size();
+ for (int i = 0; i < numItems; i++) {
final ImeSubtypeListItem item = items.get(i);
- if (item.mImi.supportsSwitchingToNextInputMethod() ==
- supportsSwitchingToNextInputMethod) {
+ if (item.mImi.supportsSwitchingToNextInputMethod()
+ == supportsSwitchingToNextInputMethod) {
result.add(item);
}
}
@@ -502,7 +502,7 @@
public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
if (mController == null) {
if (DEBUG) {
- Log.e(TAG, "mController shouldn't be null.");
+ Slog.e(TAG, "mController shouldn't be null.");
}
return;
}
@@ -520,7 +520,7 @@
InputMethodSubtype subtype) {
if (mController == null) {
if (DEBUG) {
- Log.e(TAG, "mController shouldn't be null.");
+ Slog.e(TAG, "mController shouldn't be null.");
}
return null;
}
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 1dd3d41..ef1489b 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1748,7 +1748,7 @@
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
null, null, data.mCurrentInputMethodInfo.getId(),
- clientInfo.mBindingSequence, null);
+ clientInfo.mBindingSequence, null, false);
case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
case InputMethodClientState.ALREADY_SENT_BIND_RESULT:
clientInfo.mBindingSequence++;
@@ -1770,7 +1770,7 @@
clientInfo.mInputMethodSession,
clientInfo.mWriteChannel.dup(),
data.mCurrentInputMethodInfo.getId(),
- clientInfo.mBindingSequence, null);
+ clientInfo.mBindingSequence, null, false);
case InputMethodClientState.UNREGISTERED:
Slog.e(TAG, "The client is already unregistered.");
return InputBindResult.INVALID_CLIENT;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
index 60e8cce..348a03b 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -92,6 +92,9 @@
break;
}
}
+ if (keyToIndexMap.size() < 2) {
+ throw new IllegalStateException("Indexing file is corrupt.");
+ }
return keyToIndexMap;
}
@@ -106,6 +109,9 @@
private static List<String> searchKeysRangeContainingKey(
List<String> sortedKeyList, String key, int startIndex, int endIndex) {
+ if (endIndex <= startIndex) {
+ throw new IllegalStateException("Indexing file is corrupt.");
+ }
if (endIndex - startIndex == 1) {
return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex));
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index e1c011d..65987444 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -25,6 +25,9 @@
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.Intent;
import android.hardware.contexthub.V1_0.ContextHubMsg;
@@ -38,6 +41,7 @@
import android.hardware.location.NanoAppMessage;
import android.hardware.location.NanoAppState;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -47,9 +51,11 @@
import com.android.server.location.ClientBrokerProto;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -111,6 +117,14 @@
*/
private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery ";
+ /**
+ * For clients targeting S and above, a SecurityException is thrown when they are in the denied
+ * authorization state and attempt to send a message to a nanoapp.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ private static final long CHANGE_ID_AUTH_STATE_DENIED = 181350407L;
+
/*
* The context of the service.
*/
@@ -205,14 +219,19 @@
* allowed to communicate over that channel. A channel is defined to have been opened if the
* client has sent or received messages from the particular nanoapp.
*/
- private final Map<Long, Integer> mMessageChannelNanoappIdMap =
- new ConcurrentHashMap<Long, Integer>();
+ private final Map<Long, Integer> mMessageChannelNanoappIdMap = new ConcurrentHashMap<>();
+
+ /**
+ * Set containing all nanoapps that have been forcefully transitioned to the denied
+ * authorization state (via CLI) to ensure they don't transition back to the granted state
+ * later if, for example, a permission check is performed due to another nanoapp
+ */
+ private final Set<Long> mForceDeniedNapps = new HashSet<>();
/**
* Map containing all nanoapps that have active auth state denial timers.
*/
- private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap =
- new ConcurrentHashMap<Long, AuthStateDenialTimer>();
+ private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap = new ConcurrentHashMap<>();
/**
* Callback used to obtain the latest set of nanoapp permissions and verify this client has
@@ -344,6 +363,8 @@
*
* @param message the message to send
* @return the error code of sending the message
+ * @throws SecurityException if this client doesn't have permissions to send a message to the
+ * nanoapp
*/
@ContextHubTransaction.Result
@Override
@@ -355,7 +376,13 @@
int authState = mMessageChannelNanoappIdMap.getOrDefault(
message.getNanoAppId(), AUTHORIZATION_UNKNOWN);
if (authState == AUTHORIZATION_DENIED) {
- return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
+ if (Compatibility.isChangeEnabled(CHANGE_ID_AUTH_STATE_DENIED)) {
+ throw new SecurityException("Client doesn't have valid permissions to send"
+ + " message to " + message.getNanoAppId());
+ }
+ // Return a bland error code for apps targeting old SDKs since they wouldn't be able
+ // to use an error code added in S.
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
} else if (authState == AUTHORIZATION_UNKNOWN) {
// Only check permissions the first time a nanoapp is queried since nanoapp
// permissions don't currently change at runtime. If the host permission changes
@@ -368,7 +395,7 @@
int contextHubId = mAttachedContextHubInfo.getId();
try {
- result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp);
+ result = mContextHubProxy.getHub().sendMessageToHub(contextHubId, messageToNanoApp);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ contextHubId + ")", e);
@@ -637,7 +664,8 @@
private int updateNanoAppAuthState(
long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired) {
return updateNanoAppAuthState(
- nanoAppId, nanoappPermissions, gracePeriodExpired, false /* forceDenied */);
+ nanoAppId, nanoappPermissions, gracePeriodExpired,
+ mForceDeniedNapps.contains(nanoAppId) /* forceDenied */);
}
/**
@@ -679,6 +707,7 @@
// any state -> DENIED if "forceDenied" is true
if (forceDenied) {
newAuthState = AUTHORIZATION_DENIED;
+ mForceDeniedNapps.add(nanoAppId);
} else if (gracePeriodExpired) {
if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
newAuthState = AUTHORIZATION_DENIED;
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 dde45c4..c44089b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -17,13 +17,16 @@
package com.android.server.location.contexthub;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
+import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.contexthub.V1_0.AsyncEventType;
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
@@ -59,6 +62,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
import com.android.server.location.ContextHubServiceProto;
import java.io.FileDescriptor;
@@ -127,6 +131,8 @@
// Lock object for sendWifiSettingUpdate()
private final Object mSendWifiSettingUpdateLock = new Object();
+ private final SensorPrivacyManagerInternal mSensorPrivacyManagerInternal;
+
/**
* Class extending the callback to register with a Context Hub.
*/
@@ -186,6 +192,7 @@
if (mContextHubWrapper == null) {
mTransactionManager = null;
mClientManager = null;
+ mSensorPrivacyManagerInternal = null;
mDefaultClientMap = Collections.emptyMap();
mContextHubIdToInfoMap = Collections.emptyMap();
mSupportedContextHubPerms = Collections.emptyList();
@@ -208,6 +215,8 @@
mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
mTransactionManager = new ContextHubTransactionManager(
mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
+ mSensorPrivacyManagerInternal =
+ LocalServices.getService(SensorPrivacyManagerInternal.class);
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
@@ -284,18 +293,16 @@
}
if (mContextHubWrapper.supportsMicrophoneDisableSettingNotifications()) {
- sendMicrophoneDisableSettingUpdate();
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
- SensorPrivacyManager.OnSensorPrivacyChangedListener listener =
- new SensorPrivacyManager.OnSensorPrivacyChangedListener() {
- @Override
- public void onSensorPrivacyChanged(boolean enabled) {
- sendMicrophoneDisableSettingUpdate();
- }
- };
- SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
- manager.addSensorPrivacyListener(
- SensorPrivacyManager.Sensors.MICROPHONE, listener);
+ mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
+ SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
+ if (userId == getCurrentUserId()) {
+ Log.d(TAG, "User: " + userId + " enabled: " + enabled);
+ sendMicrophoneDisableSettingUpdate(enabled);
+ }
+ });
+
}
}
@@ -1074,19 +1081,48 @@
}
/**
- * Obtains the latest microphone disable setting value and notifies the
- * Context Hub.
+ * Notifies a microphone disable settings change to the Context Hub.
*/
- private void sendMicrophoneDisableSettingUpdate() {
- SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
- boolean disabled = manager.isSensorPrivacyEnabled(
- SensorPrivacyManager.Sensors.MICROPHONE);
- Log.d(TAG, "Mic Disabled Setting: " + disabled);
- mContextHubWrapper.onMicrophoneDisableSettingChanged(disabled);
+ private void sendMicrophoneDisableSettingUpdate(boolean enabled) {
+ Log.d(TAG, "Mic Disabled Setting: " + enabled);
+ mContextHubWrapper.onMicrophoneDisableSettingChanged(enabled);
+ }
+
+ /**
+ * Obtains the latest microphone disabled setting for the current user
+ * and notifies the Context Hub.
+ */
+ private void sendMicrophoneDisableSettingUpdateForCurrentUser() {
+ boolean isEnabled = mSensorPrivacyManagerInternal.isSensorPrivacyEnabled(
+ getCurrentUserId(), SensorPrivacyManager.Sensors.MICROPHONE);
+ sendMicrophoneDisableSettingUpdate(isEnabled);
}
private String getCallingPackageName() {
return mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
}
+
+ private int getCurrentUserId() {
+ final long id = Binder.clearCallingIdentity();
+ try {
+ UserInfo currentUser = ActivityManager.getService().getCurrentUser();
+ return currentUser.id;
+ } catch (RemoteException e) {
+ // Activity manager not running, nothing we can do - assume user 0.
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ return UserHandle.USER_SYSTEM;
+ }
+
+ /**
+ * Send a microphone disable settings update whenever the foreground user changes.
+ * We always send a settings update regardless of the previous state for the same user
+ * since the CHRE framework is expected to handle repeated identical setting update.
+ */
+ public void onUserChanged() {
+ Log.d(TAG, "User changed to id: " + getCurrentUserId());
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
+ }
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 3a5c220..3245fdf 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -104,12 +104,6 @@
int hubId, IContexthubCallback callback) throws RemoteException;
/**
- * Calls the appropriate sendMessageToHub function depending on the HAL version.
- */
- public abstract int sendMessageToHub(int hubId,
- android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException;
-
- /**
* @return A valid instance of Contexthub HAL 1.0.
*/
public abstract android.hardware.contexthub.V1_0.IContexthub getHub();
@@ -180,11 +174,6 @@
mHub.registerCallback(hubId, callback);
}
- public int sendMessageToHub(int hubId,
- android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
- return mHub.sendMessageToHub(hubId, message);
- }
-
public android.hardware.contexthub.V1_0.IContexthub getHub() {
return mHub;
}
@@ -234,11 +223,6 @@
mHub.registerCallback(hubId, callback);
}
- public int sendMessageToHub(int hubId,
- android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
- return mHub.sendMessageToHub(hubId, message);
- }
-
public android.hardware.contexthub.V1_0.IContexthub getHub() {
return mHub;
}
@@ -304,14 +288,6 @@
mHub.registerCallback_1_2(hubId, callback);
}
- public int sendMessageToHub(int hubId,
- android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
- android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
- new android.hardware.contexthub.V1_2.ContextHubMsg();
- newMessage.msg_1_0 = message;
- return mHub.sendMessageToHub_1_2(hubId, newMessage);
- }
-
public android.hardware.contexthub.V1_0.IContexthub getHub() {
return mHub;
}
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
index 6d250ec..6201b94 100644
--- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -119,19 +119,11 @@
for (UserAuthInfo userAuthInfo : pendingResetLockuts) {
Slog.d(TAG, "Resetting face lockout for sensor: " + sensorId
+ ", user: " + userAuthInfo.userId);
- final VerifyCredentialResponse response = spManager.verifyChallengeInternal(
- getGatekeeperService(), userAuthInfo.gatekeeperPassword, challenge,
- userAuthInfo.userId);
- if (response == null) {
- Slog.wtf(TAG, "VerifyChallenge failed, null response");
- continue;
+ final byte[] hat = requestHatFromGatekeeperPassword(spManager, userAuthInfo,
+ challenge);
+ if (hat != null) {
+ faceManager.resetLockout(sensorId, userAuthInfo.userId, hat);
}
- if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
- Slog.wtf(TAG, "VerifyChallenge failed, response: "
- + response.getResponseCode());
- }
- faceManager.resetLockout(sensorId, userAuthInfo.userId,
- response.getGatekeeperHAT());
}
sensorIds.remove(sensorId);
@@ -146,15 +138,6 @@
finishCallback.onFinished();
}
}
-
- synchronized IGateKeeperService getGatekeeperService() {
- final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
- if (service == null) {
- Slog.e(TAG, "Unable to acquire GateKeeperService");
- return null;
- }
- return IGateKeeperService.Stub.asInterface(service);
- }
}
@Nullable private FaceResetLockoutTask mFaceResetLockoutTask;
@@ -214,10 +197,19 @@
mFingerprintManager.resetLockout(prop.sensorId, user.userId,
null /* hardwareAuthToken */);
}
+ } else if (!prop.resetLockoutRequiresChallenge) {
+ for (UserAuthInfo user : pendingResetLockouts) {
+ Slog.d(TAG, "Resetting fingerprint lockout for sensor: " + prop.sensorId
+ + ", user: " + user.userId);
+ final byte[] hat = requestHatFromGatekeeperPassword(mSpManager, user,
+ 0 /* challenge */);
+ if (hat != null) {
+ mFingerprintManager.resetLockout(prop.sensorId, user.userId, hat);
+ }
+ }
} else {
- Slog.e(TAG, "Fingerprint resetLockout with HAT not supported yet");
- // TODO(b/152414803): Implement this when resetLockout is implemented below
- // the framework.
+ Slog.w(TAG, "No fingerprint HAL interface requires HAT with challenge"
+ + ", sensorId: " + prop.sensorId);
}
}
}
@@ -228,11 +220,6 @@
* in-flight challenge, we generate a single challenge to reset lockout for all profiles. This
* hopefully reduces/eliminates issues such as overwritten challenge, incorrectly revoked
* challenge, or other race conditions.
- *
- * TODO(b/162965646) This logic can be avoided if multiple in-flight challenges are supported.
- * Though it will need to continue to exist to support existing HIDLs, each profile that
- * requires resetLockout could have its own challenge, and the `mPendingResetLockouts` queue
- * can be avoided.
*/
private void processPendingLockoutsForFace(List<UserAuthInfo> pendingResetLockouts) {
if (mFaceManager != null) {
@@ -251,10 +238,60 @@
mFaceResetLockoutTask = new FaceResetLockoutTask(mFaceFinishCallback, mFaceManager,
mSpManager, sensorIds, pendingResetLockouts);
for (final FaceSensorPropertiesInternal prop : faceSensorProperties) {
- // Generate a challenge for each sensor. The challenge does not need to be
- // per-user, since the HAT returned by gatekeeper contains userId.
- mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask);
+ if (prop.resetLockoutRequiresHardwareAuthToken) {
+ if (prop.resetLockoutRequiresChallenge) {
+ // Generate a challenge for each sensor. The challenge does not need to be
+ // per-user, since the HAT returned by gatekeeper contains userId.
+ mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask);
+ } else {
+ for (UserAuthInfo user : pendingResetLockouts) {
+ Slog.d(TAG, "Resetting face lockout for sensor: " + prop.sensorId
+ + ", user: " + user.userId);
+ final byte[] hat = requestHatFromGatekeeperPassword(mSpManager, user,
+ 0 /* challenge */);
+ if (hat != null) {
+ mFaceManager.resetLockout(prop.sensorId, user.userId, hat);
+ }
+ }
+ }
+ } else {
+ Slog.w(TAG, "Lockout is below the HAL for all face authentication interfaces"
+ + ", sensorId: " + prop.sensorId);
+ }
}
}
}
+
+ @Nullable
+ private static byte[] requestHatFromGatekeeperPassword(
+ @NonNull SyntheticPasswordManager spManager,
+ @NonNull UserAuthInfo userAuthInfo, long challenge) {
+ final VerifyCredentialResponse response = spManager.verifyChallengeInternal(
+ getGatekeeperService(), userAuthInfo.gatekeeperPassword, challenge,
+ userAuthInfo.userId);
+ if (response == null) {
+ Slog.wtf(TAG, "VerifyChallenge failed, null response");
+ return null;
+ }
+ if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ Slog.wtf(TAG, "VerifyChallenge failed, response: "
+ + response.getResponseCode());
+ return null;
+ }
+ if (response.getGatekeeperHAT() == null) {
+ Slog.e(TAG, "Null HAT received from spManager");
+ }
+
+ return response.getGatekeeperHAT();
+ }
+
+ @Nullable
+ private static synchronized IGateKeeperService getGatekeeperService() {
+ final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
+ if (service == null) {
+ Slog.e(TAG, "Unable to acquire GateKeeperService");
+ return null;
+ }
+ return IGateKeeperService.Stub.asInterface(service);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 43c7365..22a9a47 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -91,7 +91,6 @@
import android.security.AndroidKeyStoreMaintenance;
import android.security.Authorization;
import android.security.KeyStore;
-import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.UserNotAuthenticatedException;
@@ -158,7 +157,6 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
-import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -263,13 +261,7 @@
@Override
public void onStart() {
- Optional<Boolean> keystore2_enabled =
- android.sysprop.Keystore2Properties.keystore2_enabled();
- if (keystore2_enabled.isPresent() && keystore2_enabled.get()) {
- android.security.keystore2.AndroidKeyStoreProvider.install();
- } else {
- AndroidKeyStoreProvider.install();
- }
+ android.security.keystore2.AndroidKeyStoreProvider.install();
mLockSettingsService = new LockSettingsService(getContext());
publishBinderService("lock_settings", mLockSettingsService);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0a074e1..b10d56b 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1068,6 +1068,12 @@
public boolean sendMediaButton(String packageName, int pid, int uid,
boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
try {
+ if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
+ final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
+ + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
+ }
if (asSystemService) {
mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
@@ -1085,6 +1091,12 @@
public boolean sendMediaButton(String packageName, int pid, int uid,
boolean asSystemService, KeyEvent keyEvent) {
try {
+ if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
+ final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
+ + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
+ }
if (asSystemService) {
mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null);
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 23d8429..46ece74 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -63,6 +63,7 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
+import android.os.PowerExemptionManager;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -82,9 +83,11 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.Watchdog.Monitor;
+import com.android.server.am.ActivityManagerLocal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -114,6 +117,13 @@
*/
private static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
+ /**
+ * Denotes the duration during which an app receiving a media session callback will be
+ * exempted from FGS-from-BG restriction and so will be allowed to start an FGS even if it is
+ * in the background state while it receives a media session callback.
+ */
+ private static final long FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS = 10_000;
+
private final Context mContext;
private final SessionManagerImpl mSessionManagerImpl;
private final MessageHandler mHandler = new MessageHandler();
@@ -136,6 +146,7 @@
private KeyguardManager mKeyguardManager;
private AudioManager mAudioManager;
private boolean mHasFeatureLeanback;
+ private ActivityManagerLocal mActivityManagerLocal;
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -219,6 +230,8 @@
final IntentFilter filter = new IntentFilter(
NotificationManager.ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED);
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
+
+ mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
}
@Override
@@ -538,6 +551,26 @@
throw new IllegalArgumentException("packageName is not owned by the calling process");
}
+ void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
+ int callingPid, int callingUid, String callingPackage, String reason) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ enforcePackageName(callingPackage, callingUid);
+ if (targetUid != callingUid && mActivityManagerLocal.canStartForegroundService(
+ callingPid, callingUid, callingPackage)) {
+ final Context userContext = mContext.createContextAsUser(
+ UserHandle.of(UserHandle.getUserId(targetUid)), /* flags= */ 0);
+ final PowerExemptionManager powerExemptionManager = userContext.getSystemService(
+ PowerExemptionManager.class);
+ powerExemptionManager.addToTemporaryAllowList(targetPackage,
+ FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS,
+ PowerExemptionManager.REASON_MEDIA_SESSION_CALLBACK, reason);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
/**
* Checks a caller's authorization to register an IRemoteControlDisplay.
* Authorization is granted if one of the following is true:
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index da62aca..0fa6283 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -58,6 +58,23 @@
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_MASK;
+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;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
+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;
@@ -246,6 +263,7 @@
import com.android.internal.util.StatLogger;
import com.android.internal.util.XmlUtils;
import com.android.net.module.util.NetworkIdentityUtils;
+import com.android.net.module.util.PermissionUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -413,6 +431,14 @@
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;
+ // TODO: Add similar docs for other messages.
+ /**
+ * Message to indicate that reasons for why an uid is blocked changed.
+ * arg1 = uid
+ * arg2 = oldBlockedReasons
+ * obj = newBlockedReasons
+ */
+ private static final int MSG_BLOCKED_REASON_CHANGED = 21;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -559,7 +585,10 @@
/** Foreground at UID granularity. */
@GuardedBy("mUidRulesFirstLock")
- final SparseArray<UidState> mUidState = new SparseArray<UidState>();
+ private final SparseArray<UidState> mUidState = new SparseArray<>();
+
+ @GuardedBy("mUidRulesFirstLock")
+ private final SparseArray<UidBlockedState> mUidBlockedState = new SparseArray<>();
/** Map from network ID to last observed meteredness state */
@GuardedBy("mNetworkPoliciesSecondLock")
@@ -2876,15 +2905,18 @@
}
@Override
- public void registerListener(INetworkPolicyListener listener) {
+ public void registerListener(@NonNull INetworkPolicyListener listener) {
+ Objects.requireNonNull(listener);
// TODO: Remove CONNECTIVITY_INTERNAL and the *AnyPermissionOf methods above after all apps
// have declared OBSERVE_NETWORK_POLICY.
enforceAnyPermissionOf(CONNECTIVITY_INTERNAL, OBSERVE_NETWORK_POLICY);
mListeners.register(listener);
+ // TODO: Send callbacks to the newly registered listener
}
@Override
- public void unregisterListener(INetworkPolicyListener listener) {
+ public void unregisterListener(@NonNull INetworkPolicyListener listener) {
+ Objects.requireNonNull(listener);
// TODO: Remove CONNECTIVITY_INTERNAL and the *AnyPermissionOf methods above after all apps
// have declared OBSERVE_NETWORK_POLICY.
enforceAnyPermissionOf(CONNECTIVITY_INTERNAL, OBSERVE_NETWORK_POLICY);
@@ -3078,8 +3110,16 @@
@Override
public int getRestrictBackgroundByCaller() {
mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
- final int uid = Binder.getCallingUid();
+ return getRestrictBackgroundStatusInternal(Binder.getCallingUid());
+ }
+ @Override
+ public int getRestrictBackgroundStatus(int uid) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
+ return getRestrictBackgroundStatusInternal(uid);
+ }
+
+ private int getRestrictBackgroundStatusInternal(int uid) {
synchronized (mUidRulesFirstLock) {
// Must clear identity because getUidPolicy() is restricted to system.
final long token = Binder.clearCallingIdentity();
@@ -3548,6 +3588,7 @@
* Get multipath preference value for the given network.
*/
public int getMultipathPreference(Network network) {
+ PermissionUtils.enforceNetworkStackPermission(mContext);
final Integer preference = mMultipathPolicyTracker.getMultipathPreference(network);
if (preference != null) {
return preference;
@@ -3921,6 +3962,7 @@
mUidRules.put(uid, newUidRule);
mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
}
+ updateBlockedReasonsForRestrictedModeUL(uid);
});
if (mRestrictedNetworkingMode) {
// firewall rules only need to be set when this mode is being enabled.
@@ -3941,6 +3983,7 @@
mUidRules.put(uid, newUidRule);
mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
}
+ updateBlockedReasonsForRestrictedModeUL(uid);
// if restricted networking mode is on, and the app has an access exemption, the uid rule
// will not change, but the firewall rule will have to be updated.
@@ -3952,6 +3995,31 @@
}
}
+ private void updateBlockedReasonsForRestrictedModeUL(int uid) {
+ UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ if (uidBlockedState == null) {
+ uidBlockedState = new UidBlockedState();
+ mUidBlockedState.put(uid, uidBlockedState);
+ }
+ final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+ if (mRestrictedNetworkingMode) {
+ uidBlockedState.blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE;
+ } else {
+ uidBlockedState.blockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE;
+ }
+ if (hasRestrictedModeAccess(uid)) {
+ uidBlockedState.allowedReasons |= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
+ } else {
+ uidBlockedState.allowedReasons &= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
+ }
+ uidBlockedState.updateEffectiveBlockedReasons();
+ if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
+ mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
+ uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
+ .sendToTarget();
+ }
+ }
+
private int getNewRestrictedModeUidRule(int uid, int oldUidRule) {
int newRule = oldUidRule;
newRule &= ~MASK_RESTRICTED_MODE_NETWORKS;
@@ -4072,11 +4140,21 @@
boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
|| mPowerSaveWhitelistAppIds.get(appId);
if (!deviceIdleMode) {
- isWhitelisted = isWhitelisted || mPowerSaveWhitelistExceptIdleAppIds.get(appId);
+ isWhitelisted = isWhitelisted || isWhitelistedFromPowerSaveExceptIdleUL(uid);
}
return isWhitelisted;
}
+ /**
+ * Returns whether a uid is allowlisted from power saving restrictions, except Device idle
+ * (eg: Battery Saver and app idle).
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private boolean isWhitelistedFromPowerSaveExceptIdleUL(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ return mPowerSaveWhitelistExceptIdleAppIds.get(appId);
+ }
+
// NOTE: since both fw_dozable and fw_powersave uses the same map
// (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method.
@GuardedBy("mUidRulesFirstLock")
@@ -4521,6 +4599,11 @@
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
+ UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ if (uidBlockedState == null) {
+ uidBlockedState = new UidBlockedState();
+ mUidBlockedState.put(uid, uidBlockedState);
+ }
final boolean isDenied = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
final boolean isAllowed = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
@@ -4545,6 +4628,16 @@
}
}
+ int newBlockedReasons = BLOCKED_REASON_NONE;
+ int newAllowedReasons = ALLOWED_REASON_NONE;
+ newBlockedReasons |= (isRestrictedByAdmin ? BLOCKED_METERED_REASON_ADMIN_DISABLED : 0);
+ 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 |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0);
+
if (LOGV) {
Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")"
+ ": isForeground=" +isForeground
@@ -4616,6 +4709,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();
+ }
}
}
@@ -4690,6 +4795,12 @@
// Copy existing uid rules and clear ALL_NETWORK rules.
int newUidRules = oldUidRules & (~MASK_ALL_NETWORKS);
+ UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+ if (uidBlockedState == null) {
+ uidBlockedState = new UidBlockedState();
+ mUidBlockedState.put(uid, uidBlockedState);
+ }
+
// First step: define the new rule based on user restrictions and foreground state.
// NOTE: if statements below could be inlined, but it's easier to understand the logic
@@ -4702,6 +4813,20 @@
newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
}
+ int newBlockedReasons = BLOCKED_REASON_NONE;
+ int newAllowedReasons = ALLOWED_REASON_NONE;
+ newBlockedReasons |= (mRestrictPower ? BLOCKED_REASON_BATTERY_SAVER : 0);
+ newBlockedReasons |= (mDeviceIdleMode ? BLOCKED_REASON_DOZE : 0);
+ newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
+ newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
+
+ newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
+ newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
+ newAllowedReasons |= (isWhitelistedFromPowerSaveUL(uid, true)
+ ? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0);
+ newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid)
+ ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0);
+
if (LOGV) {
Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
+ ", isIdle: " + isUidIdle
@@ -4733,6 +4858,18 @@
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();
+ }
+
return newUidRules;
}
@@ -4762,61 +4899,57 @@
}
private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
- if (listener != null) {
- try {
- listener.onUidRulesChanged(uid, uidRules);
- } catch (RemoteException ignored) {
- }
+ try {
+ listener.onUidRulesChanged(uid, uidRules);
+ } catch (RemoteException ignored) {
}
}
private void dispatchMeteredIfacesChanged(INetworkPolicyListener listener,
String[] meteredIfaces) {
- if (listener != null) {
- try {
- listener.onMeteredIfacesChanged(meteredIfaces);
- } catch (RemoteException ignored) {
- }
+ try {
+ listener.onMeteredIfacesChanged(meteredIfaces);
+ } catch (RemoteException ignored) {
}
}
private void dispatchRestrictBackgroundChanged(INetworkPolicyListener listener,
boolean restrictBackground) {
- if (listener != null) {
- try {
- listener.onRestrictBackgroundChanged(restrictBackground);
- } catch (RemoteException ignored) {
- }
+ try {
+ listener.onRestrictBackgroundChanged(restrictBackground);
+ } catch (RemoteException ignored) {
}
}
private void dispatchUidPoliciesChanged(INetworkPolicyListener listener, int uid,
int uidPolicies) {
- if (listener != null) {
- try {
- listener.onUidPoliciesChanged(uid, uidPolicies);
- } catch (RemoteException ignored) {
- }
+ try {
+ listener.onUidPoliciesChanged(uid, uidPolicies);
+ } catch (RemoteException ignored) {
}
}
private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
int overrideMask, int overrideValue, int[] networkTypes) {
- if (listener != null) {
- try {
- listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes);
- } catch (RemoteException ignored) {
- }
+ try {
+ listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes);
+ } catch (RemoteException ignored) {
}
}
private void dispatchSubscriptionPlansChanged(INetworkPolicyListener listener, int subId,
SubscriptionPlan[] plans) {
- if (listener != null) {
- try {
- listener.onSubscriptionPlansChanged(subId, plans);
- } catch (RemoteException ignored) {
- }
+ try {
+ listener.onSubscriptionPlansChanged(subId, plans);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ private void dispatchBlockedReasonChanged(INetworkPolicyListener listener, int uid,
+ int oldBlockedReasons, int newBlockedReasons) {
+ try {
+ listener.onBlockedReasonChanged(uid, oldBlockedReasons, newBlockedReasons);
+ } catch (RemoteException ignored) {
}
}
@@ -4973,6 +5106,19 @@
mListeners.finishBroadcast();
return true;
}
+ case MSG_BLOCKED_REASON_CHANGED: {
+ final int uid = msg.arg1;
+ final int newBlockedReasons = msg.arg2;
+ final int oldBlockedReasons = (int) msg.obj;
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ dispatchBlockedReasonChanged(listener, uid,
+ oldBlockedReasons, newBlockedReasons);
+ }
+ mListeners.finishBroadcast();
+ return true;
+ }
default: {
return false;
}
@@ -5704,6 +5850,51 @@
return (bundle != null) ? bundle.getBoolean(key, defaultValue) : defaultValue;
}
+ private class UidBlockedState {
+ public int blockedReasons;
+ public int allowedReasons;
+ public int effectiveBlockedReasons;
+
+ UidBlockedState() {
+ blockedReasons = BLOCKED_REASON_NONE;
+ allowedReasons = ALLOWED_REASON_NONE;
+ effectiveBlockedReasons = BLOCKED_REASON_NONE;
+ }
+
+ void updateEffectiveBlockedReasons() {
+ effectiveBlockedReasons = blockedReasons;
+ // If the uid is not subject to any blocked reasons, then return early
+ if (blockedReasons == BLOCKED_REASON_NONE) {
+ return;
+ }
+ if ((allowedReasons & ALLOWED_REASON_SYSTEM) != 0) {
+ effectiveBlockedReasons = BLOCKED_REASON_NONE;
+ }
+ if ((allowedReasons & ALLOWED_REASON_FOREGROUND) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
+ effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_USER_RESTRICTED;
+ }
+ if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_ALLOWLIST) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ }
+ if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
+ effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+ }
+ if ((allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE;
+ }
+ if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) {
+ effectiveBlockedReasons &= ~BLOCKED_METERED_REASON_DATA_SAVER;
+ }
+ }
+ }
+
private class NotificationId {
private final String mTag;
private final int mId;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0cc9f9e..e588366 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7376,15 +7376,7 @@
// so need to check the notification still valide for vibrate.
synchronized (mNotificationLock) {
if (mNotificationsByKey.get(record.getKey()) != null) {
- // Vibrator checks the appops for the op package, not the caller,
- // so we need to add the bypass dnd flag to be heard. it's ok to
- // always add this flag here because we've already checked that we can
- // bypass dnd
- AudioAttributes.Builder aab =
- new AudioAttributes.Builder(record.getAudioAttributes())
- .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY);
- mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(),
- effect, "Notification (delayed)", aab.build());
+ vibrate(record, effect, true);
} else {
Slog.e(TAG, "No vibration for canceled notification : "
+ record.getKey());
@@ -7392,8 +7384,7 @@
}
}).start();
} else {
- mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getPackageName(),
- effect, "Notification", record.getAudioAttributes());
+ vibrate(record, effect, false);
}
return true;
} finally{
@@ -7401,6 +7392,16 @@
}
}
+ private void vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed) {
+ // We need to vibrate as "android" so we can breakthrough DND. VibratorManagerService
+ // doesn't have a concept of vibrating on an app's behalf, so add the app information
+ // to the reason so we can still debug from bugreports
+ String reason = "Notification (" + record.getSbn().getOpPkg() + " "
+ + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : "");
+ mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME,
+ effect, reason, record.getAudioAttributes());
+ }
+
private boolean isNotificationForCurrentUser(NotificationRecord record) {
final int currentUser;
final long token = Binder.clearCallingIdentity();
@@ -10634,17 +10635,17 @@
return true;
}
}
- String toastMessage = "Indirect activity start from " + packageName;
String logcatMessage =
"Indirect notification activity start (trampoline) from " + packageName;
-
+ // Call to toast() method is posted to mHandler below to offload PM lookup from the
+ // activity start path
if (CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid)) {
- toast(toastMessage + " blocked.");
+ mHandler.post(() -> toast(packageName, uid, /* blocked */ true));
Slog.e(TAG, logcatMessage + " blocked");
return false;
} else {
if (mPackagesShown.add(packageName)) {
- toast(toastMessage + ". This will be blocked in S.");
+ mHandler.post(() -> toast(packageName, uid, /* blocked */ false));
}
Slog.w(TAG, logcatMessage + ", this should be avoided for performance reasons");
return true;
@@ -10660,10 +10661,19 @@
&& !CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
}
- private void toast(String message) {
- mUiHandler.post(() ->
- Toast.makeText(getUiContext(), message + "\nSee g.co/dev/trampolines.",
- Toast.LENGTH_LONG).show());
+ private void toast(String packageName, int uid, boolean blocked) {
+ final CharSequence label;
+ try {
+ label = mPackageManagerClient.getApplicationLabel(
+ mPackageManager.getApplicationInfo(packageName, 0,
+ UserHandle.getUserId(uid)));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unexpected exception obtaining app label from PackageManager", e);
+ return;
+ }
+ mUiHandler.post(() -> Toast.makeText(getUiContext(),
+ label + " launch " + (blocked ? "blocked" : "will be blocked")
+ + "\ng.co/dev/trampolines", Toast.LENGTH_LONG).show());
}
}
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 1acbabd..ca8202f 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -105,6 +105,12 @@
private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>();
/**
+ * A mapping from the set of App IDs that query other App IDs via library name to the
+ * list of packages that they can see.
+ */
+ private final SparseSetArray<Integer> mQueryableViaUsesLibrary = new SparseSetArray<>();
+
+ /**
* Executor for running reasonably short background tasks such as building the initial
* visibility cache.
*/
@@ -239,6 +245,7 @@
Snapshots.copy(mImplicitlyQueryable, orig.mImplicitlyQueryable);
Snapshots.copy(mQueriesViaPackage, orig.mQueriesViaPackage);
Snapshots.copy(mQueriesViaComponent, orig.mQueriesViaComponent);
+ Snapshots.copy(mQueryableViaUsesLibrary, orig.mQueryableViaUsesLibrary);
mQueriesViaComponentRequireRecompute = orig.mQueriesViaComponentRequireRecompute;
mForceQueryable.addAll(orig.mForceQueryable);
mForceQueryableByDevicePackageNames = orig.mForceQueryableByDevicePackageNames;
@@ -508,6 +515,22 @@
return false;
}
+ private static boolean canQueryViaUsesLibrary(AndroidPackage querying,
+ AndroidPackage potentialTarget) {
+ if (potentialTarget.getLibraryNames().isEmpty()) {
+ return false;
+ }
+ final List<String> libNames = potentialTarget.getLibraryNames();
+ for (int i = 0, size = libNames.size(); i < size; i++) {
+ final String libName = libNames.get(i);
+ if (querying.getUsesLibraries().contains(libName)
+ || querying.getUsesOptionalLibraries().contains(libName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static boolean matchesProviders(
Set<String> queriesAuthorities, AndroidPackage potentialTarget) {
for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) {
@@ -707,6 +730,9 @@
|| canQueryAsInstaller(existingSetting, newPkg)) {
mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId);
}
+ if (canQueryViaUsesLibrary(existingPkg, newPkg)) {
+ mQueryableViaUsesLibrary.add(existingSetting.appId, newPkgSetting.appId);
+ }
}
// now we'll evaluate our new package's ability to see existing packages
if (!mForceQueryable.contains(existingSetting.appId)) {
@@ -718,6 +744,9 @@
|| canQueryAsInstaller(newPkgSetting, existingPkg)) {
mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId);
}
+ if (canQueryViaUsesLibrary(newPkg, existingPkg)) {
+ mQueryableViaUsesLibrary.add(newPkgSetting.appId, existingSetting.appId);
+ }
}
// if either package instruments the other, mark both as visible to one another
if (newPkgSetting.pkg != null && existingSetting.pkg != null
@@ -1035,6 +1064,10 @@
for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) {
mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId);
}
+ mQueryableViaUsesLibrary.remove(setting.appId);
+ for (int i = mQueryableViaUsesLibrary.size() - 1; i >= 0; i--) {
+ mQueryableViaUsesLibrary.remove(mQueryableViaUsesLibrary.keyAt(i), setting.appId);
+ }
mForceQueryable.remove(setting.appId);
@@ -1315,6 +1348,18 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueryableViaUsesLibrary");
+ if (mQueryableViaUsesLibrary.contains(callingAppId, targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "queryable for library users");
+ }
+ return false;
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+
return true;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -1394,6 +1439,8 @@
filteringAppId == null ? null : UserHandle.getUid(user, filteringAppId),
mImplicitlyQueryable, " ", expandPackages);
}
+ pw.println(" queryable via uses-library:");
+ dumpQueriesMap(pw, filteringAppId, mQueryableViaUsesLibrary, " ", expandPackages);
}
private static void dumpQueriesMap(PrintWriter pw, @Nullable Integer filteringId,
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 4be509b..1d556fe 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -1492,7 +1492,7 @@
}
return null;
}
- final ResolveInfo res = new ResolveInfo();
+ final ResolveInfo res = new ResolveInfo(info.hasCategory(Intent.CATEGORY_BROWSABLE));
res.activityInfo = ai;
if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
res.filter = info;
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index b34611b..29322e2 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -180,6 +180,8 @@
mId = id;
mListener = listener;
mDataLoader = null;
+
+ callListener(IDataLoaderStatusListener.DATA_LOADER_BINDING);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 0a443f3..7aaab0c 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -51,6 +51,7 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.storage.StorageManager;
@@ -62,6 +63,8 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.ArtManagerService;
+import com.android.server.pm.dex.ArtStatsLogUtils;
+import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.DexoptUtils;
@@ -99,6 +102,8 @@
private final PowerManager.WakeLock mDexoptWakeLock;
private volatile boolean mSystemReady;
+ private final ArtStatsLogger mArtStatsLogger = new ArtStatsLogger();
+
PackageDexOptimizer(Installer installer, Object installLock, Context context,
String wakeLockTag) {
this.mInstaller = installer;
@@ -252,6 +257,28 @@
profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
packageStats, options.isDowngrade(), profileName, dexMetadataPath,
options.getCompilationReason());
+
+ // Only report metrics for base apk for now.
+ // TODO: add ISA and APK type to metrics.
+ if (pkg.getBaseApkPath().equals(path)) {
+ Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
+ try {
+ long sessionId = Math.randomLongInternal();
+ ArtStatsLogUtils.writeStatsLog(
+ mArtStatsLogger,
+ sessionId,
+ path,
+ compilerFilter,
+ sharedGid,
+ packageStats.getCompileTime(path),
+ dexMetadataPath,
+ options.getCompilationReason(),
+ newResult);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
// The end result is:
// - FAILED if any path failed,
// - PERFORMED if at least one path needed compilation,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 82b12aa..bafe987 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1011,8 +1011,9 @@
"DataLoader installation of APEX modules is not allowed.");
}
- if (this.params.dataLoaderParams.getComponentName().getPackageName()
- == SYSTEM_DATA_LOADER_PACKAGE && mContext.checkCallingOrSelfPermission(
+ boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals(
+ this.params.dataLoaderParams.getComponentName().getPackageName());
+ if (systemDataLoader && mContext.checkCallingOrSelfPermission(
Manifest.permission.USE_SYSTEM_DATA_LOADERS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("You need the "
@@ -3653,6 +3654,7 @@
@Override
public void onStatusChanged(int dataLoaderId, int status) {
switch (status) {
+ case IDataLoaderStatusListener.DATA_LOADER_BINDING:
case IDataLoaderStatusListener.DATA_LOADER_STOPPED:
case IDataLoaderStatusListener.DATA_LOADER_DESTROYED:
return;
@@ -3763,8 +3765,8 @@
healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS;
healthCheckParams.unhealthyMonitoringMs = INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS;
- final boolean systemDataLoader =
- params.getComponentName().getPackageName() == SYSTEM_DATA_LOADER_PACKAGE;
+ final boolean systemDataLoader = SYSTEM_DATA_LOADER_PACKAGE.equals(
+ params.getComponentName().getPackageName());
final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() {
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dfe72b2..ff042c2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -252,6 +252,7 @@
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;
@@ -882,7 +883,7 @@
// Lock for global state used when modifying package state or settings.
// Methods that must be called with this lock held have
// the suffix "Locked". Some methods may use the legacy suffix "LP"
- final Object mLock;
+ final PackageManagerTracedLock mLock;
// Keys are String (package name), values are Package.
@Watched
@@ -1041,7 +1042,7 @@
private final PackageAbiHelper mAbiHelper;
private final Context mContext;
- private final Object mLock;
+ private final PackageManagerTracedLock mLock;
private final Installer mInstaller;
private final Object mInstallLock;
private final Handler mBackgroundHandler;
@@ -1081,7 +1082,7 @@
mDomainVerificationManagerInternalProducer;
private final Singleton<Handler> mHandlerProducer;
- Injector(Context context, Object lock, Installer installer,
+ Injector(Context context, PackageManagerTracedLock lock, Installer installer,
Object installLock, PackageAbiHelper abiHelper,
Handler backgroundHandler,
List<ScanPartition> systemPartitions,
@@ -1181,7 +1182,7 @@
return mUserManagerProducer.get(this, mPackageManager);
}
- public Object getLock() {
+ public PackageManagerTracedLock getLock() {
return mLock;
}
@@ -1768,6 +1769,11 @@
public int[] getAllUserIds() {
return mUserManager.getUserIds();
}
+
+ @Override
+ public boolean doesUserExist(@UserIdInt int userId) {
+ return mUserManager.exists(userId);
+ }
}
/**
@@ -2639,8 +2645,7 @@
// We'll want to include browser possibilities in a few cases
boolean includeBrowser = false;
- if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates,
- matchFlags)) {
+ if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) {
result.addAll(undefinedList);
// Maybe add one for the other profile.
if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel
@@ -5959,7 +5964,7 @@
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
t.traceBegin("create package manager");
- final Object lock = new Object();
+ final PackageManagerTracedLock lock = new PackageManagerTracedLock();
final Object installLock = new Object();
HandlerThread backgroundThread = new HandlerThread("PackageManagerBg");
backgroundThread.start();
@@ -25693,6 +25698,7 @@
if (!convertedFromPreCreated || !readPermissionStateForUser(userId)) {
mPermissionManager.onUserCreated(userId);
mLegacyPermissionManager.grantDefaultPermissions(userId);
+ mDomainVerificationManager.clearUser(userId);
}
}
@@ -26884,11 +26890,12 @@
outUpdatedPackageNames.add(targetPackageName);
modified = true;
}
+
+ if (modified) {
+ invalidatePackageInfoCache();
+ }
}
- if (modified) {
- invalidatePackageInfoCache();
- }
return true;
}
@@ -27317,6 +27324,28 @@
return PackageManagerService.this.getPackageStartability(
packageName, callingUid, userId) == PACKAGE_STARTABILITY_FROZEN;
}
+
+ @Override
+ public boolean isPackageUsesPermissionNeverForLocation(@NonNull String packageName,
+ @NonNull String permissionName) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(permissionName);
+ final AndroidPackage pkg;
+ synchronized (mLock) {
+ pkg = mPackages.get(packageName);
+ }
+ if (pkg == null) return false;
+ final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+ final int size = usesPermissions.size();
+ for (int i = 0; i < size; i++) {
+ final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+ if (Objects.equals(usesPermission.name, permissionName)) {
+ return (usesPermission.usesPermissionFlags
+ & ParsedUsesPermission.FLAG_NEVER_FOR_LOCATION) != 0;
+ }
+ }
+ return false;
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
copy to services/core/java/com/android/server/pm/PackageManagerTracedLock.java
index 54242be..e15e8a8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
+++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents;
+package com.android.server.pm;
/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ * This is a unique class that is used as the PackageManager lock. It can be targeted for lock
+ * injection, similar to {@link ActivityManagerGlobalLock}.
*/
-oneway interface ISplitScreenListener {
- void onStagePositionChanged(int stage, int position);
- void onTaskStageChanged(int taskId, int stage, boolean visible);
+public class PackageManagerTracedLock {
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ec7b451..b51b833 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -353,7 +353,7 @@
private static final String ATTR_DATABASE_VERSION = "databaseVersion";
private static final String ATTR_VALUE = "value";
- private final Object mLock;
+ private final PackageManagerTracedLock mLock;
private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
@@ -525,7 +525,7 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public Settings(Map<String, PackageSetting> pkgSettings) {
- mLock = new Object();
+ mLock = new PackageManagerTracedLock();
mPackages.putAll(pkgSettings);
mAppIds = new WatchedArrayList<>();
mOtherAppIds = new WatchedSparseArray<>();
@@ -562,7 +562,7 @@
Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence,
LegacyPermissionDataProvider permissionDataProvider,
@NonNull DomainVerificationManagerInternal domainVerificationManager,
- @NonNull Object lock) {
+ @NonNull PackageManagerTracedLock lock) {
mLock = lock;
mAppIds = new WatchedArrayList<>();
mOtherAppIds = new WatchedSparseArray<>();
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 0a74032..8f87b19 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -214,7 +214,7 @@
newSigningDetails = ApkSignatureVerifier.verify(apexPath, minSignatureScheme);
} catch (PackageParserException e) {
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
- "Failed to parse APEX package " + apexPath, e);
+ "Failed to parse APEX package " + apexPath + " : " + e, e);
}
// Get signing details of the existing package
@@ -232,7 +232,8 @@
existingApexPkg.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
- "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir, e);
+ "Failed to parse APEX package " + existingApexPkg.applicationInfo.sourceDir
+ + " : " + e, e);
}
// Verify signing details for upgrade
@@ -291,7 +292,7 @@
}
} catch (PackageParserException e) {
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
- "Failed to parse APEX package " + apexInfo.modulePath, e);
+ "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
}
final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
ApexManager.MATCH_ACTIVE_PACKAGE);
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
new file mode 100644
index 0000000..0c8e36b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -0,0 +1,262 @@
+/*
+ * 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.dex;
+
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK;
+import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK;
+
+import android.util.jar.StrictJarFile;
+import android.util.Slog;
+
+import com.android.internal.art.ArtStatsLog;
+import com.android.server.pm.PackageManagerService;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+
+/** Utils class to report ART metrics to statsd. */
+public class ArtStatsLogUtils {
+ private static final String TAG = ArtStatsLogUtils.class.getSimpleName();
+ private static final String PROFILE_DEX_METADATA = "primary.prof";
+ private static final String VDEX_DEX_METADATA = "primary.vdex";
+
+
+ private static final int ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY =
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY;
+ private static final int ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED =
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
+ private static final int ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED =
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+
+ private static final int ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK =
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK;
+ private static final int ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK =
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK;
+
+ private static final Map<Integer, Integer> COMPILATION_REASON_MAP = new HashMap();
+
+ static {
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_UNKNOWN, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_UNKNOWN);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_FIRST_BOOT, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_FIRST_BOOT);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_BOOT_AFTER_OTA, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_POST_BOOT, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_POST_BOOT);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_FAST, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_FAST);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK_SECONDARY,
+ ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED,
+ ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED,
+ ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_BACKGROUND_DEXOPT, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BG_DEXOPT);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_AB_OTA, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_AB_OTA);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE,
+ ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INACTIVE);
+ COMPILATION_REASON_MAP.put(PackageManagerService.REASON_SHARED,
+ ArtStatsLog.ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_SHARED);
+ }
+
+ private static final Map<String, Integer> COMPILE_FILTER_MAP = new HashMap();
+
+ static {
+ COMPILE_FILTER_MAP.put("error", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_ERROR);
+ COMPILE_FILTER_MAP.put("unknown", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_UNKNOWN);
+ COMPILE_FILTER_MAP.put("assume-verified", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_ASSUMED_VERIFIED);
+ COMPILE_FILTER_MAP.put("extract", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_EXTRACT);
+ COMPILE_FILTER_MAP.put("verify", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_VERIFY);
+ COMPILE_FILTER_MAP.put("quicken", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_QUICKEN);
+ COMPILE_FILTER_MAP.put("space-profile", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPACE_PROFILE);
+ COMPILE_FILTER_MAP.put("space", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPACE);
+ COMPILE_FILTER_MAP.put("speed-profile", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPEED_PROFILE);
+ COMPILE_FILTER_MAP.put("speed", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_SPEED);
+ COMPILE_FILTER_MAP.put("everything-profile", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_EVERYTHING_PROFILE);
+ COMPILE_FILTER_MAP.put("everything", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_EVERYTHING);
+ COMPILE_FILTER_MAP.put("run-from-apk", ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK);
+ COMPILE_FILTER_MAP.put("run-from-apk-fallback",
+ ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK);
+ COMPILE_FILTER_MAP.put("run-from-vdex-fallback",
+ ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK);
+ }
+
+ public static void writeStatsLog(
+ ArtStatsLogger logger,
+ long sessionId,
+ String path,
+ String compilerFilter,
+ int uid,
+ long compileTime,
+ String dexMetadataPath,
+ int compilationReason,
+ int result) {
+ int dexMetadataType = getDexMetadataType(dexMetadataPath);
+ logger.write(
+ sessionId,
+ uid,
+ compilationReason,
+ compilerFilter,
+ ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_RESULT_CODE,
+ result,
+ dexMetadataType);
+ logger.write(
+ sessionId,
+ uid,
+ compilationReason,
+ compilerFilter,
+ ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_DEX_CODE_BYTES,
+ getDexBytes(path),
+ dexMetadataType);
+ logger.write(
+ sessionId,
+ uid,
+ compilationReason,
+ compilerFilter,
+ ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_TOTAL_TIME,
+ compileTime,
+ dexMetadataType);
+ }
+
+ private static long getDexBytes(String apkPath) {
+ StrictJarFile jarFile = null;
+ long dexBytes = 0;
+ try {
+ jarFile = new StrictJarFile(apkPath,
+ /*verify=*/ false,
+ /*signatureSchemeRollbackProtectionsEnforced=*/ false);
+ Iterator<ZipEntry> it = jarFile.iterator();
+ while (it.hasNext()) {
+ ZipEntry entry = it.next();
+ if (entry.getName().matches("classes(\\d)*[.]dex")) {
+ dexBytes += entry.getSize();
+ }
+ }
+ return dexBytes;
+ } catch (IOException ignore) {
+ Slog.e(TAG, "Error when parsing APK " + apkPath);
+ return -1L;
+ } finally {
+ try {
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ private static int getDexMetadataType(String dexMetadataPath) {
+ if (dexMetadataPath == null) {
+ return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE;
+ }
+ StrictJarFile jarFile = null;
+ try {
+ jarFile = new StrictJarFile(dexMetadataPath,
+ /*verify=*/ false,
+ /*signatureSchemeRollbackProtectionsEnforced=*/false);
+ boolean hasProfile = findFileName(jarFile, PROFILE_DEX_METADATA);
+ boolean hasVdex = findFileName(jarFile, VDEX_DEX_METADATA);
+ if (hasProfile && hasVdex) {
+ return ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX;
+ } else if (hasProfile) {
+ return ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE;
+ } else if (hasVdex) {
+ return ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX;
+ } else {
+ return ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN;
+ }
+ } catch (IOException ignore) {
+ Slog.e(TAG, "Error when parsing dex metadata " + dexMetadataPath);
+ return ArtStatsLog.ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_ERROR;
+ } finally {
+ try {
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ private static boolean findFileName(StrictJarFile jarFile, String filename) throws IOException {
+ Iterator<ZipEntry> it = jarFile.iterator();
+ while (it.hasNext()) {
+ ZipEntry entry = it.next();
+ if (entry.getName().equals(filename)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static class ArtStatsLogger {
+ public void write(
+ long sessionId,
+ int uid,
+ int compilationReason,
+ String compilerFilter,
+ int kind,
+ long value,
+ int dexMetadataType) {
+ ArtStatsLog.write(
+ ArtStatsLog.ART_DATUM_REPORTED,
+ sessionId,
+ uid,
+ COMPILE_FILTER_MAP.getOrDefault(compilerFilter, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_UNKNOWN),
+ COMPILATION_REASON_MAP.getOrDefault(compilationReason, ArtStatsLog.
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_UNKNOWN),
+ /*timestamp_millis=*/ 0L,
+ ArtStatsLog.ART_DATUM_REPORTED__THREAD_TYPE__ART_THREAD_MAIN,
+ kind,
+ value,
+ dexMetadataType);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 349561d..37f3175 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -986,7 +986,7 @@
* Fetches the battery manager object and caches it if it hasn't been fetched already.
*/
private BatteryManager getBatteryManager() {
- if (mBatteryManager == null) {
+ if (mBatteryManager == null && mContext != null) {
mBatteryManager = mContext.getSystemService(BatteryManager.class);
}
@@ -1008,10 +1008,6 @@
&& mPowerManager.getCurrentThermalStatus()
>= PowerManager.THERMAL_STATUS_SEVERE);
- if (DEBUG) {
- Log.d(TAG, "Battery, thermal, or memory are critical: " + isBtmCritical);
- }
-
return isBtmCritical;
}
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 e3ccb75..27bf8a13 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -737,10 +737,12 @@
}
}
if (locationExtraPackageNames != null) {
- // Also grant location permission to location extra packages.
+ // Also grant location and activity recognition permission to location extra packages.
for (String packageName : locationExtraPackageNames) {
grantPermissionsToSystemPackage(pm, packageName, userId,
ALWAYS_LOCATION_PERMISSIONS);
+ grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId,
+ ACTIVITY_RECOGNITION_PERMISSIONS);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index b61fd8d..39ed488 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -21,7 +21,6 @@
import android.annotation.UserIdInt;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
-import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -151,7 +150,7 @@
Integer state = reusedMap.valueAt(stateIndex);
writer.print(domain);
writer.print(": ");
- writer.println(DomainVerificationManager.stateToDebugString(state));
+ writer.println(DomainVerificationState.stateToDebugString(state));
}
writer.decreaseIndent();
writer.decreaseIndent();
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 1721a18..f4bcd3e 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
@@ -141,6 +141,12 @@
"Caller is not allowed to edit other users");
}
+ if (!mCallback.doesUserExist(callingUserId)) {
+ throw new SecurityException("User " + callingUserId + " does not exist");
+ } else if (!mCallback.doesUserExist(targetUserId)) {
+ throw new SecurityException("User " + targetUserId + " does not exist");
+ }
+
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
@@ -161,6 +167,12 @@
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit user selections");
+ if (!mCallback.doesUserExist(callingUserId)) {
+ throw new SecurityException("User " + callingUserId + " does not exist");
+ } else if (!mCallback.doesUserExist(targetUserId)) {
+ throw new SecurityException("User " + targetUserId + " does not exist");
+ }
+
if (packageName == null) {
return true;
}
@@ -184,6 +196,12 @@
}
}
+ if (!mCallback.doesUserExist(callingUserId)) {
+ throw new SecurityException("User " + callingUserId + " does not exist");
+ } else if (!mCallback.doesUserExist(targetUserId)) {
+ throw new SecurityException("User " + targetUserId + " does not exist");
+ }
+
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
@@ -197,6 +215,12 @@
"Caller is not allowed to edit other users");
}
+ if (!mCallback.doesUserExist(callingUserId)) {
+ throw new SecurityException("User " + callingUserId + " does not exist");
+ } else if (!mCallback.doesUserExist(targetUserId)) {
+ throw new SecurityException("User " + targetUserId + " does not exist");
+ }
+
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
@@ -221,6 +245,12 @@
mContext.enforcePermission(
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
callingPid, callingUid, "Caller is not allowed to query user selections");
+
+ if (!mCallback.doesUserExist(callingUserId)) {
+ throw new SecurityException("User " + callingUserId + " does not exist");
+ } else if (!mCallback.doesUserExist(targetUserId)) {
+ throw new SecurityException("User " + targetUserId + " does not exist");
+ }
}
public interface Callback {
@@ -229,5 +259,7 @@
* if the package was not installed
*/
boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId);
+
+ boolean doesUserExist(@UserIdInt int userId);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 0c2b4c5..7318273 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -80,14 +80,14 @@
* been preserved for migration purposes, but is otherwise ignored. Corresponds to
* {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS}.
*/
- int APPROVAL_LEVEL_LEGACY_ALWAYS = 1;
+ int APPROVAL_LEVEL_LEGACY_ALWAYS = 2;
/**
* The app has been chosen by the user through
* {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
* indicating an explicit choice to use this app to open an unverified domain.
*/
- int APPROVAL_LEVEL_SELECTION = 2;
+ int APPROVAL_LEVEL_SELECTION = 3;
/**
* The app is approved through the digital asset link statement being hosted at the domain
@@ -95,7 +95,7 @@
* {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)} by
* the domain verification agent on device.
*/
- int APPROVAL_LEVEL_VERIFIED = 3;
+ int APPROVAL_LEVEL_VERIFIED = 4;
/**
* The app has been installed as an instant app, which grants it total authority on the domains
@@ -105,7 +105,7 @@
* The user is still able to disable instant app link handling through
* {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, boolean)}.
*/
- int APPROVAL_LEVEL_INSTANT_APP = 4;
+ int APPROVAL_LEVEL_INSTANT_APP = 5;
/**
* Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)}
@@ -333,10 +333,10 @@
@Nullable
UUID getDomainVerificationInfoId(@NonNull String packageName);
+ @DomainVerificationManager.Error
@RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
- void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
- @NonNull Set<String> domains, int state)
- throws IllegalArgumentException, NameNotFoundException;
+ int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+ @NonNull Set<String> domains, int state) throws NameNotFoundException;
interface Connection extends DomainVerificationEnforcer.Callback {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
index a7a52e0..2a17c6d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -24,7 +24,6 @@
import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
-import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.pm.verify.domain.IDomainVerificationManager;
import android.os.ServiceSpecificException;
@@ -61,11 +60,12 @@
}
}
+ @DomainVerificationManager.Error
@Override
- public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet,
+ public int setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet,
int state) {
try {
- mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
+ return mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
domainSet.getDomains(), state);
} catch (Exception e) {
throw rethrow(e);
@@ -82,11 +82,12 @@
}
}
+ @DomainVerificationManager.Error
@Override
- public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet,
+ public int setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet,
boolean enabled, @UserIdInt int userId) {
try {
- mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
+ return mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
domainSet.getDomains(), enabled, userId);
} catch (Exception e) {
throw rethrow(e);
@@ -116,14 +117,9 @@
}
private RuntimeException rethrow(Exception exception) throws RuntimeException {
- if (exception instanceof InvalidDomainSetException) {
- int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET;
- packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16;
- return new ServiceSpecificException(packedErrorCode,
- ((InvalidDomainSetException) exception).getPackageName());
- } else if (exception instanceof NameNotFoundException) {
+ if (exception instanceof NameNotFoundException) {
return new ServiceSpecificException(
- DomainVerificationManager.ERROR_NAME_NOT_FOUND);
+ DomainVerificationManager.INTERNAL_ERROR_NAME_NOT_FOUND);
} else if (exception instanceof RuntimeException) {
return (RuntimeException) exception;
} else {
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 e85bbe4..a0e252a 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
@@ -19,6 +19,7 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
+import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -34,7 +35,6 @@
import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
-import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
import android.content.pm.verify.domain.DomainVerificationState;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.pm.verify.domain.IDomainVerificationManager;
@@ -243,17 +243,17 @@
throws NameNotFoundException {
mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
synchronized (mLock) {
- DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
- if (pkgState == null) {
- return null;
- }
-
AndroidPackage pkg = mConnection.getPackageLocked(packageName);
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
- Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ return null;
+ }
+
+ ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
// TODO(b/159952358): Should the domain list be cached?
ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg);
@@ -267,44 +267,56 @@
DomainVerificationState.STATE_NO_RESPONSE);
}
+ final int mapSize = hostToStateMap.size();
+ for (int index = 0; index < mapSize; index++) {
+ int internalValue = hostToStateMap.valueAt(index);
+ int publicValue = DomainVerificationState.convertToInfoState(internalValue);
+ hostToStateMap.setValueAt(index, publicValue);
+ }
+
// TODO(b/159952358): Do not return if no values are editable (all ignored states)?
return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
}
}
- public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
- int state) throws InvalidDomainSetException, NameNotFoundException {
+ @DomainVerificationManager.Error
+ public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+ int state) throws NameNotFoundException {
if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
if (state != DomainVerificationState.STATE_SUCCESS) {
- throw new IllegalArgumentException(
- "Verifier can only set STATE_SUCCESS or codes greater than or equal to "
- + "STATE_FIRST_VERIFIER_DEFINED");
+ return DomainVerificationManager.ERROR_INVALID_STATE_CODE;
}
}
- setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, domains,
- state);
+ return setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId,
+ domains, state);
}
+ @DomainVerificationManager.Error
@Override
- public void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+ public int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
@NonNull Set<String> domains, int state)
- throws InvalidDomainSetException, NameNotFoundException {
+ throws NameNotFoundException {
mEnforcer.assertApprovedVerifier(callingUid, mProxy);
synchronized (mLock) {
List<String> verifiedDomains = new ArrayList<>();
- DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+ GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
true /* forAutoVerify */, callingUid, null /* userId */);
+ if (result.isError()) {
+ return result.getErrorCode();
+ }
+
+ DomainVerificationPkgState pkgState = result.getPkgState();
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
for (String domain : domains) {
Integer previousState = stateMap.get(domain);
if (previousState != null
- && !DomainVerificationManager.isStateModifiable(previousState)) {
+ && !DomainVerificationState.isModifiable(previousState)) {
continue;
}
- if (DomainVerificationManager.isStateVerified(state)) {
+ if (DomainVerificationState.isVerified(state)) {
verifiedDomains.add(domain);
}
@@ -318,6 +330,7 @@
}
mConnection.scheduleWriteSettings();
+ return DomainVerificationManager.STATUS_OK;
}
@Override
@@ -460,17 +473,24 @@
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
- pkgState.getOrCreateUserState(userId)
- .setLinkHandlingAllowed(allowed);
+ if (userId == UserHandle.USER_ALL) {
+ for (int aUserId : mConnection.getAllUserIds()) {
+ pkgState.getOrCreateUserState(aUserId)
+ .setLinkHandlingAllowed(allowed);
+ }
+ } else {
+ pkgState.getOrCreateUserState(userId)
+ .setLinkHandlingAllowed(allowed);
+ }
}
}
mConnection.scheduleWriteSettings();
}
- public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+ public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
- throws InvalidDomainSetException, NameNotFoundException {
+ throws NameNotFoundException {
synchronized (mLock) {
final int callingUid = mConnection.getCallingUid();
// Pass null for package name here and do the app visibility enforcement inside
@@ -478,14 +498,17 @@
// ID reason if the target app is invisible
if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
null /* packageName */, userId)) {
- throw new InvalidDomainSetException(domainSetId, null,
- InvalidDomainSetException.REASON_ID_INVALID);
+ return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID;
}
- DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+ GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
false /* forAutoVerify */, callingUid, userId);
- DomainVerificationInternalUserState userState =
- pkgState.getOrCreateUserState(userId);
+ if (result.isError()) {
+ return result.getErrorCode();
+ }
+
+ DomainVerificationPkgState pkgState = result.getPkgState();
+ DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(userId);
// Disable other packages if approving this one. Note that this check is only done for
// enabling. This allows an escape hatch in case multiple packages somehow get selected.
@@ -503,8 +526,7 @@
userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked);
int highestApproval = packagesToLevel.second;
if (highestApproval > APPROVAL_LEVEL_SELECTION) {
- throw new InvalidDomainSetException(domainSetId, null,
- InvalidDomainSetException.REASON_UNABLE_TO_APPROVE);
+ return DomainVerificationManager.ERROR_UNABLE_TO_APPROVE;
}
domainToApprovedPackages.put(domain, packagesToLevel.first);
@@ -544,6 +566,7 @@
}
mConnection.scheduleWriteSettings();
+ return DomainVerificationManager.STATUS_OK;
}
@Override
@@ -636,16 +659,16 @@
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
synchronized (mLock) {
- DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
- if (pkgState == null) {
- return null;
- }
-
AndroidPackage pkg = mConnection.getPackageLocked(packageName);
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+ if (pkgState == null) {
+ return null;
+ }
+
ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
int webDomainsSize = webDomains.size();
@@ -659,7 +682,7 @@
Integer state = stateMap.get(host);
int domainState;
- if (state != null && DomainVerificationManager.isStateVerified(state)) {
+ if (state != null && DomainVerificationState.isVerified(state)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
} else if (enabledHosts.contains(host)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
@@ -793,19 +816,12 @@
Integer oldStateInteger = oldStateMap.get(domain);
if (oldStateInteger != null) {
int oldState = oldStateInteger;
- switch (oldState) {
- case DomainVerificationState.STATE_SUCCESS:
- case DomainVerificationState.STATE_RESTORED:
- case DomainVerificationState.STATE_MIGRATED:
- newStateMap.put(domain, oldState);
- break;
- default:
- // In all other cases, the state code is left unset
- // (STATE_NO_RESPONSE) to signal to the verification agent that any
- // existing error has been cleared and the domain should be
- // re-attempted. This makes update of a package a signal to
- // re-verify.
- break;
+ // If the following case fails, the state code is left unset
+ // (STATE_NO_RESPONSE) to signal to the verification agent that any existing
+ // error has been cleared and the domain should be re-attempted. This makes
+ // update of a package a signal to re-verify.
+ if (DomainVerificationState.shouldMigrate(oldState)) {
+ newStateMap.put(domain, oldState);
}
}
}
@@ -858,13 +874,13 @@
boolean sendBroadcast = true;
DomainVerificationPkgState pkgState;
- pkgState = mSettings.getPendingState(pkgName);
+ pkgState = mSettings.removePendingState(pkgName);
if (pkgState != null) {
// Don't send when attaching from pending read, which is usually boot scan. Re-send on
// boot is handled in a separate method once all packages are added.
sendBroadcast = false;
} else {
- pkgState = mSettings.getRestoredState(pkgName);
+ pkgState = mSettings.removeRestoredState(pkgName);
}
AndroidPackage pkg = newPkgSetting.getPkg();
@@ -872,7 +888,7 @@
boolean hasAutoVerifyDomains = !domains.isEmpty();
boolean isPendingOrRestored = pkgState != null;
if (isPendingOrRestored) {
- pkgState.setId(domainSetId);
+ pkgState = new DomainVerificationPkgState(pkgState, domainSetId, hasAutoVerifyDomains);
} else {
pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains);
}
@@ -1097,28 +1113,25 @@
* @param userIdForFilter which user to filter app access to, or null if the caller has already
* validated package visibility
*/
+ @CheckResult
@GuardedBy("mLock")
- private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
+ private GetAttachedResult getAndValidateAttachedLocked(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
- @Nullable Integer userIdForFilter)
- throws InvalidDomainSetException, NameNotFoundException {
+ @Nullable Integer userIdForFilter) throws NameNotFoundException {
if (domainSetId == null) {
- throw new InvalidDomainSetException(null, null,
- InvalidDomainSetException.REASON_ID_NULL);
+ return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL);
}
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId);
if (pkgState == null) {
- throw new InvalidDomainSetException(domainSetId, null,
- InvalidDomainSetException.REASON_ID_INVALID);
+ return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID);
}
String pkgName = pkgState.getPackageName();
if (userIdForFilter != null
&& mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) {
- throw new InvalidDomainSetException(domainSetId, null,
- InvalidDomainSetException.REASON_ID_INVALID);
+ return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID);
}
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
@@ -1127,8 +1140,8 @@
}
if (CollectionUtils.isEmpty(domains)) {
- throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
- InvalidDomainSetException.REASON_SET_NULL_OR_EMPTY);
+ return GetAttachedResult.error(
+ DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY);
}
AndroidPackage pkg = pkgSetting.getPkg();
ArraySet<String> declaredDomains = forAutoVerify
@@ -1136,11 +1149,10 @@
: mCollector.collectAllWebDomains(pkg);
if (domains.retainAll(declaredDomains)) {
- throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
- InvalidDomainSetException.REASON_UNKNOWN_DOMAIN);
+ return GetAttachedResult.error(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN);
}
- return pkgState;
+ return GetAttachedResult.success(pkgState);
}
@Override
@@ -1185,7 +1197,7 @@
/**
* Determine whether or not a broadcast should be sent at boot for the given {@param pkgState}.
* Sends only if the only states recorded are default as decided by {@link
- * DomainVerificationManager#isStateDefault(int)}.
+ * DomainVerificationState#isDefault(int)}.
*
* If any other state is set, it's assumed that the domain verification agent is aware of the
* package and has already scheduled future verification requests.
@@ -1199,7 +1211,7 @@
int statesSize = stateMap.size();
for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
Integer state = stateMap.valueAt(stateIndex);
- if (!DomainVerificationManager.isStateDefault(state)) {
+ if (!DomainVerificationState.isDefault(state)) {
return false;
}
}
@@ -1318,83 +1330,50 @@
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
String domain = intent.getData().getHost();
- // Collect package names
- ArrayMap<String, Integer> packageApprovals = new ArrayMap<>();
+ // Collect valid infos
+ ArrayMap<ResolveInfo, Integer> infoApprovals = new ArrayMap<>();
int infosSize = infos.size();
for (int index = 0; index < infosSize; index++) {
- packageApprovals.put(infos.get(index).getComponentInfo().packageName,
- APPROVAL_LEVEL_NONE);
+ final ResolveInfo info = infos.get(index);
+ // Only collect for intent filters that can auto resolve
+ if (info.isAutoResolutionAllowed()) {
+ infoApprovals.put(info, null);
+ }
}
// Find all approval levels
- int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId,
+ int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId,
pkgSettingFunction);
if (highestApproval == APPROVAL_LEVEL_NONE) {
return Pair.create(emptyList(), highestApproval);
}
- // Filter to highest, non-zero packages
- ArraySet<String> approvedPackages = new ArraySet<>();
- int approvalsSize = packageApprovals.size();
- for (int index = 0; index < approvalsSize; index++) {
- if (packageApprovals.valueAt(index) == highestApproval) {
- approvedPackages.add(packageApprovals.keyAt(index));
+ // Filter to highest, non-zero infos
+ for (int index = infoApprovals.size() - 1; index >= 0; index--) {
+ if (infoApprovals.valueAt(index) != highestApproval) {
+ infoApprovals.removeAt(index);
}
}
- ArraySet<String> filteredPackages = new ArraySet<>();
- if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+ if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) {
// To maintain legacy behavior while the Settings API is not implemented,
// show the chooser if all approved apps are marked ask, skipping the
// last app, last declaration filtering.
- filteredPackages.addAll(approvedPackages);
- } else {
- // Filter to last installed package
- long latestInstall = Long.MIN_VALUE;
- int approvedSize = approvedPackages.size();
- for (int index = 0; index < approvedSize; index++) {
- String packageName = approvedPackages.valueAt(index);
- PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
- if (pkgSetting == null) {
- continue;
- }
- long installTime = pkgSetting.getFirstInstallTime();
- if (installTime > latestInstall) {
- latestInstall = installTime;
- filteredPackages.clear();
- filteredPackages.add(packageName);
- } else if (installTime == latestInstall) {
- filteredPackages.add(packageName);
- }
- }
+ filterToLastFirstInstalled(infoApprovals, pkgSettingFunction);
}
- // Filter to approved ResolveInfos
- ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>();
- for (int index = 0; index < infosSize; index++) {
- ResolveInfo info = infos.get(index);
- String packageName = info.getComponentInfo().packageName;
- if (filteredPackages.contains(packageName)) {
- List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName);
- if (infosPerPackage == null) {
- infosPerPackage = new ArrayList<>();
- approvedInfos.put(packageName, infosPerPackage);
- }
- infosPerPackage.add(info);
- }
+ // Easier to transform into list as the filterToLastDeclared method
+ // requires swapping indexes, which doesn't work with ArrayMap keys
+ final int size = infoApprovals.size();
+ List<ResolveInfo> finalList = new ArrayList<>(size);
+ for (int index = 0; index < size; index++) {
+ finalList.add(infoApprovals.keyAt(index));
}
- List<ResolveInfo> finalList;
- if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
- // If legacy ask, skip the last declaration filtering
- finalList = new ArrayList<>();
- int size = approvedInfos.size();
- for (int index = 0; index < size; index++) {
- finalList.addAll(approvedInfos.valueAt(index));
- }
- } else {
+ // If legacy ask, skip the last declaration filtering
+ if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) {
// Find the last declared ResolveInfo per package
- finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction);
+ filterToLastDeclared(finalList, pkgSettingFunction);
}
return Pair.create(finalList, highestApproval);
@@ -1403,68 +1382,127 @@
/**
* @return highest approval level found
*/
- private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap,
+ @ApprovalLevel
+ private int fillMapWithApprovalLevels(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
@NonNull String domain, @UserIdInt int userId,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
int highestApproval = APPROVAL_LEVEL_NONE;
int size = inputMap.size();
for (int index = 0; index < size; index++) {
- String packageName = inputMap.keyAt(index);
+ if (inputMap.valueAt(index) != null) {
+ // Already filled by previous iteration
+ continue;
+ }
+
+ ResolveInfo info = inputMap.keyAt(index);
+ final String packageName = info.getComponentInfo().packageName;
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
if (pkgSetting == null) {
- inputMap.setValueAt(index, APPROVAL_LEVEL_NONE);
+ fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE);
continue;
}
int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
highestApproval = Math.max(highestApproval, approval);
- inputMap.setValueAt(index, approval);
+ fillInfoMapForSamePackage(inputMap, packageName, approval);
}
return highestApproval;
}
- @NonNull
- private List<ResolveInfo> filterToLastDeclared(
- @NonNull ArrayMap<String, List<ResolveInfo>> inputMap,
- @NonNull Function<String, PackageSetting> pkgSettingFunction) {
- List<ResolveInfo> finalList = new ArrayList<>(inputMap.size());
+ private void fillInfoMapForSamePackage(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
+ @NonNull String targetPackageName, @ApprovalLevel int level) {
+ final int size = inputMap.size();
+ for (int index = 0; index < size; index++) {
+ final String packageName = inputMap.keyAt(index).getComponentInfo().packageName;
+ if (Objects.equals(targetPackageName, packageName)) {
+ inputMap.setValueAt(index, level);
+ }
+ }
+ }
- int inputSize = inputMap.size();
- for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) {
- String packageName = inputMap.keyAt(inputIndex);
- List<ResolveInfo> infos = inputMap.valueAt(inputIndex);
+ @NonNull
+ private void filterToLastFirstInstalled(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ // First, find the package with the latest first install time
+ String targetPackageName = null;
+ long latestInstall = Long.MIN_VALUE;
+ final int size = inputMap.size();
+ for (int index = 0; index < size; index++) {
+ ResolveInfo info = inputMap.keyAt(index);
+ String packageName = info.getComponentInfo().packageName;
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+ if (pkgSetting == null) {
+ continue;
+ }
+
+ long installTime = pkgSetting.getFirstInstallTime();
+ if (installTime > latestInstall) {
+ latestInstall = installTime;
+ targetPackageName = packageName;
+ }
+ }
+
+ // Then, remove all infos that don't match the package
+ for (int index = inputMap.size() - 1; index >= 0; index--) {
+ ResolveInfo info = inputMap.keyAt(index);
+ if (!Objects.equals(targetPackageName, info.getComponentInfo().packageName)) {
+ inputMap.removeAt(index);
+ }
+ }
+ }
+
+ @NonNull
+ private void filterToLastDeclared(@NonNull List<ResolveInfo> inputList,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ // Must call size each time as the size of the list will decrease
+ for (int index = 0; index < inputList.size(); index++) {
+ ResolveInfo info = inputList.get(index);
+ String targetPackageName = info.getComponentInfo().packageName;
+ PackageSetting pkgSetting = pkgSettingFunction.apply(targetPackageName);
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
if (pkg == null) {
continue;
}
- ResolveInfo result = null;
- int highestIndex = -1;
- int infosSize = infos.size();
- for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) {
- ResolveInfo info = infos.get(infoIndex);
- List<ParsedActivity> activities = pkg.getActivities();
- int activitiesSize = activities.size();
- for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
- if (Objects.equals(activities.get(activityIndex).getComponentName(),
- info.getComponentInfo().getComponentName())) {
- if (activityIndex > highestIndex) {
- highestIndex = activityIndex;
- result = info;
- }
- break;
- }
+ ResolveInfo result = info;
+ int highestIndex = indexOfIntentFilterEntry(pkg, result);
+
+ // Search backwards so that lower results can be removed as they're found
+ for (int searchIndex = inputList.size() - 1; searchIndex >= index + 1; searchIndex--) {
+ ResolveInfo searchInfo = inputList.get(searchIndex);
+ if (!Objects.equals(targetPackageName, searchInfo.getComponentInfo().packageName)) {
+ continue;
}
+
+ int entryIndex = indexOfIntentFilterEntry(pkg, searchInfo);
+ if (entryIndex > highestIndex) {
+ highestIndex = entryIndex;
+ result = searchInfo;
+ }
+
+ // Always remove the entry so that the current index
+ // is left as the sole candidate of the target package
+ inputList.remove(searchIndex);
}
- // Shouldn't be null, but might as well be safe
- if (result != null) {
- finalList.add(result);
+ // Swap the current index for the result, leaving this as
+ // the only entry with the target package name
+ inputList.set(index, result);
+ }
+ }
+
+ private int indexOfIntentFilterEntry(@NonNull AndroidPackage pkg,
+ @NonNull ResolveInfo target) {
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ if (Objects.equals(activities.get(activityIndex).getComponentName(),
+ target.getComponentInfo().getComponentName())) {
+ return activityIndex;
}
}
- return finalList;
+ return -1;
}
@Override
@@ -1472,8 +1510,7 @@
@NonNull List<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) {
String packageName = pkgSetting.getName();
- if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates,
- resolveInfoFlags)) {
+ if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) {
if (DEBUG_APPROVAL) {
debugApproval(packageName, intent, userId, false, "not valid intent");
}
@@ -1542,7 +1579,7 @@
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
// Check if the exact host matches
Integer state = stateMap.get(host);
- if (state != null && DomainVerificationManager.isStateVerified(state)) {
+ if (state != null && DomainVerificationState.isVerified(state)) {
if (DEBUG_APPROVAL) {
debugApproval(packageName, debugObject, userId, true,
"host verified exactly");
@@ -1553,7 +1590,7 @@
// Otherwise see if the host matches a verified domain by wildcard
int stateMapSize = stateMap.size();
for (int index = 0; index < stateMapSize; index++) {
- if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+ if (!DomainVerificationState.isVerified(stateMap.valueAt(index))) {
continue;
}
@@ -1672,4 +1709,40 @@
Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for "
+ debugObject + " for user " + userId + ": " + reason);
}
+
+ private static class GetAttachedResult {
+
+ @Nullable
+ private DomainVerificationPkgState mPkgState;
+
+ private int mErrorCode;
+
+ GetAttachedResult(@Nullable DomainVerificationPkgState pkgState, int errorCode) {
+ mPkgState = pkgState;
+ mErrorCode = errorCode;
+ }
+
+ @NonNull
+ static GetAttachedResult error(@DomainVerificationManager.Error int errorCode) {
+ return new GetAttachedResult(null, errorCode);
+ }
+
+ @NonNull
+ static GetAttachedResult success(@NonNull DomainVerificationPkgState pkgState) {
+ return new GetAttachedResult(pkgState, DomainVerificationManager.STATUS_OK);
+ }
+
+ @NonNull
+ DomainVerificationPkgState getPkgState() {
+ return mPkgState;
+ }
+
+ boolean isError() {
+ return mErrorCode != DomainVerificationManager.STATUS_OK;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index f3d1dbb..8b59da7 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -256,16 +256,16 @@
}
@Nullable
- public DomainVerificationPkgState getPendingState(@NonNull String pkgName) {
+ public DomainVerificationPkgState removePendingState(@NonNull String pkgName) {
synchronized (mLock) {
- return mPendingPkgStates.get(pkgName);
+ return mPendingPkgStates.remove(pkgName);
}
}
@Nullable
- public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) {
+ public DomainVerificationPkgState removeRestoredState(@NonNull String pkgName) {
synchronized (mLock) {
- return mRestoredPkgStates.get(pkgName);
+ return mRestoredPkgStates.remove(pkgName);
}
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index 94767f5..7e755fa 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -38,6 +38,8 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
+import java.util.function.Function;
public class DomainVerificationShell {
@@ -226,22 +228,19 @@
userId = translateUserId(userId, "runSetAppLinksUserState");
- String enabledString = commandHandler.getNextArgRequired();
+ String enabledArg = commandHandler.getNextArg();
+ if (TextUtils.isEmpty(enabledArg)) {
+ commandHandler.getErrPrintWriter().println("Error: enabled param not specified");
+ return false;
+ }
- // Manually ensure that "true" and "false" are the only options, to ensure a domain isn't
- // accidentally parsed as a boolean
boolean enabled;
- switch (enabledString) {
- case "true":
- enabled = true;
- break;
- case "false":
- enabled = false;
- break;
- default:
- commandHandler.getErrPrintWriter().println(
- "Invalid enabled param: " + enabledString);
- return false;
+ try {
+ enabled = parseEnabled(enabledArg);
+ } catch (IllegalArgumentException e) {
+ commandHandler.getErrPrintWriter()
+ .println("Error: invalid enabled param: " + e.getMessage());
+ return false;
}
ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
@@ -255,8 +254,8 @@
}
try {
- mCallback.setDomainVerificationUserSelectionInternal(userId,
- packageName, enabled, domains);
+ mCallback.setDomainVerificationUserSelectionInternal(userId, packageName, enabled,
+ domains);
} catch (NameNotFoundException e) {
commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
return false;
@@ -362,15 +361,12 @@
private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) {
String packageName = null;
Integer userId = null;
- Boolean allowed = null;
String option;
while ((option = commandHandler.getNextOption()) != null) {
if (option.equals("--package")) {
- packageName = commandHandler.getNextArgRequired();
- } if (option.equals("--user")) {
+ packageName = commandHandler.getNextArg();
+ } else if (option.equals("--user")) {
userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
- } else if (allowed == null) {
- allowed = Boolean.valueOf(option);
} else {
commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option);
return false;
@@ -389,11 +385,21 @@
return false;
}
- if (allowed == null) {
+ String allowedArg = commandHandler.getNextArg();
+ if (TextUtils.isEmpty(allowedArg)) {
commandHandler.getErrPrintWriter().println("Error: allowed setting not specified");
return false;
}
+ boolean allowed;
+ try {
+ allowed = parseEnabled(allowedArg);
+ } catch (IllegalArgumentException e) {
+ commandHandler.getErrPrintWriter()
+ .println("Error: invalid allowed setting: " + e.getMessage());
+ return false;
+ }
+
userId = translateUserId(userId, "runSetAppLinksAllowed");
try {
@@ -422,6 +428,22 @@
}
/**
+ * Manually ensure that "true" and "false" are the only options, to ensure a domain isn't
+ * accidentally parsed as a boolean.
+ */
+ @NonNull
+ private boolean parseEnabled(@NonNull String arg) throws IllegalArgumentException {
+ switch (arg.toLowerCase(Locale.US)) {
+ case "true":
+ return true;
+ case "false":
+ return false;
+ default:
+ throw new IllegalArgumentException(arg + " is not a valid boolean");
+ }
+ }
+
+ /**
* Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are
* even more internal, and so that testing is easier.
*/
@@ -498,7 +520,8 @@
void verifyPackages(@Nullable List<String> packageNames, boolean reVerify);
/**
- * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer)
+ * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer,
+ * Function)
*/
void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable @UserIdInt Integer userId) throws NameNotFoundException;
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 883bbad..cb3b5c9 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.content.pm.ResolveInfo;
import android.os.Binder;
import com.android.internal.util.CollectionUtils;
@@ -30,7 +29,6 @@
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
-import java.util.List;
import java.util.Set;
public final class DomainVerificationUtils {
@@ -46,7 +44,6 @@
}
public static boolean isDomainVerificationIntent(Intent intent,
- @NonNull List<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags) {
if (!intent.isWebIntent()) {
return false;
@@ -63,42 +60,18 @@
&& intent.hasCategory(Intent.CATEGORY_BROWSABLE);
}
- // In cases where at least one browser is resolved and only one non-browser is resolved,
- // the Intent is coerced into an app links intent, under the assumption the browser can
- // be skipped if the app is approved at any level for the domain.
- boolean foundBrowser = false;
- boolean foundOneApp = false;
-
- final int candidatesSize = candidates.size();
- for (int index = 0; index < candidatesSize; index++) {
- final ResolveInfo info = candidates.get(index);
- if (info.handleAllWebDataURI) {
- foundBrowser = true;
- } else if (foundOneApp) {
- // Already true, so duplicate app
- foundOneApp = false;
- break;
- } else {
- foundOneApp = true;
- }
- }
-
boolean matchDefaultByFlags = (resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
- boolean onlyOneNonBrowser = foundBrowser && foundOneApp;
// Check if matches (BROWSABLE || none) && DEFAULT
if (categoriesSize == 0) {
- // No categories, run coerce case, matching DEFAULT by flags
- return onlyOneNonBrowser && matchDefaultByFlags;
- } else if (intent.hasCategory(Intent.CATEGORY_DEFAULT)) {
- // Run coerce case, matching by explicit DEFAULT
- return onlyOneNonBrowser;
+ // No categories, only allow matching DEFAULT by flags
+ return matchDefaultByFlags;
} else if (intent.hasCategory(Intent.CATEGORY_BROWSABLE)) {
// Intent matches BROWSABLE, must match DEFAULT by flags
return matchDefaultByFlags;
} else {
- // Otherwise not matching any app link categories
- return false;
+ // Otherwise only needs to have DEFAULT
+ return intent.hasCategory(Intent.CATEGORY_DEFAULT);
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
index a089a60..40c7091 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -68,6 +68,12 @@
this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0));
}
+ public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState,
+ @NonNull UUID id, boolean hasAutoVerifyDomains) {
+ this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(),
+ pkgState.getUserStates());
+ }
+
@Nullable
public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) {
return mUserStates.get(userId);
@@ -84,10 +90,6 @@
return userState;
}
- public void setId(@NonNull UUID id) {
- mId = id;
- }
-
public void removeUser(@UserIdInt int userId) {
mUserStates.remove(userId);
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 18042af..fa36683 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -207,20 +207,24 @@
int callingUid = response.callingUid;
if (!successfulDomains.isEmpty()) {
try {
- mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
- successfulDomains, DomainVerificationState.STATE_SUCCESS);
- } catch (DomainVerificationManager.InvalidDomainSetException
- | PackageManager.NameNotFoundException e) {
+ if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ successfulDomains, DomainVerificationState.STATE_SUCCESS)
+ != DomainVerificationManager.STATUS_OK) {
+ Slog.e(TAG, "Failure reporting successful domains for " + packageName);
+ }
+ } catch (Exception e) {
Slog.e(TAG, "Failure reporting successful domains for " + packageName, e);
}
}
if (!failedDomains.isEmpty()) {
try {
- mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
- failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE);
- } catch (DomainVerificationManager.InvalidDomainSetException
- | PackageManager.NameNotFoundException e) {
+ if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+ failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE)
+ != DomainVerificationManager.STATUS_OK) {
+ Slog.e(TAG, "Failure reporting failed domains for " + packageName);
+ }
+ } catch (Exception e) {
Slog.e(TAG, "Failure reporting failed domains for " + packageName, e);
}
}
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 9c3a394..5b48abb 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -24,11 +24,11 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
-import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
@@ -113,7 +113,7 @@
private boolean mDeviceProvisioned = false;
private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
private boolean mIsWaitingForEcmExit = false;
- private boolean mHasTelephony;
+ private final boolean mHasTelephony;
private boolean mHasVibrator;
private final boolean mShowSilentToggle;
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
@@ -137,9 +137,8 @@
filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
context.registerReceiver(mBroadcastReceiver, filter);
- ConnectivityManager cm = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ mHasTelephony =
+ context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
// get notified of phone state changes
TelephonyManager telephonyManager =
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ffea6a7..d0aa28b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2235,9 +2235,9 @@
/** {@inheritDoc} */
@Override
- public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
- CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
- int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+ public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+ int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+ int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
if (!SHOW_SPLASH_SCREENS) {
return null;
}
@@ -2264,10 +2264,12 @@
if (theme != context.getThemeResId() || labelRes != 0) {
try {
- context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
+ context = context.createPackageContextAsUser(packageName, CONTEXT_RESTRICTED,
+ UserHandle.of(userId));
context.setTheme(theme);
} catch (PackageManager.NameNotFoundException e) {
- // Ignore
+ Slog.w(TAG, "Failed creating package context with package name "
+ + packageName + " for user " + userId, e);
}
}
@@ -4249,12 +4251,6 @@
}
mPowerManager.wakeUp(wakeTime, reason, details);
-
- // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
- final HdmiControl hdmiControl = getHdmiControl();
- if (hdmiControl != null) {
- hdmiControl.turnOnTv();
- }
return true;
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 0735977b..d512edf 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -714,9 +714,9 @@
* @return The starting surface.
*
*/
- public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
- CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
- int logo, int windowFlags, Configuration overrideConfig, int displayId);
+ StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+ int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+ int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId);
/**
* Set or clear a window which can behave as the keyguard.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index a95628f..44f14b4 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -258,8 +258,12 @@
}
}
+ /**
+ * @deprecated Notify occlude status change via remote animation.
+ */
+ @Deprecated
public void setOccluded(boolean isOccluded, boolean animate) {
- if (mKeyguardService != null) {
+ if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
mKeyguardService.setOccluded(isOccluded, animate);
}
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index fd2d8e1..beebb31 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -16,6 +16,8 @@
package com.android.server.recoverysystem;
+import static android.os.UserHandle.USER_SYSTEM;
+
import android.annotation.IntDef;
import android.content.Context;
import android.content.IntentSender;
@@ -33,12 +35,14 @@
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.RebootEscrowListener;
import com.android.server.LocalServices;
@@ -52,6 +56,8 @@
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
/**
* The recovery system service is responsible for coordinating recovery related
@@ -127,10 +133,28 @@
/**
* The action to perform upon resume on reboot clear request for a given client.
*/
- @IntDef({ROR_NOT_REQUESTED,
+ @IntDef({ ROR_NOT_REQUESTED,
ROR_REQUESTED_NEED_CLEAR,
ROR_REQUESTED_SKIP_CLEAR})
- private @interface ResumeOnRebootActionsOnClear{}
+ private @interface ResumeOnRebootActionsOnClear {}
+
+ /**
+ * The error code for reboots initiated by resume on reboot clients.
+ */
+ private static final int REBOOT_ERROR_NONE = 0;
+ private static final int REBOOT_ERROR_UNKNOWN = 1;
+ private static final int REBOOT_ERROR_INVALID_PACKAGE_NAME = 2;
+ private static final int REBOOT_ERROR_LSKF_NOT_CAPTURED = 3;
+ private static final int REBOOT_ERROR_SLOT_MISMATCH = 4;
+ private static final int REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE = 5;
+
+ @IntDef({ REBOOT_ERROR_NONE,
+ REBOOT_ERROR_UNKNOWN,
+ REBOOT_ERROR_INVALID_PACKAGE_NAME,
+ REBOOT_ERROR_LSKF_NOT_CAPTURED,
+ REBOOT_ERROR_SLOT_MISMATCH,
+ REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE})
+ private @interface ResumeOnRebootRebootErrorCode {}
static class Injector {
protected final Context mContext;
@@ -202,6 +226,35 @@
public void threadSleep(long millis) throws InterruptedException {
Thread.sleep(millis);
}
+
+ public int getUidFromPackageName(String packageName) {
+ try {
+ return mContext.getPackageManager().getPackageUidAsUser(packageName, USER_SYSTEM);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed to find uid for " + packageName);
+ }
+ return -1;
+ }
+
+ public void reportRebootEscrowPreparationMetrics(int uid,
+ @ResumeOnRebootActionsOnRequest int requestResult, int requestedClientCount) {
+ FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_PREPARATION_REPORTED, uid,
+ requestResult, requestedClientCount);
+ }
+
+ public void reportRebootEscrowLskfCapturedMetrics(int uid, int requestedClientCount,
+ int requestedToLskfCapturedDurationInSeconds) {
+ FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_LSKF_CAPTURE_REPORTED, uid,
+ requestedClientCount, requestedToLskfCapturedDurationInSeconds);
+ }
+
+ public void reportRebootEscrowRebootMetrics(int errorCode, int uid,
+ int preparedClientCount, int requestCount, boolean slotSwitch, boolean serverBased,
+ int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts) {
+ FrameworkStatsLog.write(FrameworkStatsLog.REBOOT_ESCROW_REBOOT_REPORTED, errorCode,
+ uid, preparedClientCount, requestCount, slotSwitch, serverBased,
+ lskfCapturedToRebootDurationInSeconds, lskfCapturedCounts);
+ }
}
/**
@@ -367,6 +420,16 @@
}
}
+ private void reportMetricsOnRequestLskf(String packageName, int requestResult) {
+ int uid = mInjector.getUidFromPackageName(packageName);
+ int pendingRequestCount;
+ synchronized (this) {
+ pendingRequestCount = mCallerPendingRequest.size();
+ }
+
+ mInjector.reportRebootEscrowPreparationMetrics(uid, requestResult, pendingRequestCount);
+ }
+
@Override // Binder call
public boolean requestLskf(String packageName, IntentSender intentSender) {
enforcePermissionForResumeOnReboot();
@@ -378,6 +441,8 @@
@ResumeOnRebootActionsOnRequest int action = updateRoRPreparationStateOnNewRequest(
packageName, intentSender);
+ reportMetricsOnRequestLskf(packageName, action);
+
switch (action) {
case ROR_SKIP_PREPARATION_AND_NOTIFY:
// We consider the preparation done if someone else has prepared.
@@ -420,12 +485,26 @@
return needPreparation ? ROR_NEED_PREPARATION : ROR_SKIP_PREPARATION_NOT_NOTIFY;
}
+ private void reportMetricsOnPreparedForReboot() {
+ List<String> preparedClients;
+ synchronized (this) {
+ preparedClients = new ArrayList<>(mCallerPreparedForReboot);
+ }
+
+ for (String packageName : preparedClients) {
+ int uid = mInjector.getUidFromPackageName(packageName);
+ mInjector.reportRebootEscrowLskfCapturedMetrics(uid, preparedClients.size(),
+ -1 /* duration */);
+ }
+ }
+
@Override
public void onPreparedForReboot(boolean ready) {
if (!ready) {
return;
}
updateRoRPreparationStateOnPreparedForReboot();
+ reportMetricsOnPreparedForReboot();
}
private synchronized void updateRoRPreparationStateOnPreparedForReboot() {
@@ -548,22 +627,49 @@
return true;
}
- private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) {
+ private @ResumeOnRebootRebootErrorCode int armRebootEscrow(String packageName,
+ boolean slotSwitch) {
if (packageName == null) {
Slog.w(TAG, "Missing packageName when rebooting with lskf.");
- return false;
+ return REBOOT_ERROR_INVALID_PACKAGE_NAME;
}
if (!isLskfCaptured(packageName)) {
- return false;
+ return REBOOT_ERROR_LSKF_NOT_CAPTURED;
}
if (!verifySlotForNextBoot(slotSwitch)) {
- return false;
+ return REBOOT_ERROR_SLOT_MISMATCH;
}
- // TODO(xunchang) write the vbmeta digest along with the escrowKey before reboot.
if (!mInjector.getLockSettingsService().armRebootEscrow()) {
Slog.w(TAG, "Failure to escrow key for reboot");
+ return REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE;
+ }
+
+ return REBOOT_ERROR_NONE;
+ }
+
+ 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);
+ int preparedClientCount;
+ synchronized (this) {
+ preparedClientCount = mCallerPreparedForReboot.size();
+ }
+
+ // TODO(b/179105110) report the true value of duration and counts
+ mInjector.reportRebootEscrowRebootMetrics(errorCode, uid, preparedClientCount,
+ 1 /* request count */, slotSwitch, serverBased,
+ -1 /* duration */, 1 /* lskf capture count */);
+ }
+
+ private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) {
+ @ResumeOnRebootRebootErrorCode int errorCode = armRebootEscrow(packageName, slotSwitch);
+ reportMetricsOnRebootWithLskf(packageName, slotSwitch, errorCode);
+
+ if (errorCode != REBOOT_ERROR_NONE) {
return false;
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 8023fd4..a7b9e95 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -25,7 +25,16 @@
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkIdentity.OEM_PAID;
+import static android.net.NetworkIdentity.OEM_PRIVATE;
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
+import static android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD;
+import static android.net.NetworkTemplate.MATCH_WIFI_WILDCARD;
import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
+import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
import static android.net.NetworkTemplate.buildTemplateMobileWithRatType;
import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
@@ -435,6 +444,7 @@
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER:
+ case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER:
synchronized (mDataBytesTransferLock) {
return pullDataBytesTransferLocked(atomTag, data);
}
@@ -867,6 +877,8 @@
FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
mNetworkStatsBaselines.addAll(
collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
// Listen to subscription changes to record historical subscriptions that activated before
// pulling, this is used by {@code DATA_USAGE_BYTES_TRANSFER}.
@@ -879,6 +891,7 @@
registerMobileBytesTransferBackground();
registerBytesTransferByTagAndMetered();
registerDataUsageBytesTransfer();
+ registerOemManagedBytesTransfer();
}
/**
@@ -1057,7 +1070,7 @@
new int[] {TRANSPORT_WIFI, TRANSPORT_CELLULAR},
/*slicedByFgbg=*/false, /*slicedByTag=*/true,
/*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- /*subInfo=*/null));
+ /*subInfo=*/null, OEM_MANAGED_ALL));
}
break;
}
@@ -1067,6 +1080,10 @@
}
break;
}
+ case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER: {
+ ret.addAll(getDataUsageBytesTransferSnapshotForOemManaged());
+ break;
+ }
default:
throw new IllegalArgumentException("Unknown atomTag " + atomTag);
}
@@ -1094,7 +1111,7 @@
final NetworkStatsExt diff = new NetworkStatsExt(
item.stats.subtract(baseline.stats).removeEmptyEntries(), item.transports,
item.slicedByFgbg, item.slicedByTag, item.slicedByMetered, item.ratType,
- item.subInfo);
+ item.subInfo, item.oemManaged);
// If no diff, skip.
if (diff.stats.size() == 0) continue;
@@ -1106,6 +1123,9 @@
case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER:
addDataUsageBytesTransferAtoms(diff, pulledData);
break;
+ case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER:
+ addOemDataUsageBytesTransferAtoms(diff, pulledData);
+ break;
default:
addNetworkStats(atomTag, pulledData, diff);
}
@@ -1177,6 +1197,49 @@
}
}
+ private void addOemDataUsageBytesTransferAtoms(@NonNull NetworkStatsExt statsExt,
+ @NonNull List<StatsEvent> pulledData) {
+ final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
+ final int oemManaged = statsExt.oemManaged;
+ for (final int transport : statsExt.transports) {
+ for (int i = 0; i < statsExt.stats.size(); i++) {
+ statsExt.stats.getValues(i, entry);
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER, entry.uid, (entry.set > 0),
+ oemManaged, transport, entry.rxBytes, entry.rxPackets, entry.txBytes,
+ entry.txPackets));
+ }
+ }
+ }
+
+ @NonNull private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForOemManaged() {
+ final int[] transports = new int[] {MATCH_ETHERNET, MATCH_MOBILE_WILDCARD,
+ MATCH_WIFI_WILDCARD};
+ final int[] oemManagedTypes = new int[] {OEM_PAID | OEM_PRIVATE, OEM_PAID, OEM_PRIVATE};
+
+ final List<NetworkStatsExt> ret = new ArrayList<>();
+
+ for (final int transport : transports) {
+ for (final int oemManaged : oemManagedTypes) {
+ /* A null subscriberId will set wildcard=true, since we aren't trying to select a
+ specific ssid or subscriber. */
+ final NetworkTemplate template = new NetworkTemplate(transport,
+ /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null,
+ METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ oemManaged);
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(template, true);
+ if (stats != null) {
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+ new int[] {transport}, /*slicedByFgbg=*/true, /*slicedByTag=*/true,
+ /*slicedByMetered=*/true, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ /*subInfo=*/null, oemManaged));
+ }
+ }
+ }
+
+ return ret;
+ }
+
/**
* Create a snapshot of NetworkStats for a given transport.
*/
@@ -1229,7 +1292,8 @@
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
- /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo));
+ /*slicedByTag=*/false, /*slicedByMetered=*/false, ratType, subInfo,
+ OEM_MANAGED_ALL));
}
}
return ret;
@@ -1374,6 +1438,19 @@
);
}
+ private void registerOemManagedBytesTransfer() {
+ int tagId = FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER;
+ PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+ .setAdditiveFields(new int[] {5, 6, 7, 8})
+ .build();
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ metadata,
+ BackgroundThread.getExecutor(),
+ mStatsCallbackImpl
+ );
+ }
+
private void registerBluetoothBytesTransfer() {
int tagId = FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
diff --git a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
index 628c1d6..9f8b27f 100644
--- a/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
+++ b/services/core/java/com/android/server/stats/pull/SystemMemoryUtil.java
@@ -26,7 +26,7 @@
private SystemMemoryUtil() {}
static Metrics getMetrics() {
- int totalIonKb = (int) Debug.getIonHeapsSizeKb();
+ int totalIonKb = (int) Debug.getDmabufHeapTotalExportedKb();
int gpuTotalUsageKb = (int) Debug.getGpuTotalUsageKb();
int gpuDmaBufUsageKb = (int) Debug.getGpuDmaBufUsageKb();
int dmaBufTotalExportedKb = (int) Debug.getDmabufTotalExportedKb();
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
index 06c81ee..7dbba0d 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsExt.java
@@ -16,6 +16,8 @@
package com.android.server.stats.pull.netstats;
+import static android.net.NetworkTemplate.OEM_MANAGED_ALL;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkStats;
@@ -37,17 +39,18 @@
public final boolean slicedByTag;
public final boolean slicedByMetered;
public final int ratType;
+ public final int oemManaged;
@Nullable
public final SubInfo subInfo;
public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) {
this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false,
- TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null);
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, /*subInfo=*/null, OEM_MANAGED_ALL);
}
public NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg,
boolean slicedByTag, boolean slicedByMetered, int ratType,
- @Nullable SubInfo subInfo) {
+ @Nullable SubInfo subInfo, int oemManaged) {
this.stats = stats;
// Sort transports array so that we can test for equality without considering order.
@@ -59,6 +62,7 @@
this.slicedByMetered = slicedByMetered;
this.ratType = ratType;
this.subInfo = subInfo;
+ this.oemManaged = oemManaged;
}
/**
@@ -67,6 +71,7 @@
public boolean hasSameSlicing(@NonNull NetworkStatsExt other) {
return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg
&& slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered
- && ratType == other.ratType && Objects.equals(subInfo, other.subInfo);
+ && ratType == other.ratType && Objects.equals(subInfo, other.subInfo)
+ && oemManaged == other.oemManaged;
}
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 302a23f..c4f5575 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -36,6 +36,7 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.fingerprint.IUdfpsHbmListener;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -828,12 +829,24 @@
}
@Override
+ public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ enforceStatusBarService();
+ if (mBar != null) {
+ try {
+ mBar.setUdfpsHbmListener(listener);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ @Override
public void startTracing() {
if (mBar != null) {
try {
mBar.startTracing();
mTracingEnabled = true;
- } catch (RemoteException ex) {}
+ } catch (RemoteException ex) {
+ }
}
}
diff --git a/services/core/java/com/android/server/timedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
new file mode 100644
index 0000000..f00f856
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/ConfigurationInternal.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timedetector;
+
+import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
+
+import android.annotation.UserIdInt;
+import android.app.time.Capabilities.CapabilityState;
+import android.app.time.TimeCapabilities;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
+import android.os.UserHandle;
+
+import java.util.Objects;
+
+/**
+ * Holds configuration values that affect time behaviour.
+ */
+public final class ConfigurationInternal {
+
+ private final @UserIdInt int mUserId;
+ private final boolean mUserConfigAllowed;
+ private final boolean mAutoDetectionEnabled;
+
+ private ConfigurationInternal(Builder builder) {
+ mUserId = builder.mUserId;
+ mUserConfigAllowed = builder.mUserConfigAllowed;
+ mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
+ }
+
+ /** Returns a {@link TimeCapabilitiesAndConfig} objects based on configuration values. */
+ public TimeCapabilitiesAndConfig capabilitiesAndConfig() {
+ return new TimeCapabilitiesAndConfig(timeCapabilities(), timeConfiguration());
+ }
+
+ private TimeConfiguration timeConfiguration() {
+ return new TimeConfiguration.Builder()
+ .setAutoDetectionEnabled(mAutoDetectionEnabled)
+ .build();
+ }
+
+ private TimeCapabilities timeCapabilities() {
+ @CapabilityState int configureAutoTimeDetectionEnabledCapability =
+ mUserConfigAllowed
+ ? CAPABILITY_POSSESSED
+ : CAPABILITY_NOT_ALLOWED;
+
+ @CapabilityState int suggestTimeManuallyCapability =
+ mUserConfigAllowed
+ ? CAPABILITY_POSSESSED
+ : CAPABILITY_NOT_ALLOWED;
+
+ return new TimeCapabilities.Builder(UserHandle.of(mUserId))
+ .setConfigureAutoTimeDetectionEnabledCapability(
+ configureAutoTimeDetectionEnabledCapability)
+ .setSuggestTimeManuallyCapability(suggestTimeManuallyCapability)
+ .build();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ConfigurationInternal that = (ConfigurationInternal) o;
+ return mUserId == that.mUserId
+ && mUserConfigAllowed == that.mUserConfigAllowed
+ && mAutoDetectionEnabled == that.mAutoDetectionEnabled;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionEnabled);
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigurationInternal{"
+ + "mUserId=" + mUserId
+ + ", mUserConfigAllowed=" + mUserConfigAllowed
+ + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled
+ + '}';
+ }
+
+ static final class Builder {
+ private final @UserIdInt int mUserId;
+ private boolean mUserConfigAllowed;
+ private boolean mAutoDetectionEnabled;
+
+ Builder(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ Builder setUserConfigAllowed(boolean userConfigAllowed) {
+ mUserConfigAllowed = userConfigAllowed;
+ return this;
+ }
+
+ Builder setAutoDetectionEnabled(boolean autoDetectionEnabled) {
+ mAutoDetectionEnabled = autoDetectionEnabled;
+ return this;
+ }
+
+ ConfigurationInternal build() {
+ return new ConfigurationInternal(this);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 5cd1718..4f5e8fa 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -21,6 +21,7 @@
import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -28,6 +29,8 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.Slog;
@@ -71,6 +74,7 @@
@NonNull private final ContentResolver mContentResolver;
@NonNull private final PowerManager.WakeLock mWakeLock;
@NonNull private final AlarmManager mAlarmManager;
+ @NonNull private final UserManager mUserManager;
@NonNull private final int[] mOriginPriorities;
public EnvironmentImpl(@NonNull Context context) {
@@ -83,6 +87,8 @@
mAlarmManager = Objects.requireNonNull(context.getSystemService(AlarmManager.class));
+ mUserManager = Objects.requireNonNull(context.getSystemService(UserManager.class));
+
mSystemClockUpdateThresholdMillis =
SystemProperties.getInt("ro.sys.time_detector_update_diff",
SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
@@ -115,6 +121,14 @@
}
@Override
+ public ConfigurationInternal configurationInternal(@UserIdInt int userId) {
+ return new ConfigurationInternal.Builder(userId)
+ .setUserConfigAllowed(isUserConfigAllowed(userId))
+ .setAutoDetectionEnabled(isAutoTimeDetectionEnabled())
+ .build();
+ }
+
+ @Override
public void acquireWakeLock() {
if (mWakeLock.isHeld()) {
Slog.wtf(TAG, "WakeLock " + mWakeLock + " already held");
@@ -150,6 +164,11 @@
}
}
+ private boolean isUserConfigAllowed(@UserIdInt int userId) {
+ UserHandle userHandle = UserHandle.of(userId);
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
+ }
+
private static int[] getOriginPriorities(@NonNull Context context) {
String[] originStrings =
context.getResources().getStringArray(R.array.config_autoTimeSourcesPriority);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index b210339..eefa045a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -18,7 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
@@ -36,6 +39,7 @@
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
+import com.android.server.timezonedetector.CallerIdentityInjector;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -71,6 +75,7 @@
@NonNull private final Handler mHandler;
@NonNull private final Context mContext;
@NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
+ @NonNull private final CallerIdentityInjector mCallerIdentityInjector;
private static TimeDetectorService create(@NonNull Context context) {
TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context);
@@ -97,9 +102,42 @@
@VisibleForTesting
public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
@NonNull TimeDetectorStrategy timeDetectorStrategy) {
+ this(context, handler, timeDetectorStrategy, CallerIdentityInjector.REAL);
+ }
+
+ @VisibleForTesting
+ public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
+ @NonNull TimeDetectorStrategy timeDetectorStrategy,
+ @NonNull CallerIdentityInjector callerIdentityInjector) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
+ mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
+ }
+
+ @Override
+ public TimeCapabilitiesAndConfig getCapabilitiesAndConfig() {
+ int userId = mCallerIdentityInjector.getCallingUserId();
+ return getTimeCapabilitiesAndConfig(userId);
+ }
+
+ private TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig(@UserIdInt int userId) {
+ enforceManageTimeDetectorPermission();
+
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
+ try {
+ ConfigurationInternal configurationInternal =
+ mTimeDetectorStrategy.getConfigurationInternal(userId);
+ return configurationInternal.capabilitiesAndConfig();
+ } finally {
+ mCallerIdentityInjector.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean updateConfiguration(TimeConfiguration timeConfiguration) {
+ // TODO(b/172891783) Add actual logic
+ return false;
}
@Override
@@ -193,4 +231,11 @@
android.Manifest.permission.SET_TIME,
"suggest time from external source");
}
+
+ private void enforceManageTimeDetectorPermission() {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION,
+ "manage time and time zone detection");
+ }
+
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 792f372..cde66be 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
import android.app.timedetector.ManualTimeSuggestion;
@@ -88,6 +89,9 @@
/** Processes the suggested time from external sources. */
void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion);
+ /** Returns the configuration that controls time detector behaviour for specified user. */
+ ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
+
/**
* Handles the auto-time configuration changing For example, when the auto-time setting is
* toggled on or off.
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 16e8632c..289d8d6 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.app.time.ExternalTimeSuggestion;
import android.app.timedetector.GnssTimeSuggestion;
@@ -155,6 +156,12 @@
*/
@Origin int[] autoOriginPriorities();
+ /**
+ * Returns {@link ConfigurationInternal} for specified user.
+ */
+ @NonNull
+ ConfigurationInternal configurationInternal(@UserIdInt int userId);
+
/** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
void acquireWakeLock();
@@ -267,6 +274,12 @@
}
@Override
+ @NonNull
+ public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
+ return mEnvironment.configurationInternal(userId);
+ }
+
+ @Override
public synchronized void handleAutoTimeConfigChanged() {
boolean enabled = mEnvironment.isAutoTimeDetectionEnabled();
// When automatic time detection is enabled we update the system clock instantly if we can.
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index ee78a4e..b4aa201 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -16,10 +16,10 @@
package com.android.server.timezonedetector;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 5d34dd7..c34a7d3 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -15,7 +15,7 @@
*/
package com.android.server.timezonedetector;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index a1d2f8a..3ae67ae 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3298,7 +3298,11 @@
values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
sessionToken.toString());
- mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ try{
+ mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ }catch(IllegalArgumentException ex){
+ Slog.w(TAG, "error in insert db for MSG_LOG_WATCH_START", ex);
+ }
args.recycle();
break;
}
@@ -3313,7 +3317,11 @@
values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
sessionToken.toString());
- mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ try{
+ mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ }catch(IllegalArgumentException ex){
+ Slog.w(TAG, "error in insert db for MSG_LOG_WATCH_END", ex);
+ }
args.recycle();
break;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 3893267..b90408f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibrationEffect;
@@ -35,9 +36,9 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.FrameworkStatsLog;
-import com.google.android.collect.Lists;
-
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
@@ -47,11 +48,16 @@
private static final boolean DEBUG = false;
/**
- * Extra timeout added to the end of each synced vibration step as a timeout for the callback
- * wait, to ensure it finishes even when callbacks from individual vibrators are lost.
+ * Extra timeout added to the end of each vibration step to ensure it finishes even when
+ * vibrator callbacks are lost.
*/
private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
+ /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
+ private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
+
+ private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+
/** Callbacks for playing a {@link Vibration}. */
interface VibrationCallbacks {
@@ -83,13 +89,10 @@
private final IBatteryStats mBatteryStatsService;
private final Vibration mVibration;
private final VibrationCallbacks mCallbacks;
- private final SparseArray<VibratorController> mVibrators;
+ private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
+ private final StepQueue mStepQueue = new StepQueue();
- @GuardedBy("mLock")
- @Nullable
- private VibrateStep mCurrentVibrateStep;
- @GuardedBy("mLock")
- private boolean mForceStop;
+ private volatile boolean mForceStop;
VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
@@ -102,7 +105,6 @@
mBatteryStatsService = batteryStatsService;
CombinedVibrationEffect effect = vib.getEffect();
- mVibrators = new SparseArray<>();
for (int i = 0; i < availableVibrators.size(); i++) {
if (effect.hasVibrator(availableVibrators.keyAt(i))) {
mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
@@ -110,6 +112,15 @@
}
}
+ Vibration getVibration() {
+ return mVibration;
+ }
+
+ @VisibleForTesting
+ SparseArray<VibratorController> getVibrators() {
+ return mVibrators;
+ }
+
@Override
public void binderDied() {
if (DEBUG) {
@@ -136,8 +147,11 @@
/** Cancel current vibration and shuts down the thread gracefully. */
public void cancel() {
+ mForceStop = true;
synchronized (mLock) {
- mForceStop = true;
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration cancelled");
+ }
mLock.notify();
}
}
@@ -148,11 +162,10 @@
if (DEBUG) {
Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
}
- if (mCurrentVibrateStep != null) {
- for (int i = 0; i < mVibrators.size(); i++) {
- mCurrentVibrateStep.vibratorComplete(mVibrators.keyAt(i));
- }
+ for (int i = 0; i < mVibrators.size(); i++) {
+ mStepQueue.consumeOnVibratorComplete(mVibrators.keyAt(i));
}
+ mLock.notify();
}
}
@@ -162,120 +175,73 @@
if (DEBUG) {
Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
}
- if (mCurrentVibrateStep != null) {
- mCurrentVibrateStep.vibratorComplete(vibratorId);
- }
+ mStepQueue.consumeOnVibratorComplete(vibratorId);
+ mLock.notify();
}
}
- Vibration getVibration() {
- return mVibration;
- }
-
- @VisibleForTesting
- SparseArray<VibratorController> getVibrators() {
- return mVibrators;
- }
-
private Vibration.Status playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try {
- List<Step> steps = generateSteps(mVibration.getEffect());
- if (steps.isEmpty()) {
- // No vibrator matching any incoming vibration effect.
- return Vibration.Status.IGNORED;
- }
- Vibration.Status status = Vibration.Status.FINISHED;
- final int stepCount = steps.size();
- for (int i = 0; i < stepCount; i++) {
- Step step = steps.get(i);
- synchronized (mLock) {
- if (step instanceof VibrateStep) {
- mCurrentVibrateStep = (VibrateStep) step;
+ CombinedVibrationEffect.Sequential effect = toSequential(mVibration.getEffect());
+ int stepsPlayed = 0;
+
+ synchronized (mLock) {
+ mStepQueue.offer(new StartVibrateStep(effect));
+ Step topOfQueue;
+
+ while ((topOfQueue = mStepQueue.peek()) != null) {
+ long waitTime = topOfQueue.calculateWaitTime();
+ if (waitTime <= 0) {
+ stepsPlayed += mStepQueue.consume();
} else {
- mCurrentVibrateStep = null;
+ try {
+ mLock.wait(waitTime);
+ } catch (InterruptedException e) { }
+ }
+ if (mForceStop) {
+ mStepQueue.cancel();
+ return Vibration.Status.CANCELLED;
}
}
- status = step.play();
- if (status != Vibration.Status.FINISHED) {
- // This step was ignored by the vibrators, probably effects were unsupported.
- break;
- }
- if (mForceStop) {
- break;
- }
}
- if (mForceStop) {
- return Vibration.Status.CANCELLED;
- }
- return status;
+
+ // Some effects might be ignored because the specified vibrator don't exist or doesn't
+ // support the effect. We only report ignored here if nothing was played besides the
+ // StartVibrateStep (which means every attempt to turn on the vibrator was ignored).
+ return stepsPlayed > effect.getEffects().size()
+ ? Vibration.Status.FINISHED : Vibration.Status.IGNORED_UNSUPPORTED;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
- private List<Step> generateSteps(CombinedVibrationEffect effect) {
- if (effect instanceof CombinedVibrationEffect.Sequential) {
- CombinedVibrationEffect.Sequential sequential =
- (CombinedVibrationEffect.Sequential) effect;
- List<Step> steps = new ArrayList<>();
- final int sequentialEffectCount = sequential.getEffects().size();
- for (int i = 0; i < sequentialEffectCount; i++) {
- int delay = sequential.getDelays().get(i);
- if (delay > 0) {
- steps.add(new DelayStep(delay));
- }
- steps.addAll(generateSteps(sequential.getEffects().get(i)));
+ private void noteVibratorOn(long duration) {
+ try {
+ if (duration <= 0) {
+ return;
}
- final int stepCount = steps.size();
- for (int i = 0; i < stepCount; i++) {
- if (steps.get(i) instanceof VibrateStep) {
- return steps;
- }
+ if (duration == Long.MAX_VALUE) {
+ // Repeating duration has started. Report a fixed duration here, noteVibratorOff
+ // should be called when this is cancelled.
+ duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
}
- // No valid vibrate step was generated, ignore effect completely.
- return Lists.newArrayList();
+ mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+ mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
+ duration);
+ } catch (RemoteException e) {
}
- VibrateStep vibrateStep = null;
- if (effect instanceof CombinedVibrationEffect.Mono) {
- vibrateStep = createVibrateStep(mapToAvailableVibrators(
- ((CombinedVibrationEffect.Mono) effect).getEffect()));
- } else if (effect instanceof CombinedVibrationEffect.Stereo) {
- vibrateStep = createVibrateStep(filterByAvailableVibrators(
- ((CombinedVibrationEffect.Stereo) effect).getEffects()));
- }
- return vibrateStep == null ? Lists.newArrayList() : Lists.newArrayList(vibrateStep);
}
- @Nullable
- private VibrateStep createVibrateStep(SparseArray<VibrationEffect> effects) {
- if (effects.size() == 0) {
- return null;
+ private void noteVibratorOff() {
+ try {
+ mBatteryStatsService.noteVibratorOff(mVibration.uid);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+ mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+ /* duration= */ 0);
+ } catch (RemoteException e) {
}
- if (effects.size() == 1) {
- // Create simplified step that handles a single vibrator.
- return new SingleVibrateStep(mVibrators.get(effects.keyAt(0)), effects.valueAt(0));
- }
- return new SyncedVibrateStep(effects);
- }
-
- private SparseArray<VibrationEffect> mapToAvailableVibrators(VibrationEffect effect) {
- SparseArray<VibrationEffect> mappedEffects = new SparseArray<>(mVibrators.size());
- for (int i = 0; i < mVibrators.size(); i++) {
- mappedEffects.put(mVibrators.keyAt(i), effect);
- }
- return mappedEffects;
- }
-
- private SparseArray<VibrationEffect> filterByAvailableVibrators(
- SparseArray<VibrationEffect> effects) {
- SparseArray<VibrationEffect> filteredEffects = new SparseArray<>();
- for (int i = 0; i < effects.size(); i++) {
- if (mVibrators.contains(effects.keyAt(i))) {
- filteredEffects.put(effects.keyAt(i), effects.valueAt(i));
- }
- }
- return filteredEffects;
}
/**
@@ -306,341 +272,240 @@
return timing;
}
- /**
- * Sleeps until given {@code wakeUpTime}.
- *
- * <p>This stops immediately when {@link #cancel()} is called.
- *
- * @return true if waited until wake-up time, false if it was cancelled.
- */
- private boolean waitUntil(long wakeUpTime) {
- synchronized (mLock) {
- long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- while (durationRemaining > 0) {
- try {
- mLock.wait(durationRemaining);
- } catch (InterruptedException e) {
- }
- if (mForceStop) {
- return false;
- }
- durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- }
+ private static CombinedVibrationEffect.Sequential toSequential(CombinedVibrationEffect effect) {
+ if (effect instanceof CombinedVibrationEffect.Sequential) {
+ return (CombinedVibrationEffect.Sequential) effect;
}
- return true;
+ return (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+ .addNext(effect)
+ .combine();
}
- /**
- * Sleeps until given {@link VibrateStep#isVibrationComplete()}, or until {@code wakeUpTime}.
- *
- * <p>This stops immediately when {@link #cancel()} is called.
- *
- * @return true if finished on vibration complete, false if it was cancelled or timed out.
- */
- private boolean waitForVibrationComplete(VibrateStep step, long wakeUpTime) {
- synchronized (mLock) {
- long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- while (!step.isVibrationComplete() && durationRemaining > 0) {
- try {
- mLock.wait(durationRemaining);
- } catch (InterruptedException e) {
- }
- if (mForceStop) {
- return false;
- }
- durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- }
- }
- return step.isVibrationComplete();
- }
-
- private void noteVibratorOn(long duration) {
- try {
- mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
- duration);
- } catch (RemoteException e) {
- }
- }
-
- private void noteVibratorOff() {
- try {
- mBatteryStatsService.noteVibratorOff(mVibration.uid);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
- /* duration= */ 0);
- } catch (RemoteException e) {
- }
- }
-
- /** Represent a single synchronized step while playing a {@link CombinedVibrationEffect}. */
- private interface Step {
- Vibration.Status play();
- }
-
- /** Represent a synchronized vibration step. */
- private interface VibrateStep extends Step {
- /** Callback to notify a vibrator has finished playing a effect. */
- void vibratorComplete(int vibratorId);
-
- /** Returns true if the vibration played by this step is complete. */
- boolean isVibrationComplete();
- }
-
- /** Represent a vibration on a single vibrator. */
- private final class SingleVibrateStep implements VibrateStep {
- private final VibratorController mVibrator;
- private final VibrationEffect mEffect;
+ /** Queue for {@link Step Steps}, sorted by their start time. */
+ private final class StepQueue {
+ @GuardedBy("mLock")
+ private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
@GuardedBy("mLock")
- private boolean mVibrationComplete;
-
- SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) {
- mVibrator = vibrator;
- mEffect = effect;
+ public void offer(@NonNull Step step) {
+ mNextSteps.offer(step);
}
@GuardedBy("mLock")
- @Override
- public boolean isVibrationComplete() {
- return mVibrationComplete;
+ @Nullable
+ public Step peek() {
+ return mNextSteps.peek();
+ }
+
+ /**
+ * Play and remove the step at the top of this queue, and also adds the next steps
+ * generated to be played next.
+ *
+ * @return the number of steps played
+ */
+ @GuardedBy("mLock")
+ public int consume() {
+ Step nextStep = mNextSteps.poll();
+ if (nextStep != null) {
+ mNextSteps.addAll(nextStep.play());
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Play and remove the step in this queue that should be anticipated by the vibrator
+ * completion callback.
+ *
+ * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
+ * first step found is played by this method, in no particular order.
+ */
+ @GuardedBy("mLock")
+ public void consumeOnVibratorComplete(int vibratorId) {
+ Iterator<Step> it = mNextSteps.iterator();
+ List<Step> nextSteps = EMPTY_STEP_LIST;
+ while (it.hasNext()) {
+ Step step = it.next();
+ if (step.shouldPlayWhenVibratorComplete(vibratorId)) {
+ it.remove();
+ nextSteps = step.play();
+ break;
+ }
+ }
+ mNextSteps.addAll(nextSteps);
+ }
+
+ /**
+ * Cancel the current queue, clearing all remaining steps.
+ *
+ * <p>This will remove and trigger {@link Step#cancel()} in all steps, in order.
+ */
+ @GuardedBy("mLock")
+ public void cancel() {
+ Step step;
+ while ((step = mNextSteps.poll()) != null) {
+ step.cancel();
+ }
+ }
+ }
+
+ /**
+ * Represent a single step for playing a vibration.
+ *
+ * <p>Every step has a start time, which can be used to apply delays between steps while
+ * executing them in sequence.
+ */
+ private abstract class Step implements Comparable<Step> {
+ public final long startTime;
+
+ Step(long startTime) {
+ this.startTime = startTime;
+ }
+
+ /** Play this step, returning a (possibly empty) list of next steps. */
+ @NonNull
+ public abstract List<Step> play();
+
+ /** Cancel this pending step. */
+ public void cancel() {
+ }
+
+ /**
+ * Return true to play this step right after a vibrator has notified vibration completed,
+ * used to anticipate steps waiting on vibrator callbacks with a timeout.
+ */
+ public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ return false;
+ }
+
+ /** Returns the time in millis to wait before playing this step. */
+ public long calculateWaitTime() {
+ if (startTime == Long.MAX_VALUE) {
+ // This step don't have a predefined start time, it's just marked to be executed
+ // after all other steps have finished.
+ return 0;
+ }
+ return Math.max(0, startTime - SystemClock.uptimeMillis());
}
@Override
- public void vibratorComplete(int vibratorId) {
- if (mVibrator.getVibratorInfo().getId() != vibratorId) {
- return;
- }
- if (mEffect instanceof VibrationEffect.OneShot
- || mEffect instanceof VibrationEffect.Waveform) {
- // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks.
- return;
- }
- mVibrator.off();
- synchronized (mLock) {
- mVibrationComplete = true;
- mLock.notify();
- }
+ public int compareTo(Step o) {
+ return Long.compare(startTime, o.startTime);
+ }
+ }
+
+ /**
+ * Starts a sync vibration.
+ *
+ * <p>If this step has successfully started playing a vibration on any vibrator, it will always
+ * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished
+ * all their individual steps.
+ *
+ * <o>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
+ * sequential effect isn't finished yet.
+ */
+ private final class StartVibrateStep extends Step {
+ public final CombinedVibrationEffect.Sequential sequentialEffect;
+ public final int currentIndex;
+
+ StartVibrateStep(CombinedVibrationEffect.Sequential effect) {
+ this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0);
+ }
+
+ StartVibrateStep(long startTime, CombinedVibrationEffect.Sequential effect, int index) {
+ super(startTime);
+ sequentialEffect = effect;
+ currentIndex = index;
}
@Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SingleVibrateStep");
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep");
+ List<Step> nextSteps = new ArrayList<>();
long duration = -1;
try {
if (DEBUG) {
- Slog.d(TAG, "SingleVibrateStep starting...");
+ Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex);
}
- long startTime = SystemClock.uptimeMillis();
- duration = vibratePredefined(mEffect);
-
- if (duration > 0) {
- noteVibratorOn(duration);
- // Vibration is playing with no need to control amplitudes, just wait for native
- // callback or timeout.
- if (waitForVibrationComplete(this,
- startTime + duration + CALLBACKS_EXTRA_TIMEOUT)) {
- return Vibration.Status.FINISHED;
- }
- // Timed out or vibration cancelled. Stop vibrator anyway.
- mVibrator.off();
- return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
+ CombinedVibrationEffect effect = sequentialEffect.getEffects().get(currentIndex);
+ DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
+ if (effectMapping == null) {
+ // Unable to map effects to vibrators, ignore this step.
+ return nextSteps;
}
- startTime = SystemClock.uptimeMillis();
- AmplitudeStep amplitudeStep = vibrateWithAmplitude(mEffect, startTime);
- if (amplitudeStep == null) {
- // Vibration could not be played with or without amplitude steps.
- return Vibration.Status.IGNORED_UNSUPPORTED;
- }
-
- duration = mEffect instanceof VibrationEffect.Prebaked
- ? ((VibrationEffect.Prebaked) mEffect).getFallbackEffect().getDuration()
- : mEffect.getDuration();
- if (duration < Long.MAX_VALUE) {
- // Only report vibration stats if we know how long we will be vibrating.
- noteVibratorOn(duration);
- }
- while (amplitudeStep != null) {
- if (!waitUntil(amplitudeStep.startTime)) {
- mVibrator.off();
- return Vibration.Status.CANCELLED;
- }
- amplitudeStep.play();
- amplitudeStep = amplitudeStep.nextStep();
- }
-
- return Vibration.Status.FINISHED;
+ duration = startVibrating(effectMapping, nextSteps);
+ noteVibratorOn(duration);
} finally {
- if (duration > 0 && duration < Long.MAX_VALUE) {
- noteVibratorOff();
- }
- if (DEBUG) {
- Slog.d(TAG, "SingleVibrateStep done.");
+ // If this step triggered any vibrator then add a finish step to wait for all
+ // active vibrators to finish their individual steps before going to the next.
+ Step nextStep = duration > 0 ? new FinishVibrateStep(this) : nextStep();
+ if (nextStep != null) {
+ nextSteps.add(nextStep);
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
+ return nextSteps;
}
/**
- * Try to vibrate given effect using prebaked or composed predefined effects.
- *
- * @return the duration, in millis, expected for the vibration, or -1 if effect cannot be
- * played with predefined effects.
+ * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the
+ * time this method is called, or null if sequence is complete.
*/
- private long vibratePredefined(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- long duration = mVibrator.on(prebaked, mVibration.id);
- if (duration > 0) {
- return duration;
- }
- if (prebaked.getFallbackEffect() != null) {
- return vibratePredefined(prebaked.getFallbackEffect());
- }
- } else if (effect instanceof VibrationEffect.Composed) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
- return mVibrator.on(composed, mVibration.id);
+ @Nullable
+ private Step nextStep() {
+ int nextIndex = currentIndex + 1;
+ if (nextIndex >= sequentialEffect.getEffects().size()) {
+ return null;
}
- // OneShot and Waveform effects require amplitude change after calling vibrator.on.
- return -1;
+ long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
+ long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
+ return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex);
}
- /**
- * Try to vibrate given effect using {@link AmplitudeStep} to control vibration amplitude.
- *
- * @return the {@link AmplitudeStep} to start this vibration, or {@code null} if vibration
- * do not require amplitude control.
- */
- private AmplitudeStep vibrateWithAmplitude(VibrationEffect effect, long startTime) {
- int vibratorId = mVibrator.getVibratorInfo().getId();
- if (effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- return new AmplitudeStep(vibratorId, oneShot, startTime, startTime);
- } else if (effect instanceof VibrationEffect.Waveform) {
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
- return new AmplitudeStep(vibratorId, waveform, startTime, startTime);
- } else if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- if (prebaked.getFallbackEffect() != null) {
- return vibrateWithAmplitude(prebaked.getFallbackEffect(), startTime);
- }
+ /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
+ @Nullable
+ private DeviceEffectMap createEffectToVibratorMapping(
+ CombinedVibrationEffect effect) {
+ if (effect instanceof CombinedVibrationEffect.Mono) {
+ return new DeviceEffectMap((CombinedVibrationEffect.Mono) effect);
+ }
+ if (effect instanceof CombinedVibrationEffect.Stereo) {
+ return new DeviceEffectMap((CombinedVibrationEffect.Stereo) effect);
}
return null;
}
- }
-
- /** Represent a synchronized vibration step on multiple vibrators. */
- private final class SyncedVibrateStep implements VibrateStep {
- private final SparseArray<VibrationEffect> mEffects;
- private final long mRequiredCapabilities;
- private final int[] mVibratorIds;
-
- @GuardedBy("mLock")
- private int mActiveVibratorCounter;
-
- SyncedVibrateStep(SparseArray<VibrationEffect> effects) {
- mEffects = effects;
- mActiveVibratorCounter = mEffects.size();
- mRequiredCapabilities = calculateRequiredSyncCapabilities(effects);
- mVibratorIds = new int[effects.size()];
- for (int i = 0; i < effects.size(); i++) {
- mVibratorIds[i] = effects.keyAt(i);
- }
- }
-
- @GuardedBy("mLock")
- @Override
- public boolean isVibrationComplete() {
- return mActiveVibratorCounter <= 0;
- }
-
- @Override
- public void vibratorComplete(int vibratorId) {
- VibrationEffect effect = mEffects.get(vibratorId);
- if (effect == null) {
- return;
- }
- if (effect instanceof VibrationEffect.OneShot
- || effect instanceof VibrationEffect.Waveform) {
- // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks.
- return;
- }
- mVibrators.get(vibratorId).off();
- synchronized (mLock) {
- --mActiveVibratorCounter;
- mLock.notify();
- }
- }
-
- @Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SyncedVibrateStep");
- long duration = -1;
- try {
- if (DEBUG) {
- Slog.d(TAG, "SyncedVibrateStep starting...");
- }
- final PriorityQueue<AmplitudeStep> nextSteps = new PriorityQueue<>(mEffects.size());
- long startTime = SystemClock.uptimeMillis();
- duration = startVibratingSynced(startTime, nextSteps);
-
- if (duration <= 0) {
- // Vibrate step failed, vibrator could not be turned on for this step.
- return Vibration.Status.IGNORED;
- }
-
- noteVibratorOn(duration);
- while (!nextSteps.isEmpty()) {
- AmplitudeStep step = nextSteps.poll();
- if (!waitUntil(step.startTime)) {
- stopAllVibrators();
- return Vibration.Status.CANCELLED;
- }
- step.play();
- AmplitudeStep nextStep = step.nextStep();
- if (nextStep == null) {
- // This vibrator has finished playing the effect for this step.
- synchronized (mLock) {
- mActiveVibratorCounter--;
- }
- } else {
- nextSteps.add(nextStep);
- }
- }
-
- synchronized (mLock) {
- // All OneShot and Waveform effects have finished. Just wait for the other
- // effects to end via native callbacks before finishing this synced step.
- final long wakeUpTime = startTime + duration + CALLBACKS_EXTRA_TIMEOUT;
- if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) {
- return Vibration.Status.FINISHED;
- }
-
- // Timed out or vibration cancelled. Stop all vibrators anyway.
- stopAllVibrators();
- return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
- }
- } finally {
- if (duration > 0) {
- noteVibratorOff();
- }
- if (DEBUG) {
- Slog.d(TAG, "SyncedVibrateStep done.");
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
/**
* Starts playing effects on designated vibrators, in sync.
*
- * @return A positive duration, in millis, to wait for the completion of this effect.
- * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
- * returns the duration of a single run to be used as timeout for callbacks.
+ * @param effectMapping The {@link CombinedVibrationEffect} mapped to this device vibrators
+ * @param nextSteps An output list to accumulate the future {@link Step Steps} created
+ * by this method, typically one for each vibrator that has
+ * successfully started vibrating on this step.
+ * @return The duration, in millis, of the {@link CombinedVibrationEffect}. Repeating
+ * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
+ * have ignored all effects.
*/
- private long startVibratingSynced(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
+ private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) {
+ int vibratorCount = effectMapping.size();
+ if (vibratorCount == 0) {
+ // No effect was mapped to any available vibrator.
+ return 0;
+ }
+
+ VibratorOnStep[] steps = new VibratorOnStep[vibratorCount];
+ long vibrationStartTime = SystemClock.uptimeMillis();
+ for (int i = 0; i < vibratorCount; i++) {
+ steps[i] = new VibratorOnStep(vibrationStartTime,
+ mVibrators.get(effectMapping.vibratorIdAt(i)), effectMapping.effectAt(i));
+ }
+
+ if (steps.length == 1) {
+ // No need to prepare and trigger sync effects on a single vibrator.
+ return startVibrating(steps[0], nextSteps);
+ }
+
// This synchronization of vibrators should be executed one at a time, even if we are
// vibrating different sets of vibrators in parallel. The manager can only prepareSynced
// one set of vibrators at a time.
@@ -648,17 +513,24 @@
boolean hasPrepared = false;
boolean hasTriggered = false;
try {
- hasPrepared = mCallbacks.prepareSyncedVibration(mRequiredCapabilities,
- mVibratorIds);
- long timeout = startVibrating(startTime, nextSteps);
+ hasPrepared = mCallbacks.prepareSyncedVibration(
+ effectMapping.getRequiredSyncCapabilities(),
+ effectMapping.getVibratorIds());
- // Check if preparation was successful, otherwise devices area already vibrating
- if (hasPrepared) {
+ long duration = 0;
+ for (VibratorOnStep step : steps) {
+ duration = Math.max(duration, startVibrating(step, nextSteps));
+ }
+
+ // Check if sync was prepared and if any step was accepted by a vibrator,
+ // otherwise there is nothing to trigger here.
+ if (hasPrepared && duration > 0) {
hasTriggered = mCallbacks.triggerSyncedVibration(mVibration.id);
}
- return timeout;
+ return duration;
} finally {
if (hasPrepared && !hasTriggered) {
+ // Trigger has failed or all steps were ignored by the vibrators.
mCallbacks.cancelSyncedVibration();
return 0;
}
@@ -666,77 +538,365 @@
}
}
- /**
- * Starts playing effects on designated vibrators.
- *
- * <p>This includes the {@link VibrationEffect.OneShot} and {@link VibrationEffect.Waveform}
- * effects, that should start in sync with all other effects in this step. The waveforms are
- * controlled by {@link AmplitudeStep} added to the {@code nextSteps} queue.
- *
- * @return A positive duration, in millis, to wait for the completion of this effect.
- * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
- * returns the duration of a single run to be used as timeout for callbacks.
- */
- private long startVibrating(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
- long maxDuration = 0;
- for (int i = 0; i < mEffects.size(); i++) {
- VibratorController controller = mVibrators.get(mEffects.keyAt(i));
- VibrationEffect effect = mEffects.valueAt(i);
- maxDuration = Math.max(maxDuration,
- startVibrating(controller, effect, startTime, nextSteps));
+ private long startVibrating(VibratorOnStep step, List<Step> nextSteps) {
+ nextSteps.addAll(step.play());
+ return step.getDuration();
+ }
+ }
+
+ /**
+ * Finish a sync vibration started by a {@link StartVibrateStep}.
+ *
+ * <p>This only plays after all active vibrators steps have finished, and adds a {@link
+ * StartVibrateStep} to the queue if the sequential effect isn't finished yet.
+ */
+ private final class FinishVibrateStep extends Step {
+ public final StartVibrateStep startedStep;
+
+ FinishVibrateStep(StartVibrateStep startedStep) {
+ super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue.
+ this.startedStep = startedStep;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep");
+ try {
+ if (DEBUG) {
+ Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex);
+ }
+ noteVibratorOff();
+ Step nextStep = startedStep.nextStep();
+ return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
- return maxDuration;
+ }
+
+ @Override
+ public void cancel() {
+ noteVibratorOff();
+ }
+ }
+
+ /**
+ * Represent a step turn the vibrator on.
+ *
+ * <p>No other calls to the vibrator is made from this step, so this can be played in between
+ * calls to 'prepare' and 'trigger' for synchronized vibrations.
+ */
+ private final class VibratorOnStep extends Step {
+ public final VibratorController controller;
+ public final VibrationEffect effect;
+ private long mDuration;
+
+ VibratorOnStep(long startTime, VibratorController controller, VibrationEffect effect) {
+ super(startTime);
+ this.controller = controller;
+ this.effect = effect;
}
/**
- * Play a single effect on a single vibrator.
- *
- * @return A positive duration, in millis, to wait for the completion of this effect.
- * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
- * returns the duration of a single run to be used as timeout for callbacks.
+ * Return the duration, in millis, of this effect. Repeating waveforms return {@link
+ * Long#MAX_VALUE}. Zero or negative values indicate the vibrator has ignored this effect.
*/
- private long startVibrating(VibratorController controller, VibrationEffect effect,
- long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
- int vibratorId = controller.getVibratorInfo().getId();
- long duration;
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOnStep");
+ try {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId());
+ }
+ List<Step> nextSteps = new ArrayList<>();
+ mDuration = startVibrating(effect, nextSteps);
+ return nextSteps;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+ 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 AmplitudeStep(vibratorId, oneShot, startTime, startTime + duration));
+ nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot,
+ now + duration + CALLBACKS_EXTRA_TIMEOUT));
} else if (effect instanceof VibrationEffect.Waveform) {
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
- duration = getVibratorOnDuration(waveform, 0);
- if (duration > 0) {
- // Waveform starts by turning vibrator on. Do it in this sync vibrate step.
- controller.on(duration, mVibration.id);
+ // Return the full duration of this waveform effect.
+ duration = waveform.getDuration();
+ long onDuration = getVibratorOnDuration(waveform, 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.
+ // The next steps will handle the amplitudes after the vibrator has turned on.
+ controller.on(onDuration, mVibration.id);
}
- nextSteps.add(
- new AmplitudeStep(vibratorId, waveform, startTime, startTime + duration));
+ 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;
duration = controller.on(prebaked, mVibration.id);
- if (duration <= 0 && prebaked.getFallbackEffect() != null) {
- return startVibrating(controller, prebaked.getFallbackEffect(), startTime,
- nextSteps);
+ 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 (effect instanceof VibrationEffect.Composed) {
VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
duration = controller.on(composed, mVibration.id);
+ if (duration > 0) {
+ nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
+ controller));
+ }
} else {
duration = 0;
}
return duration;
}
+ }
- private void stopAllVibrators() {
- for (int vibratorId : mVibratorIds) {
- VibratorController controller = mVibrators.get(vibratorId);
- if (controller != null) {
- controller.off();
+ /**
+ * Represents a step to turn the vibrator off.
+ *
+ * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
+ * and can anticipated by vibrator complete callbacks.
+ */
+ private final class VibratorOffStep extends Step {
+ public final VibratorController controller;
+
+ VibratorOffStep(long startTime, VibratorController controller) {
+ super(startTime);
+ this.controller = controller;
+ }
+
+ @Override
+ public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ return controller.getVibratorInfo().getId() == vibratorId;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOffStep");
+ try {
+ stopVibrating();
+ return EMPTY_STEP_LIST;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ stopVibrating();
+ }
+
+ private void stopVibrating() {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
+ }
+ controller.off();
+ }
+ }
+
+ /** 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 int currentIndex;
+ public final long expectedVibratorStopTime;
+
+ private long mNextVibratorStopTime;
+
+ VibratorAmplitudeStep(long startTime, VibratorController controller,
+ VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) {
+ this(startTime, controller,
+ (VibrationEffect.Waveform) VibrationEffect.createWaveform(
+ new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()},
+ /* repeat= */ -1),
+ expectedVibratorStopTime);
+ }
+
+ VibratorAmplitudeStep(long startTime, VibratorController controller,
+ VibrationEffect.Waveform waveform, long expectedVibratorStopTime) {
+ this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime);
+ }
+
+ VibratorAmplitudeStep(long startTime, VibratorController controller,
+ VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) {
+ super(startTime);
+ this.controller = controller;
+ this.waveform = waveform;
+ this.currentIndex = index;
+ this.expectedVibratorStopTime = expectedVibratorStopTime;
+ mNextVibratorStopTime = expectedVibratorStopTime;
+ }
+
+ @Override
+ public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ if (controller.getVibratorInfo().getId() == vibratorId) {
+ mNextVibratorStopTime = SystemClock.uptimeMillis();
+ }
+ return false;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorAmplitudeStep");
+ try {
+ if (DEBUG) {
+ long latency = SystemClock.uptimeMillis() - startTime;
+ Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
+ }
+ if (waveform.getTimings()[currentIndex] == 0) {
+ // Skip waveform entries with zero timing.
+ return nextSteps();
+ }
+ int amplitude = waveform.getAmplitudes()[currentIndex];
+ if (amplitude == 0) {
+ stopVibrating();
+ return nextSteps();
+ }
+ 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);
+ if (onDuration > 0) {
+ startVibrating(onDuration);
+ mNextVibratorStopTime =
+ SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
+ }
+ }
+ changeAmplitude(amplitude);
+ return nextSteps();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ stopVibrating();
+ }
+
+ private void stopVibrating() {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
+ }
+ controller.off();
+ mNextVibratorStopTime = SystemClock.uptimeMillis();
+ }
+
+ private void startVibrating(long duration) {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+ + duration + "ms");
+ }
+ controller.on(duration, mVibration.id);
+ }
+
+ private void changeAmplitude(int amplitude) {
+ if (DEBUG) {
+ Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
+ + " to " + amplitude);
+ }
+ controller.setAmplitude(amplitude);
+ }
+
+ @NonNull
+ private List<Step> nextSteps() {
+ long nextStartTime = startTime + waveform.getTimings()[currentIndex];
+ int nextIndex = currentIndex + 1;
+ if (nextIndex >= waveform.getTimings().length) {
+ nextIndex = waveform.getRepeatIndex();
+ }
+ if (nextIndex < 0) {
+ return Arrays.asList(new VibratorOffStep(nextStartTime, controller));
+ }
+ return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform,
+ nextIndex, mNextVibratorStopTime));
+ }
+ }
+
+ /**
+ * Map a {@link CombinedVibrationEffect} to the vibrators available on the device.
+ *
+ * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
+ * play all of the effects in sync.
+ */
+ private final class DeviceEffectMap {
+ private final SparseArray<VibrationEffect> mVibratorEffects;
+ private final int[] mVibratorIds;
+ private final long mRequiredSyncCapabilities;
+
+ DeviceEffectMap(CombinedVibrationEffect.Mono mono) {
+ mVibratorEffects = new SparseArray<>(mVibrators.size());
+ mVibratorIds = new int[mVibrators.size()];
+ for (int i = 0; i < mVibrators.size(); i++) {
+ int vibratorId = mVibrators.keyAt(i);
+ mVibratorEffects.put(vibratorId, mono.getEffect());
+ mVibratorIds[i] = vibratorId;
+ }
+ mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+ }
+
+ DeviceEffectMap(CombinedVibrationEffect.Stereo stereo) {
+ SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
+ mVibratorEffects = new SparseArray<>();
+ for (int i = 0; i < stereoEffects.size(); i++) {
+ int vibratorId = stereoEffects.keyAt(i);
+ if (mVibrators.contains(vibratorId)) {
+ mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i));
}
}
+ mVibratorIds = new int[mVibratorEffects.size()];
+ for (int i = 0; i < mVibratorEffects.size(); i++) {
+ mVibratorIds[i] = mVibratorEffects.keyAt(i);
+ }
+ mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+ }
+
+ /**
+ * Return the number of vibrators mapped to play the {@link CombinedVibrationEffect} on this
+ * device.
+ */
+ public int size() {
+ return mVibratorIds.length;
+ }
+
+ /**
+ * Return all capabilities required to play the {@link CombinedVibrationEffect} in
+ * between calls to {@link IVibratorManager#prepareSynced} and
+ * {@link IVibratorManager#triggerSynced}.
+ */
+ public long getRequiredSyncCapabilities() {
+ return mRequiredSyncCapabilities;
+ }
+
+ /** Return all vibrator ids mapped to play the {@link CombinedVibrationEffect}. */
+ public int[] getVibratorIds() {
+ return mVibratorIds;
+ }
+
+ /** Return the id of the vibrator at given index. */
+ public int vibratorIdAt(int index) {
+ return mVibratorEffects.keyAt(index);
+ }
+
+ /** Return the {@link VibrationEffect} at given index. */
+ public VibrationEffect effectAt(int index) {
+ return mVibratorEffects.valueAt(index);
}
/**
@@ -782,145 +942,4 @@
&& (prepareCapabilities & ~capability) != 0;
}
}
-
- /** Represent a step to set amplitude on a single vibrator. */
- private final class AmplitudeStep implements Step, Comparable<AmplitudeStep> {
- public final int vibratorId;
- public final VibrationEffect.Waveform waveform;
- public final int currentIndex;
- public final long startTime;
- public final long vibratorStopTime;
-
- AmplitudeStep(int vibratorId, VibrationEffect.OneShot oneShot,
- long startTime, long vibratorStopTime) {
- this(vibratorId, (VibrationEffect.Waveform) VibrationEffect.createWaveform(
- new long[]{oneShot.getDuration()},
- new int[]{oneShot.getAmplitude()}, /* repeat= */ -1),
- startTime,
- vibratorStopTime);
- }
-
- AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform,
- long startTime, long vibratorStopTime) {
- this(vibratorId, waveform, /* index= */ 0, startTime, vibratorStopTime);
- }
-
- AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform,
- int index, long startTime, long vibratorStopTime) {
- this.vibratorId = vibratorId;
- this.waveform = waveform;
- this.currentIndex = index;
- this.startTime = startTime;
- this.vibratorStopTime = vibratorStopTime;
- }
-
- @Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
- try {
- if (DEBUG) {
- Slog.d(TAG, "AmplitudeStep starting on vibrator " + vibratorId + "...");
- }
- VibratorController controller = mVibrators.get(vibratorId);
- if (currentIndex < 0) {
- controller.off();
- if (DEBUG) {
- Slog.d(TAG, "Vibrator turned off and finishing");
- }
- return Vibration.Status.FINISHED;
- }
- if (waveform.getTimings()[currentIndex] == 0) {
- // Skip waveform entries with zero timing.
- return Vibration.Status.FINISHED;
- }
- int amplitude = waveform.getAmplitudes()[currentIndex];
- if (amplitude == 0) {
- controller.off();
- if (DEBUG) {
- Slog.d(TAG, "Vibrator turned off");
- }
- return Vibration.Status.FINISHED;
- }
- if (startTime >= vibratorStopTime) {
- // Vibrator has stopped. Turn vibrator back on for the duration of another
- // cycle before setting the amplitude.
- long onDuration = getVibratorOnDuration(waveform, currentIndex);
- if (onDuration > 0) {
- controller.on(onDuration, mVibration.id);
- if (DEBUG) {
- Slog.d(TAG, "Vibrator turned on for " + onDuration + "ms");
- }
- }
- }
- controller.setAmplitude(amplitude);
- if (DEBUG) {
- Slog.d(TAG, "Amplitude changed to " + amplitude);
- }
- return Vibration.Status.FINISHED;
- } finally {
- if (DEBUG) {
- Slog.d(TAG, "AmplitudeStep done.");
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @Override
- public int compareTo(AmplitudeStep o) {
- return Long.compare(startTime, o.startTime);
- }
-
- /** Return next {@link AmplitudeStep} from this waveform, of {@code null} if finished. */
- @Nullable
- public AmplitudeStep nextStep() {
- if (currentIndex < 0) {
- // Waveform has ended, no more steps to run.
- return null;
- }
- long nextStartTime = startTime + waveform.getTimings()[currentIndex];
- int nextIndex = currentIndex + 1;
- if (nextIndex >= waveform.getTimings().length) {
- nextIndex = waveform.getRepeatIndex();
- }
- return new AmplitudeStep(vibratorId, waveform, nextIndex, nextStartTime,
- nextVibratorStopTime());
- }
-
- /** Return next time the vibrator will stop after this step is played. */
- private long nextVibratorStopTime() {
- if (currentIndex < 0 || waveform.getTimings()[currentIndex] == 0
- || startTime < vibratorStopTime) {
- return vibratorStopTime;
- }
- return startTime + getVibratorOnDuration(waveform, currentIndex);
- }
- }
-
- /** Represent a delay step with fixed duration, that starts counting when it starts playing. */
- private final class DelayStep implements Step {
- private final int mDelay;
-
- DelayStep(int delay) {
- mDelay = delay;
- }
-
- @Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "DelayStep");
- try {
- if (DEBUG) {
- Slog.d(TAG, "DelayStep of " + mDelay + "ms starting...");
- }
- if (waitUntil(SystemClock.uptimeMillis() + mDelay)) {
- return Vibration.Status.FINISHED;
- }
- return Vibration.Status.CANCELLED;
- } finally {
- if (DEBUG) {
- Slog.d(TAG, "DelayStep done.");
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index eaba083..e3dc70b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -54,50 +54,6 @@
void onComplete(int vibratorId, long vibrationId);
}
- /**
- * Initializes the native part of this controller, creating a global reference to given
- * {@link OnVibrationCompleteListener} and returns a newly allocated native pointer. This
- * wrapper is responsible for deleting this pointer by calling the method pointed
- * by {@link #vibratorGetFinalizer()}.
- *
- * <p><b>Note:</b> Make sure the given implementation of {@link OnVibrationCompleteListener}
- * do not hold any strong reference to the instance responsible for deleting the returned
- * pointer, to avoid creating a cyclic GC root reference.
- */
- static native long vibratorInit(int vibratorId, OnVibrationCompleteListener listener);
-
- /**
- * Returns pointer to native function responsible for cleaning up the native pointer allocated
- * and returned by {@link #vibratorInit(int, OnVibrationCompleteListener)}.
- */
- static native long vibratorGetFinalizer();
-
- static native boolean vibratorIsAvailable(long nativePtr);
-
- static native void vibratorOn(long nativePtr, long milliseconds, long vibrationId);
-
- static native void vibratorOff(long nativePtr);
-
- static native void vibratorSetAmplitude(long nativePtr, int amplitude);
-
- static native int[] vibratorGetSupportedEffects(long nativePtr);
-
- static native int[] vibratorGetSupportedPrimitives(long nativePtr);
-
- static native long vibratorPerformEffect(
- long nativePtr, long effect, long strength, long vibrationId);
-
- static native long vibratorPerformComposedEffect(
- long nativePtr, VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId);
-
- static native void vibratorSetExternalControl(long nativePtr, boolean enabled);
-
- static native long vibratorGetCapabilities(long nativePtr);
-
- static native void vibratorAlwaysOnEnable(long nativePtr, long id, long effect, long strength);
-
- static native void vibratorAlwaysOnDisable(long nativePtr, long id);
-
VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
this(vibratorId, listener, new NativeWrapper());
}
@@ -109,7 +65,8 @@
mNativeWrapper.init(vibratorId, listener);
mVibratorInfo = new VibratorInfo(vibratorId, nativeWrapper.getCapabilities(),
- nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives());
+ nativeWrapper.getSupportedEffects(), nativeWrapper.getSupportedPrimitives(),
+ nativeWrapper.getResonantFrequency(), nativeWrapper.getQFactor());
}
/** Register state listener for this vibrator. */
@@ -336,13 +293,47 @@
/** Wrapper around the static-native methods of {@link VibratorController} for tests. */
@VisibleForTesting
public static class NativeWrapper {
+ /**
+ * Initializes the native part of this controller, creating a global reference to given
+ * {@link OnVibrationCompleteListener} and returns a newly allocated native pointer. This
+ * wrapper is responsible for deleting this pointer by calling the method pointed
+ * by {@link #getNativeFinalizer()}.
+ *
+ * <p><b>Note:</b> Make sure the given implementation of {@link OnVibrationCompleteListener}
+ * do not hold any strong reference to the instance responsible for deleting the returned
+ * pointer, to avoid creating a cyclic GC root reference.
+ */
+ private static native long nativeInit(int vibratorId, OnVibrationCompleteListener listener);
+
+ /**
+ * Returns pointer to native function responsible for cleaning up the native pointer
+ * allocated and returned by {@link #nativeInit(int, OnVibrationCompleteListener)}.
+ */
+ private static native long getNativeFinalizer();
+ 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 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 void setExternalControl(long nativePtr, boolean enabled);
+ private static native long getCapabilities(long nativePtr);
+ private static native void alwaysOnEnable(long nativePtr, long id, long effect,
+ long strength);
+ private static native void alwaysOnDisable(long nativePtr, long id);
+ private static native float getResonantFrequency(long nativePtr);
+ private static native float getQFactor(long nativePtr);
private long mNativePtr = 0;
/** Initializes native controller and allocation registry to destroy native instances. */
public void init(int vibratorId, OnVibrationCompleteListener listener) {
- mNativePtr = VibratorController.vibratorInit(vibratorId, listener);
- long finalizerPtr = VibratorController.vibratorGetFinalizer();
+ mNativePtr = nativeInit(vibratorId, listener);
+ long finalizerPtr = getNativeFinalizer();
if (finalizerPtr != 0) {
NativeAllocationRegistry registry =
@@ -354,65 +345,73 @@
/** Check if the vibrator is currently available. */
public boolean isAvailable() {
- return VibratorController.vibratorIsAvailable(mNativePtr);
+ return isAvailable(mNativePtr);
}
/** Turns vibrator on for given time. */
public void on(long milliseconds, long vibrationId) {
- VibratorController.vibratorOn(mNativePtr, milliseconds, vibrationId);
+ on(mNativePtr, milliseconds, vibrationId);
}
/** Turns vibrator off. */
public void off() {
- VibratorController.vibratorOff(mNativePtr);
+ off(mNativePtr);
}
/** Sets the amplitude for the vibrator to run. */
public void setAmplitude(int amplitude) {
- VibratorController.vibratorSetAmplitude(mNativePtr, amplitude);
+ setAmplitude(mNativePtr, amplitude);
}
/** Returns all predefined effects supported by the device vibrator. */
public int[] getSupportedEffects() {
- return VibratorController.vibratorGetSupportedEffects(mNativePtr);
+ return getSupportedEffects(mNativePtr);
}
/** Returns all compose primitives supported by the device vibrator. */
public int[] getSupportedPrimitives() {
- return VibratorController.vibratorGetSupportedPrimitives(mNativePtr);
+ return getSupportedPrimitives(mNativePtr);
}
/** Turns vibrator on to perform one of the supported effects. */
public long perform(long effect, long strength, long vibrationId) {
- return VibratorController.vibratorPerformEffect(
- mNativePtr, effect, strength, vibrationId);
+ return performEffect(mNativePtr, effect, strength, vibrationId);
}
/** Turns vibrator on to perform one of the supported composed effects. */
public long compose(
VibrationEffect.Composition.PrimitiveEffect[] effect, long vibrationId) {
- return VibratorController.vibratorPerformComposedEffect(mNativePtr, effect,
- vibrationId);
+ return performComposedEffect(mNativePtr, effect, vibrationId);
}
/** Enabled the device vibrator to be controlled by another service. */
public void setExternalControl(boolean enabled) {
- VibratorController.vibratorSetExternalControl(mNativePtr, enabled);
+ setExternalControl(mNativePtr, enabled);
}
/** Returns all capabilities of the device vibrator. */
public long getCapabilities() {
- return VibratorController.vibratorGetCapabilities(mNativePtr);
+ return getCapabilities(mNativePtr);
}
/** Enable always-on vibration with given id and effect. */
public void alwaysOnEnable(long id, long effect, long strength) {
- VibratorController.vibratorAlwaysOnEnable(mNativePtr, id, effect, strength);
+ alwaysOnEnable(mNativePtr, id, effect, strength);
}
/** Disable always-on vibration for given id. */
public void alwaysOnDisable(long id) {
- VibratorController.vibratorAlwaysOnDisable(mNativePtr, id);
+ alwaysOnDisable(mNativePtr, id);
+ }
+
+ /** Gets the vibrator's resonant frequency (F0) */
+ public float getResonantFrequency() {
+ return getResonantFrequency(mNativePtr);
+ }
+
+ /** Gets the vibrator's Q factor */
+ public float getQFactor() {
+ return getQFactor(mNativePtr);
}
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 370d921..75f06e5 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -90,7 +90,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -2447,8 +2446,8 @@
@Override
public void removeOnLocalColorsChangedListener(
- @NonNull ILocalWallpaperColorConsumer callback, int which, int userId,
- int displayId) throws RemoteException {
+ @NonNull ILocalWallpaperColorConsumer callback, List<RectF> removeAreas, int which,
+ int userId, int displayId) throws RemoteException {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
}
@@ -2457,43 +2456,45 @@
throw new SecurityException("calling user id does not match");
}
final long identity = Binder.clearCallingIdentity();
- ArrayList<RectF> removeAreas = new ArrayList<>();
- ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>>
- callbacksToRemove = new ArrayList<>();
+ ArrayList<RectF> purgeAreas = new ArrayList<>();
+ IBinder binder = callback.asBinder();
try {
synchronized (mLock) {
- ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder());
- mLocalColorCallbackDisplayId.remove(callback.asBinder());
- if (areas == null) areas = new ArraySet<>();
- for (RectF area : areas) {
- RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area);
- if (callbacks == null) continue;
- callbacksToRemove.add(new Pair<>(callbacks, callback));
- if (callbacks.getRegisteredCallbackCount() == 0) {
- mLocalColorAreaCallbacks.remove(area);
- removeAreas.add(area);
- }
- ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
- if (displayAreas != null) {
- displayAreas.remove(area);
+ ArraySet<RectF> currentAreas = mLocalColorCallbackAreas.get(binder);
+ if (currentAreas == null) return;
+ currentAreas.removeAll(removeAreas);
+ if (currentAreas.size() == 0) {
+ mLocalColorCallbackDisplayId.remove(binder);
+ for (RectF removeArea : removeAreas) {
+ RemoteCallbackList<ILocalWallpaperColorConsumer> remotes =
+ mLocalColorAreaCallbacks.get(removeArea);
+ if (remotes == null) continue;
+ remotes.unregister(callback);
+ if (remotes.getRegisteredCallbackCount() == 0) {
+ mLocalColorAreaCallbacks.remove(removeArea);
+ purgeAreas.add(removeArea);
+ ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
+ if (displayAreas != null) {
+ displayAreas.remove(removeArea);
+ if (displayAreas.size() == 0) {
+ mLocalColorDisplayIdAreas.remove(displayId);
+ }
+ }
+ }
}
}
}
- for (int i = 0; i < callbacksToRemove.size(); i++) {
- Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>
- pair = callbacksToRemove.get(i);
- pair.first.unregister(pair.second);
- }
+
} catch (Exception e) {
// ignore any exception
} finally {
Binder.restoreCallingIdentity(identity);
}
- if (removeAreas.size() == 0) return;
+ if (purgeAreas.size() == 0) return;
IWallpaperEngine engine = getEngine(which, userId, displayId);
if (engine == null) return;
- engine.removeLocalColorsAreas(removeAreas);
+ engine.removeLocalColorsAreas(purgeAreas);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c830ba9..c5115b2 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -65,6 +65,7 @@
import android.service.voice.VoiceInteractionManagerInternal;
import android.util.Slog;
import android.view.RemoteAnimationDefinition;
+import android.window.SizeConfigurationBuckets;
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -74,8 +75,6 @@
import com.android.server.uri.NeededUriGrants;
import com.android.server.vr.VrManagerInternal;
-import java.util.Arrays;
-
/**
* Server side implementation for the client activity to interact with system.
*
@@ -244,16 +243,14 @@
}
@Override
- public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
- int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
- ProtoLog.v(WM_DEBUG_CONFIGURATION, "Report configuration: %s %s %s",
- token, Arrays.toString(horizontalSizeConfiguration),
- Arrays.toString(verticalSizeConfigurations));
+ public void reportSizeConfigurations(IBinder token,
+ SizeConfigurationBuckets sizeConfigurations) {
+ ProtoLog.v(WM_DEBUG_CONFIGURATION, "Report configuration: %s %s",
+ token, sizeConfigurations);
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
- r.setSizeConfigurations(horizontalSizeConfiguration, verticalSizeConfigurations,
- smallestSizeConfigurations);
+ r.setSizeConfigurations(sizeConfigurations);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index db3d7ad..5884102 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -316,6 +316,7 @@
import android.view.WindowManager.TransitionOldType;
import android.view.animation.Animation;
import android.window.IRemoteTransition;
+import android.window.SizeConfigurationBuckets;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -563,12 +564,7 @@
// The locusId associated with this activity, if set.
private LocusId mLocusId;
- // These configurations are collected from application's resources based on size-sensitive
- // qualifiers. For example, layout-w800dp will be added to mHorizontalSizeConfigurations as 800
- // and drawable-sw400dp will be added to both as 400.
- private int[] mVerticalSizeConfigurations;
- private int[] mHorizontalSizeConfigurations;
- private int[] mSmallestSizeConfigurations;
+ private SizeConfigurationBuckets mSizeConfigurations;
/**
* The precomputed display insets for resolving configuration. It will be non-null if
@@ -664,13 +660,16 @@
// naturally.
private boolean mInSizeCompatModeForBounds = false;
- // Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
- // orientation then aspect ratio restrictions are also already respected.
+ // Bounds populated in resolveFixedOrientationConfiguration when this activity is letterboxed
+ // for fixed orientation. If not null, they are used as parent container in
+ // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets. If
+ // letterboxed due to fixed orientation then aspect ratio restrictions are also respected.
// This happens when an activity has fixed orientation which doesn't match orientation of the
// parent because a display is ignoring orientation request or fixed to user rotation.
// See WindowManagerService#getIgnoreOrientationRequest and
// WindowManagerService#getFixedToUserRotation for more context.
- private boolean mIsLetterboxedForFixedOrientationAndAspectRatio = false;
+ @Nullable
+ private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
// activity is not displayed?
// TODO: rename to mNoDisplay
@@ -1078,11 +1077,16 @@
pw.println(prefix + "supportsEnterPipOnTaskSwitch: "
+ supportsEnterPipOnTaskSwitch);
}
- if (info.maxAspectRatio != 0) {
- pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
+ if (info.getMaxAspectRatio() != 0) {
+ pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio());
}
- if (info.minAspectRatio != 0) {
- pw.println(prefix + "minAspectRatio=" + info.minAspectRatio);
+ if (info.getMinAspectRatio() != 0) {
+ pw.println(prefix + "minAspectRatio=" + info.getMinAspectRatio());
+ }
+ if (info.getMinAspectRatio() != info.getManifestMinAspectRatio()) {
+ // Log the fact that we've overridden the min aspect ratio from the manifest
+ pw.println(prefix + "manifestMinAspectRatio="
+ + info.getManifestMinAspectRatio());
}
pw.println(prefix + "supportsSizeChanges="
+ ActivityInfo.sizeChangesSupportModeToString(info.supportsSizeChanges()));
@@ -1168,52 +1172,8 @@
info.applicationInfo = aInfo;
}
- private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
- return crossesSizeThreshold(mHorizontalSizeConfigurations, firstDp, secondDp);
- }
-
- private boolean crossesVerticalSizeThreshold(int firstDp, int secondDp) {
- return crossesSizeThreshold(mVerticalSizeConfigurations, firstDp, secondDp);
- }
-
- private boolean crossesSmallestSizeThreshold(int firstDp, int secondDp) {
- return crossesSizeThreshold(mSmallestSizeConfigurations, firstDp, secondDp);
- }
-
- /**
- * The purpose of this method is to decide whether the activity needs to be relaunched upon
- * changing its size. In most cases the activities don't need to be relaunched, if the resize
- * is small, all the activity content has to do is relayout itself within new bounds. There are
- * cases however, where the activity's content would be completely changed in the new size and
- * the full relaunch is required.
- *
- * The activity will report to us vertical and horizontal thresholds after which a relaunch is
- * required. These thresholds are collected from the application resource qualifiers. For
- * example, if application has layout-w600dp resource directory, then it needs a relaunch when
- * we resize from width of 650dp to 550dp, as it crosses the 600dp threshold. However, if
- * it resizes width from 620dp to 700dp, it won't be relaunched as it stays on the same side
- * of the threshold.
- */
- private static boolean crossesSizeThreshold(int[] thresholds, int firstDp,
- int secondDp) {
- if (thresholds == null) {
- return false;
- }
- for (int i = thresholds.length - 1; i >= 0; i--) {
- final int threshold = thresholds[i];
- if ((firstDp < threshold && secondDp >= threshold)
- || (firstDp >= threshold && secondDp < threshold)) {
- return true;
- }
- }
- return false;
- }
-
- void setSizeConfigurations(int[] horizontalSizeConfiguration,
- int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
- mHorizontalSizeConfigurations = horizontalSizeConfiguration;
- mVerticalSizeConfigurations = verticalSizeConfigurations;
- mSmallestSizeConfigurations = smallestSizeConfigurations;
+ void setSizeConfigurations(SizeConfigurationBuckets sizeConfigurations) {
+ mSizeConfigurations = sizeConfigurations;
}
private void scheduleActivityMovedToDisplay(int displayId, Configuration config) {
@@ -1519,7 +1479,7 @@
private void setCornersRadius(WindowState mainWindow, int cornersRadius) {
final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
if (windowSurface != null && windowSurface.isValid()) {
- Transaction transaction = getPendingTransaction();
+ Transaction transaction = getSyncTransaction();
transaction.setCornerRadius(windowSurface, cornersRadius);
}
}
@@ -1531,7 +1491,7 @@
}
layoutLetterbox(winHint);
if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
- mLetterbox.applySurfaceChanges(getPendingTransaction());
+ mLetterbox.applySurfaceChanges(getSyncTransaction());
}
}
@@ -2154,6 +2114,10 @@
if (snapshot == null) {
return false;
}
+ if (!snapshot.getTopActivityComponent().equals(mActivityComponent)) {
+ // Obsoleted snapshot.
+ return false;
+ }
final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(this);
final int targetRotation = rotation != ROTATION_UNDEFINED
// The display may rotate according to the orientation of this activity.
@@ -2287,6 +2251,9 @@
// Go ahead and cancel the request.
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Clearing startingData for token=%s", this);
mStartingData = null;
+ // Clean surface up since we don't want the window to be added back, so we don't
+ // need to keep the surface to remove it.
+ mStartingSurface = null;
}
return;
}
@@ -6863,7 +6830,7 @@
}
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
- private void updateCompatDisplayInsets(@Nullable Rect fixedOrientationBounds) {
+ private void updateCompatDisplayInsets() {
if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
// The override configuration is set only once in size compatibility mode.
return;
@@ -6891,7 +6858,8 @@
// The role of CompatDisplayInsets is like the override bounds.
mCompatDisplayInsets =
- new CompatDisplayInsets(mDisplayContent, this, fixedOrientationBounds);
+ new CompatDisplayInsets(
+ mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
}
@VisibleForTesting
@@ -6945,8 +6913,6 @@
|| windowingMode == WINDOWING_MODE_FULLSCREEN) {
resolveFixedOrientationConfiguration(newParentConfiguration);
}
- final Rect fixedOrientationBounds = isLetterboxedForFixedOrientationAndAspectRatio()
- ? new Rect(resolvedConfig.windowConfiguration.getBounds()) : null;
if (mCompatDisplayInsets != null) {
resolveSizeCompatModeConfiguration(newParentConfiguration);
@@ -6966,7 +6932,7 @@
}
if (mVisibleRequested) {
- updateCompatDisplayInsets(fixedOrientationBounds);
+ updateCompatDisplayInsets();
}
// TODO(b/175212232): Consolidate position logic from each "resolve" method above here.
@@ -7001,7 +6967,7 @@
* WindowManagerService#getIgnoreOrientationRequest} for more context.
*/
boolean isLetterboxedForFixedOrientationAndAspectRatio() {
- return mIsLetterboxedForFixedOrientationAndAspectRatio;
+ return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
}
/**
@@ -7012,7 +6978,7 @@
* in this methiod.
*/
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
- mIsLetterboxedForFixedOrientationAndAspectRatio = false;
+ mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
if (handlesOrientationChangeFromDescendant()) {
// No need to letterbox because of fixed orientation. Display will handle
// fixed-orientation requests.
@@ -7053,8 +7019,8 @@
// Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in
// order to use the extra available space.
- final float maxAspectRatio = info.maxAspectRatio;
- final float minAspectRatio = info.minAspectRatio;
+ final float maxAspectRatio = info.getMaxAspectRatio();
+ final float minAspectRatio = info.getMinAspectRatio();
if (aspect > maxAspectRatio && maxAspectRatio != 0) {
aspect = maxAspectRatio;
} else if (aspect < minAspectRatio) {
@@ -7089,7 +7055,7 @@
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
- mIsLetterboxedForFixedOrientationAndAspectRatio = true;
+ mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
}
/**
@@ -7291,21 +7257,21 @@
// The rest of the condition is that only one side is smaller than the container, but it
// still needs to exclude the cases where the size is limited by the fixed aspect ratio.
- if (info.maxAspectRatio > 0) {
+ if (info.getMaxAspectRatio() > 0) {
final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
/ Math.min(appWidth, appHeight);
- if (aspectRatio >= info.maxAspectRatio) {
+ if (aspectRatio >= info.getMaxAspectRatio()) {
// The current size has reached the max aspect ratio.
return false;
}
}
- if (info.minAspectRatio > 0) {
+ if (info.getMinAspectRatio() > 0) {
// The activity should have at least the min aspect ratio, so this checks if the
// container still has available space to provide larger aspect ratio.
final float containerAspectRatio =
(0.5f + Math.max(containerAppWidth, containerAppHeight))
/ Math.min(containerAppWidth, containerAppHeight);
- if (containerAspectRatio <= info.minAspectRatio) {
+ if (containerAspectRatio <= info.getMinAspectRatio()) {
// The long side has reached the parent.
return false;
}
@@ -7472,9 +7438,9 @@
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds) {
- final float maxAspectRatio = info.maxAspectRatio;
+ final float maxAspectRatio = info.getMaxAspectRatio();
final Task rootTask = getRootTask();
- final float minAspectRatio = info.minAspectRatio;
+ final float minAspectRatio = info.getMinAspectRatio();
if (task == null || rootTask == null
|| (inMultiWindowMode() && !shouldCreateCompatDisplayInsets())
@@ -7635,6 +7601,12 @@
if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
+ "unchanged in %s", this);
+ // It's possible that resolveOverrideConfiguration was called before mVisibleRequested
+ // became true and mCompatDisplayInsets may not have been created so ensure
+ // that mCompatDisplayInsets is created here.
+ if (mVisibleRequested) {
+ updateCompatDisplayInsets();
+ }
return true;
}
@@ -7784,26 +7756,9 @@
// Determine what has changed. May be nothing, if this is a config that has come back from
// the app after going idle. In that case we just want to leave the official config object
// now in the activity and do nothing else.
- final Configuration currentConfig = getConfiguration();
- int changes = lastReportedConfig.diff(currentConfig);
- // We don't want to use size changes if they don't cross boundaries that are important to
- // the app.
- if ((changes & CONFIG_SCREEN_SIZE) != 0) {
- final boolean crosses = crossesHorizontalSizeThreshold(lastReportedConfig.screenWidthDp,
- currentConfig.screenWidthDp)
- || crossesVerticalSizeThreshold(lastReportedConfig.screenHeightDp,
- currentConfig.screenHeightDp);
- if (!crosses) {
- changes &= ~CONFIG_SCREEN_SIZE;
- }
- }
- if ((changes & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
- final int oldSmallest = lastReportedConfig.smallestScreenWidthDp;
- final int newSmallest = currentConfig.smallestScreenWidthDp;
- if (!crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
- changes &= ~CONFIG_SMALLEST_SCREEN_SIZE;
- }
- }
+ int changes = lastReportedConfig.diff(getConfiguration());
+ changes = SizeConfigurationBuckets.filterDiff(
+ changes, lastReportedConfig, getConfiguration(), mSizeConfigurations);
// We don't want window configuration to cause relaunches.
if ((changes & CONFIG_WINDOW_CONFIGURATION) != 0) {
changes &= ~CONFIG_WINDOW_CONFIGURATION;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c6cece3..18e5552 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1429,6 +1429,7 @@
+ "; allowBackgroundActivityStart: " + allowBackgroundActivityStart
+ "; intent: " + intent
+ "; callerApp: " + callerApp
+ + "; inVisibleTask: " + (callerApp != null && callerApp.hasActivityInVisibleTask())
+ "]");
// log aborted activity start to TRON
if (mService.isActivityStartsLoggingEnabled()) {
@@ -2707,7 +2708,8 @@
launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
break;
case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
- launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK;
+ launchFlags &=
+ ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
break;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d4eedf1..52d110c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -557,7 +557,7 @@
boolean mSupportsPictureInPicture;
boolean mSupportsMultiDisplay;
boolean mForceResizableActivities;
- boolean mSupportsNonResizableMultiWindow;
+ volatile boolean mSupportsNonResizableMultiWindow;
final List<ActivityTaskManagerInternal.ScreenObserver> mScreenObservers = new ArrayList<>();
@@ -3432,6 +3432,11 @@
}
@Override
+ public boolean supportsNonResizableMultiWindow() {
+ return mSupportsNonResizableMultiWindow;
+ }
+
+ @Override
public boolean updateConfiguration(Configuration values) {
mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5b685b4..99289e0 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1305,7 +1305,7 @@
if (isTransitionSet()) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
- mRemoteAnimationController = new RemoteAnimationController(mService,
+ mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
remoteAnimationAdapter, mHandler);
}
}
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index ab1ed67..71a10df 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -70,9 +70,10 @@
}
boolean areBackgroundActivityStartsAllowed(int pid, int uid, String packageName,
- boolean appSwitchAllowed, boolean isCheckingForFgsStart, boolean hasVisibleActivities,
- boolean hasBackgroundActivityStartPrivileges, long lastStopAppSwitchesTime,
- long lastActivityLaunchTime, long lastActivityFinishTime) {
+ boolean appSwitchAllowed, boolean isCheckingForFgsStart,
+ boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
+ long lastStopAppSwitchesTime, long lastActivityLaunchTime,
+ long lastActivityFinishTime) {
// If app switching is not allowed, we ignore all the start activity grace period
// exception so apps cannot start itself in onPause() after pressing home button.
if (appSwitchAllowed) {
@@ -110,7 +111,7 @@
return true;
}
// Allow if the caller has an activity in any foreground task.
- if (appSwitchAllowed && hasVisibleActivities) {
+ if (appSwitchAllowed && hasActivityInVisibleTask) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "[Process(" + pid
+ ")] Activity start allowed: process has activity in foreground task");
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 28a509b..0ec0142 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -74,45 +74,59 @@
/**
* CompatModePackages#DOWNSCALED is the gatekeeper of all per-app buffer downscaling
* changes. Disabling this change will prevent the following scaling factors from working:
- * CompatModePackages#DOWNSCALE_87_5
- * CompatModePackages#DOWNSCALE_75
- * CompatModePackages#DOWNSCALE_62_5
+ * CompatModePackages#DOWNSCALE_90
+ * CompatModePackages#DOWNSCALE_80
+ * CompatModePackages#DOWNSCALE_70
+ * CompatModePackages#DOWNSCALE_60
* CompatModePackages#DOWNSCALE_50
*
* If CompatModePackages#DOWNSCALED is enabled for an app package, then the app will be forcibly
- * resized to the highest enabled scaling factor e.g. 87.5% if both 87.5% and 75% were
- * enabled.
+ * resized to the highest enabled scaling factor e.g. 80% if both 80% and 70% were enabled.
*/
@ChangeId
@Disabled
- private static final long DOWNSCALED = 168419799L;
+ @Overridable
+ public static final long DOWNSCALED = 168419799L;
/**
* With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
- * CompatModePackages#DOWNSCALE_87_5 for a package will force the app to assume it's
- * running on a display with 87.5% the vertical and horizontal resolution of the real display.
+ * CompatModePackages#DOWNSCALE_90 for a package will force the app to assume it's
+ * running on a display with 90% the vertical and horizontal resolution of the real display.
*/
@ChangeId
@Disabled
- private static final long DOWNSCALE_87_5 = 176926753L;
+ @Overridable
+ public static final long DOWNSCALE_90 = 182811243L;
/**
* With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
- * CompatModePackages#DOWNSCALE_75 for a package will force the app to assume it's
- * running on a display with 75% the vertical and horizontal resolution of the real display.
+ * CompatModePackages#DOWNSCALE_80 for a package will force the app to assume it's
+ * running on a display with 80% the vertical and horizontal resolution of the real display.
*/
@ChangeId
@Disabled
- private static final long DOWNSCALE_75 = 176926829L;
+ @Overridable
+ public static final long DOWNSCALE_80 = 176926753L;
/**
* With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
- * CompatModePackages#DOWNSCALE_62_5 for a package will force the app to assume it's
- * running on a display with 62.5% the vertical and horizontal resolution of the real display.
+ * CompatModePackages#DOWNSCALE_70 for a package will force the app to assume it's
+ * running on a display with 70% the vertical and horizontal resolution of the real display.
*/
@ChangeId
@Disabled
- private static final long DOWNSCALE_62_5 = 176926771L;
+ @Overridable
+ public static final long DOWNSCALE_70 = 176926829L;
+
+ /**
+ * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+ * CompatModePackages#DOWNSCALE_60 for a package will force the app to assume it's
+ * running on a display with 60% the vertical and horizontal resolution of the real display.
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long DOWNSCALE_60 = 176926771L;
/**
* With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
@@ -121,7 +135,8 @@
*/
@ChangeId
@Disabled
- private static final long DOWNSCALE_50 = 176926741L;
+ @Overridable
+ public static final long DOWNSCALE_50 = 176926741L;
/**
* On Android TV applications that target pre-S are not expecting to receive a Window larger
@@ -273,17 +288,20 @@
float getCompatScale(String packageName, int uid) {
final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
if (CompatChanges.isChangeEnabled(DOWNSCALED, packageName, userHandle)) {
- if (CompatChanges.isChangeEnabled(DOWNSCALE_87_5, packageName, userHandle)) {
- return 8f / 7f; // 1.14285714286
+ if (CompatChanges.isChangeEnabled(DOWNSCALE_90, packageName, userHandle)) {
+ return 1f / 0.9f;
}
- if (CompatChanges.isChangeEnabled(DOWNSCALE_75, packageName, userHandle)) {
- return 4f / 3f; // 1.333333333
+ if (CompatChanges.isChangeEnabled(DOWNSCALE_80, packageName, userHandle)) {
+ return 1f / 0.8f;
}
- if (CompatChanges.isChangeEnabled(DOWNSCALE_62_5, packageName, userHandle)) {
- return /* 1 / 0.625 */ 1.6f;
+ if (CompatChanges.isChangeEnabled(DOWNSCALE_70, packageName, userHandle)) {
+ return 1f / 0.7f;
+ }
+ if (CompatChanges.isChangeEnabled(DOWNSCALE_60, packageName, userHandle)) {
+ return 1f / 0.6f;
}
if (CompatChanges.isChangeEnabled(DOWNSCALE_50, packageName, userHandle)) {
- return /* 1 / 0.5 */ 2f;
+ return 1f / 0.5f;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 5ccf576..9855ea5 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -451,7 +451,7 @@
void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) {
if (organizer == null) return;
- migrateToNewSurfaceControl();
+ migrateToNewSurfaceControl(getSyncTransaction());
mOrganizerController.onDisplayAreaVanished(organizer, this);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 426e631..6d24105 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1428,7 +1428,8 @@
computeScreenConfiguration(config);
} else if (currentConfig != null
// If waiting for a remote rotation, don't prematurely update configuration.
- && !mDisplayRotation.isWaitingForRemoteRotation()) {
+ && !(mDisplayRotation.isWaitingForRemoteRotation()
+ || mAtmService.getTransitionController().isCollecting(this))) {
// No obvious action we need to take, but if our current state mismatches the
// activity manager's, update it, disregarding font scale, which should remain set
// to the value of the previous configuration.
@@ -1470,6 +1471,12 @@
return mDisplayRotation.updateOrientation(orientation, forceUpdate);
}
+ @Override
+ boolean isSyncFinished() {
+ if (mDisplayRotation.isWaitingForRemoteRotation()) return false;
+ return super.isSyncFinished();
+ }
+
/**
* Returns a valid rotation if the activity can use different orientation than the display.
* Otherwise {@link #ROTATION_UNDEFINED}.
@@ -1763,7 +1770,7 @@
* Update rotation of the display.
*
* @return {@code true} if the rotation has been changed. In this case YOU MUST CALL
- * {@link #sendNewConfiguration} TO UNFREEZE THE SCREEN.
+ * {@link #sendNewConfiguration} TO UNFREEZE THE SCREEN unless using Shell transitions.
*/
boolean updateRotationUnchecked() {
return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
@@ -1778,8 +1785,12 @@
*/
private void applyRotation(final int oldRotation, final int rotation) {
mDisplayRotation.applyCurrentRotation(rotation);
- final boolean rotateSeamlessly = mDisplayRotation.isRotatingSeamlessly();
- final Transaction transaction = getPendingTransaction();
+ final boolean shellTransitions =
+ mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null;
+ final boolean rotateSeamlessly =
+ mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
+ final Transaction transaction =
+ shellTransitions ? getSyncTransaction() : getPendingTransaction();
ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
? null : getRotationAnimation();
// We need to update our screen size information to match the new rotation. If the rotation
@@ -1795,9 +1806,11 @@
screenRotationAnimation.setRotation(transaction, rotation);
}
- forAllWindows(w -> {
- w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
- }, true /* traverseTopToBottom */);
+ if (!shellTransitions) {
+ forAllWindows(w -> {
+ w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
+ }, true /* traverseTopToBottom */);
+ }
mWmService.mDisplayManagerInternal.performTraversal(transaction);
scheduleAnimation();
@@ -1862,6 +1875,7 @@
// Update application display metrics.
final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+ final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
displayCutout);
@@ -1878,6 +1892,7 @@
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
+ mDisplayInfo.roundedCorners = roundedCorners;
mDisplayInfo.getAppMetrics(mDisplayMetrics);
if (mDisplayScalingDisabled) {
mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
@@ -3426,7 +3441,7 @@
/** Updates the layer assignment of windows on this display. */
void assignWindowLayers(boolean setLayoutNeeded) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers");
- assignChildLayers(getPendingTransaction());
+ assignChildLayers(getSyncTransaction());
if (setLayoutNeeded) {
setLayoutNeeded();
}
@@ -4571,6 +4586,12 @@
return mWmService.makeSurfaceBuilder(mSession).setParent(getOverlayLayer());
}
+ @Override
+ public SurfaceControl.Builder makeAnimationLeash() {
+ return mWmService.makeSurfaceBuilder(mSession).setParent(mSurfaceControl)
+ .setContainerLayer();
+ }
+
/**
* Reparents the given surface to {@link #mOverlayLayer} SurfaceControl.
*/
@@ -5659,16 +5680,18 @@
}
return false; /* continue */
}
- if (taskId != INVALID_TASK_ID) {
+ if (taskId == INVALID_TASK_ID) {
+ if (!nextWindow.canReceiveKeys()) {
+ return false; /* continue */
+ }
+ } else {
Task task = nextWindow.getTask();
if (task == null || !task.isTaskId(taskId)) {
return false; /* continue */
}
}
- if (!nextWindow.canReceiveKeys()) {
- return false; /* continue */
- }
- return true; /* stop */
+
+ return true; /* stop, match found */
}
});
}
@@ -5915,4 +5938,9 @@
return mDisplayAreaPolicy.getDisplayAreaForWindowToken(windowType, options,
ownerCanManageAppToken, roundedCornerOverlay);
}
+
+ @Override
+ DisplayContent asDisplayContent() {
+ return this;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayHashController.java b/services/core/java/com/android/server/wm/DisplayHashController.java
index 7e16c22..1262dee 100644
--- a/services/core/java/com/android/server/wm/DisplayHashController.java
+++ b/services/core/java/com/android/server/wm/DisplayHashController.java
@@ -17,7 +17,9 @@
package com.android.server.wm;
import static android.service.displayhash.DisplayHasherService.EXTRA_VERIFIED_DISPLAY_HASH;
-import static android.service.displayhash.DisplayHasherService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
+import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -32,7 +34,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -45,16 +46,21 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.service.displayhash.DisplayHashParams;
import android.service.displayhash.DisplayHasherService;
import android.service.displayhash.IDisplayHasherService;
+import android.util.Size;
import android.util.Slog;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -78,12 +84,15 @@
private final Context mContext;
/**
- * Lock used for the cached {@link #mHashAlgorithms} array
+ * Lock used for the cached {@link #mDisplayHashAlgorithms} map
*/
- private final Object mHashAlgorithmsLock = new Object();
+ private final Object mDisplayHashAlgorithmsLock = new Object();
- @GuardedBy("mHashingAlgorithmsLock")
- private String[] mHashAlgorithms;
+ /**
+ * The cached map of display hash algorithms to the {@link DisplayHashParams}
+ */
+ @GuardedBy("mDisplayHashAlgorithmsLock")
+ private Map<String, DisplayHashParams> mDisplayHashAlgorithms;
private final Handler mHandler;
@@ -104,34 +113,8 @@
}
String[] getSupportedHashAlgorithms() {
- // We have a separate lock for the hashing algorithm array since it doesn't need to make
- // the request through the service connection. Instead, we have a lock to ensure we can
- // properly cache the hashing algorithms array so we don't need to call into the
- // ExtServices process for each request.
- synchronized (mHashAlgorithmsLock) {
- // Already have cached values
- if (mHashAlgorithms != null) {
- return mHashAlgorithms;
- }
-
- final ServiceInfo serviceInfo = getServiceInfo();
- if (serviceInfo == null) return null;
-
- final PackageManager pm = mContext.getPackageManager();
- final Resources res;
- try {
- res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Error getting application resources for " + serviceInfo, e);
- return null;
- }
-
- final int resourceId = serviceInfo.metaData.getInt(
- SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
- mHashAlgorithms = res.getStringArray(resourceId);
-
- return mHashAlgorithms;
- }
+ Map<String, DisplayHashParams> displayHashAlgorithms = getDisplayHashAlgorithms();
+ return displayHashAlgorithms.keySet().toArray(new String[0]);
}
@Nullable
@@ -148,13 +131,76 @@
return results.getParcelable(EXTRA_VERIFIED_DISPLAY_HASH);
}
- void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
+ private void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
String hashAlgorithm, RemoteCallback callback) {
connectAndRun(
service -> service.generateDisplayHash(mSalt, buffer, bounds, hashAlgorithm,
callback));
}
+ void generateDisplayHash(SurfaceControl.LayerCaptureArgs.Builder args,
+ Rect boundsInWindow, String hashAlgorithm, RemoteCallback callback) {
+ final Map<String, DisplayHashParams> displayHashAlgorithmsMap = getDisplayHashAlgorithms();
+ DisplayHashParams displayHashParams = displayHashAlgorithmsMap.get(hashAlgorithm);
+ if (displayHashParams == null) {
+ Slog.w(TAG, "Failed to generateDisplayHash. Invalid hashAlgorithm");
+ sendDisplayHashError(callback, DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM);
+ return;
+ }
+
+ Size size = displayHashParams.getBufferSize();
+ if (size != null && (size.getWidth() > 0 || size.getHeight() > 0)) {
+ args.setFrameScale((float) size.getWidth() / boundsInWindow.width(),
+ (float) size.getHeight() / boundsInWindow.height());
+ }
+
+ args.setGrayscale(displayHashParams.isGrayscaleBuffer());
+
+ SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
+ SurfaceControl.captureLayers(args.build());
+ if (screenshotHardwareBuffer == null
+ || screenshotHardwareBuffer.getHardwareBuffer() == null) {
+ Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
+ sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
+ return;
+ }
+
+ generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow,
+ hashAlgorithm, callback);
+ }
+
+ private Map<String, DisplayHashParams> getDisplayHashAlgorithms() {
+ // We have a separate lock for the hashing params to ensure we can properly cache the
+ // hashing params so we don't need to call into the ExtServices process for each request.
+ synchronized (mDisplayHashAlgorithmsLock) {
+ if (mDisplayHashAlgorithms != null) {
+ return mDisplayHashAlgorithms;
+ }
+
+ final SyncCommand syncCommand = new SyncCommand();
+ Bundle results = syncCommand.run((service, remoteCallback) -> {
+ try {
+ service.getDisplayHashAlgorithms(remoteCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke getDisplayHashAlgorithms command", e);
+ }
+ });
+
+ mDisplayHashAlgorithms = new HashMap<>(results.size());
+ for (String key : results.keySet()) {
+ mDisplayHashAlgorithms.put(key, results.getParcelable(key));
+ }
+
+ return mDisplayHashAlgorithms;
+ }
+ }
+
+ void sendDisplayHashError(RemoteCallback callback, int errorCode) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
+ callback.sendResult(bundle);
+ }
+
/**
* Calculate the bounds to generate the hash for. This takes into account window transform,
* magnification, and display bounds.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 01f0359..d929d50 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -448,7 +448,8 @@
final Looper looper = UiThread.getHandler().getLooper();
mHandler = new PolicyHandler(looper);
- mSystemGestures = new SystemGesturesPointerEventListener(mContext, mHandler,
+ // TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context.
+ mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
new SystemGesturesPointerEventListener.Callbacks() {
@Override
public void onSwipeFromTop() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 5df1355..d0e4c40 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,6 +22,7 @@
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
@@ -408,8 +409,11 @@
* THE SCREEN.
*/
boolean updateRotationUnchecked(boolean forceUpdate) {
+ final boolean useShellTransitions =
+ mService.mAtmService.getTransitionController().getTransitionPlayer() != null;
+
final int displayId = mDisplayContent.getDisplayId();
- if (!forceUpdate) {
+ if (!forceUpdate && !useShellTransitions) {
if (mDeferredRotationPauseCount > 0) {
// Rotation updates have been paused temporarily. Defer the update until updates
// have been resumed.
@@ -472,6 +476,12 @@
return false;
}
+ final Transition t = (useShellTransitions
+ && !mService.mAtmService.getTransitionController().isCollecting())
+ ? mService.mAtmService.getTransitionController().createTransition(TRANSIT_CHANGE)
+ : null;
+ mService.mAtmService.getTransitionController().collect(mDisplayContent);
+
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d rotation changed to %d from %d, lastOrientation=%d",
displayId, rotation, oldRotation, lastOrientation);
@@ -484,6 +494,20 @@
mDisplayContent.setLayoutNeeded();
+ if (useShellTransitions) {
+ if (t != null) {
+ // This created its own transition, so send a start request.
+ mService.mAtmService.getTransitionController().requestStartTransition(
+ t, null /* trigger */, null /* remote */);
+ } else {
+ // Use remote-rotation infra since the transition has already been requested
+ // TODO(shell-transitions): Remove this once lifecycle management can cover all
+ // rotation cases.
+ startRemoteRotation(oldRotation, mRotation);
+ }
+ return true;
+ }
+
mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
@@ -504,6 +528,19 @@
}
/**
+ * Utility to get a rotating displaycontent from a Transition.
+ * @return null if the transition doesn't contain a rotating display.
+ */
+ static DisplayContent getDisplayFromTransition(Transition transition) {
+ for (int i = transition.mParticipants.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = transition.mParticipants.valueAt(i);
+ if (!(wc instanceof DisplayContent)) continue;
+ return (DisplayContent) wc;
+ }
+ return null;
+ }
+
+ /**
* A Remote rotation is when we are waiting for some registered (remote)
* {@link IDisplayWindowRotationController} to calculate and return some hierarchy operations
* to perform in sync with the rotation.
@@ -537,6 +574,22 @@
}
mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
mIsWaitingForRemoteRotation = false;
+
+ if (mService.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+ if (!mService.mAtmService.getTransitionController().isCollecting()) {
+ throw new IllegalStateException("Trying to rotate outside a transition");
+ }
+ mService.mAtmService.getTransitionController().collect(mDisplayContent);
+ // Go through all tasks and collect them before the rotation
+ // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
+ // handling is synchronized.
+ mDisplayContent.forAllTasks(task -> {
+ if (task.isVisible()) {
+ mService.mAtmService.getTransitionController().collect(task);
+ }
+ });
+ mDisplayContent.getInsetsStateController().addProvidersToTransition();
+ }
mService.mAtmService.deferWindowLayout();
try {
mDisplayContent.sendNewConfiguration();
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 999c585..62e4a85 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -16,12 +16,15 @@
package com.android.server.wm;
+import static java.lang.Integer.toHexString;
+
import android.app.UriGrantsManager;
import android.content.ClipData;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.LocalServices;
@@ -32,6 +35,9 @@
class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
implements IBinder.DeathRecipient {
+ private static final String TAG = "DragAndDrop";
+ private static final boolean DEBUG = false;
+
private final WindowManagerGlobalLock mGlobalLock;
private final int mSourceUid;
private final String mTargetPackage;
@@ -43,7 +49,7 @@
private IBinder mActivityToken = null;
private IBinder mPermissionOwnerToken = null;
- private IBinder mTransientToken = null;
+ private IBinder mAppToken = null;
DragAndDropPermissionsHandler(WindowManagerGlobalLock lock, ClipData clipData, int sourceUid,
String targetPackage, int mode, int sourceUserId, int targetUserId) {
@@ -62,6 +68,10 @@
if (mActivityToken != null || mPermissionOwnerToken != null) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": taking permissions bound to activity: "
+ + toHexString(activityToken.hashCode()));
+ }
mActivityToken = activityToken;
// Will throw if Activity is not found.
@@ -84,14 +94,18 @@
}
@Override
- public void takeTransient(IBinder transientToken) throws RemoteException {
+ public void takeTransient(IBinder appToken) throws RemoteException {
if (mActivityToken != null || mPermissionOwnerToken != null) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": taking permissions bound to app process: "
+ + toHexString(appToken.hashCode()));
+ }
mPermissionOwnerToken = LocalServices.getService(UriGrantsManagerInternal.class)
.newUriPermissionOwner("drop");
- mTransientToken = transientToken;
- mTransientToken.linkToDeath(this, 0);
+ mAppToken = appToken;
+ mAppToken.linkToDeath(this, 0);
doTake(mPermissionOwnerToken);
}
@@ -112,11 +126,17 @@
} finally {
mActivityToken = null;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": releasing activity-bound permissions");
+ }
} else {
permissionOwner = mPermissionOwnerToken;
mPermissionOwnerToken = null;
- mTransientToken.unlinkToDeath(this, 0);
- mTransientToken = null;
+ mAppToken.unlinkToDeath(this, 0);
+ mAppToken = null;
+ if (DEBUG) {
+ Log.d(TAG, this + ": releasing process-bound permissions");
+ }
}
UriGrantsManagerInternal ugm = LocalServices.getService(UriGrantsManagerInternal.class);
@@ -139,6 +159,9 @@
@Override
public void binderDied() {
+ if (DEBUG) {
+ Log.d(TAG, this + ": app process died: " + toHexString(mAppToken.hashCode()));
+ }
try {
release();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 1120a07..d12d07a 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -283,11 +283,7 @@
return;
}
- if (keepHandling) {
- mDragState.notifyMoveLocked(newX, newY);
- } else {
- mDragState.notifyDropLocked(newX, newY);
- }
+ mDragState.updateDragSurfaceLocked(keepHandling, newX, newY);
}
}
@@ -330,6 +326,12 @@
mDragState = null;
}
+ void reportDropWindow(IBinder token, float x, float y) {
+ synchronized (mService.mGlobalLock) {
+ mDragState.reportDropWindowLock(token, x, y);
+ }
+ }
+
private class DragHandler extends Handler {
/**
* Lock for window manager.
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 08d5e80..fd4bbd7 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -109,7 +109,6 @@
float mCurrentX, mCurrentY;
float mThumbOffsetX, mThumbOffsetY;
InputInterceptor mInputInterceptor;
- WindowState mTargetWindow;
ArrayList<WindowState> mNotifiedWindows;
boolean mDragInProgress;
/**
@@ -217,18 +216,18 @@
x = mCurrentX;
y = mCurrentY;
}
- DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+ DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, null, null,
mDragResult);
try {
- ws.mClient.dispatchDragEvent(evt);
+ ws.mClient.dispatchDragEvent(event);
} catch (RemoteException e) {
Slog.w(TAG_WM, "Unable to drag-end window " + ws);
}
// if the current window is in the same process,
// the dispatch has already recycled the event
if (myPid != ws.mSession.mPid) {
- evt.recycle();
+ event.recycle();
}
}
mNotifiedWindows.clear();
@@ -270,6 +269,68 @@
mDragDropController.onDragStateClosedLocked(this);
}
+ /**
+ * Notify the drop target and tells it about the data. If the drop event is not sent to the
+ * target, invokes {@code endDragLocked} immediately.
+ */
+ void reportDropWindowLock(IBinder token, float x, float y) {
+ if (mAnimator != null) {
+ return;
+ }
+
+ final WindowState touchedWin = mService.mInputToWindowMap.get(token);
+ if (!isWindowNotified(touchedWin)) {
+ // "drop" outside a valid window -- no recipient to apply a
+ // timeout to, and we can send the drag-ended message immediately.
+ mDragResult = false;
+ endDragLocked();
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
+ return;
+ }
+
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
+
+ final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+
+ final DragAndDropPermissionsHandler dragAndDropPermissions;
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+ && mData != null) {
+ dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
+ mData,
+ mUid,
+ touchedWin.getOwningPackage(),
+ mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+ mSourceUserId,
+ targetUserId);
+ } else {
+ dragAndDropPermissions = null;
+ }
+ if (mSourceUserId != targetUserId) {
+ if (mData != null) {
+ mData.fixUris(mSourceUserId);
+ }
+ }
+ final int myPid = Process.myPid();
+ final IBinder clientToken = touchedWin.mClient.asBinder();
+ final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y,
+ true /* includeData */, targetInterceptsGlobalDrag(touchedWin),
+ dragAndDropPermissions);
+ try {
+ touchedWin.mClient.dispatchDragEvent(event);
+
+ // 5 second timeout for this window to respond to the drop
+ mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken);
+ } catch (RemoteException e) {
+ Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
+ endDragLocked();
+ } finally {
+ if (myPid != touchedWin.mSession.mPid) {
+ event.recycle();
+ }
+ }
+ mToken = clientToken;
+ }
+
class InputInterceptor {
InputChannel mClientChannel;
DragInputEventReceiver mInputEventReceiver;
@@ -397,9 +458,9 @@
ClipDescription desc, ClipData data) {
final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin);
if (mDragInProgress && isValidDropTarget(newWin, interceptsGlobalDrag)) {
- DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
- touchX, touchY, mThumbOffsetX, mThumbOffsetY, null, desc,
- interceptsGlobalDrag ? data : null, null, null, false);
+ DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, touchX, touchY,
+ interceptsGlobalDrag, false /* includeDragSurface */,
+ null /* dragAndDropPermission */);
try {
newWin.mClient.dispatchDragEvent(event);
// track each window that we've notified that the drag is starting
@@ -501,13 +562,17 @@
mAnimator = createCancelAnimationLocked();
}
- void notifyMoveLocked(float x, float y) {
+ void updateDragSurfaceLocked(boolean keepHandling, float x, float y) {
if (mAnimator != null) {
return;
}
mCurrentX = x;
mCurrentY = y;
+ if (!keepHandling) {
+ return;
+ }
+
// Move the surface to the given touch
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
@@ -518,71 +583,6 @@
}
/**
- * Finds the drop target and tells it about the data. If the drop event is not sent to the
- * target, invokes {@code endDragLocked} immediately.
- */
- void notifyDropLocked(float x, float y) {
- if (mAnimator != null) {
- return;
- }
- mCurrentX = x;
- mCurrentY = y;
-
- final WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
-
- if (!isWindowNotified(touchedWin)) {
- // "drop" outside a valid window -- no recipient to apply a
- // timeout to, and we can send the drag-ended message immediately.
- mDragResult = false;
- endDragLocked();
- return;
- }
-
- if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
-
- final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
-
- final DragAndDropPermissionsHandler dragAndDropPermissions;
- if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
- && mData != null) {
- dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
- mData,
- mUid,
- touchedWin.getOwningPackage(),
- mFlags & DRAG_FLAGS_URI_PERMISSIONS,
- mSourceUserId,
- targetUserId);
- } else {
- dragAndDropPermissions = null;
- }
- if (mSourceUserId != targetUserId){
- if (mData != null) {
- mData.fixUris(mSourceUserId);
- }
- }
- final int myPid = Process.myPid();
- final IBinder token = touchedWin.mClient.asBinder();
- final DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
- mThumbOffsetX, mThumbOffsetY, null, null, mData,
- targetInterceptsGlobalDrag(touchedWin) ? mSurfaceControl : null,
- dragAndDropPermissions, false);
- try {
- touchedWin.mClient.dispatchDragEvent(evt);
-
- // 5 second timeout for this window to respond to the drop
- mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token);
- } catch (RemoteException e) {
- Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
- endDragLocked();
- } finally {
- if (myPid != touchedWin.mSession.mPid) {
- evt.recycle();
- }
- }
- mToken = token;
- }
-
- /**
* Returns true if it has sent DRAG_STARTED broadcast out but has not been sent DRAG_END
* broadcast.
*/
@@ -590,14 +590,12 @@
return mDragInProgress;
}
- private static DragEvent obtainDragEvent(WindowState win, int action, float x, float y,
- float offsetX, float offsetY, Object localState, ClipDescription description,
- ClipData data, SurfaceControl dragSurface,
- IDragAndDropPermissions dragAndDropPermissions, boolean result) {
- final float winX = win.translateToWindowX(x);
- final float winY = win.translateToWindowY(y);
- return DragEvent.obtain(action, winX, winY, offsetX, offsetY, localState, description, data,
- dragSurface, dragAndDropPermissions, result);
+ private DragEvent obtainDragEvent(int action, float x, float y, boolean includeData,
+ boolean includeDragSurface, IDragAndDropPermissions dragAndDropPermissions) {
+ return DragEvent.obtain(action, x, y, mThumbOffsetX, mThumbOffsetY,
+ null /* localState */, mDataDescription,
+ includeData ? mData : null, includeDragSurface ? mSurfaceControl : null,
+ dragAndDropPermissions, false /* result */);
}
private ValueAnimator createReturnAnimationLocked() {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 6e89581..84616c0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -229,6 +229,12 @@
mService::reportFocusChanged, oldToken, newToken));
}
+ @Override
+ public void notifyDropWindow(IBinder token, float x, float y) {
+ mService.mH.sendMessage(PooledLambda.obtainMessage(
+ mService.mDragDropController::reportDropWindow, token, x, y));
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 986f768..465042d 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -492,8 +492,11 @@
mAnimatingShown = show;
final InsetsState state = getInsetsForWindow(mFocusedWin);
+
+ // We are about to playing the default animation. Passing a null frame indicates
+ // the controlled types should be animated regardless of the frame.
mAnimationControl = new InsetsAnimationControlImpl(controls,
- state.getDisplayFrame(), state, mListener, typesReady, this,
+ null /* frame */, state, mListener, typesReady, this,
mListener.getDurationMs(), InsetsController.SYSTEM_BARS_INTERPOLATOR,
show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, null /* translator */);
SurfaceAnimationThread.getHandler().post(
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 35e5491..38ad4f0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -30,6 +30,7 @@
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;
@@ -42,6 +43,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
@@ -58,7 +60,6 @@
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
@@ -84,16 +85,6 @@
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;
@@ -112,7 +103,8 @@
mSource = source;
mDisplayContent = displayContent;
mStateController = stateController;
- mFakeControl = new InsetsSourceControl(source.getType(), null /* leash */, new Point());
+ mFakeControl = new InsetsSourceControl(
+ source.getType(), null /* leash */, new Point(), Insets.NONE);
switch (source.getType()) {
case ITYPE_STATUS_BAR:
@@ -159,6 +151,7 @@
// 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);
@@ -255,18 +248,43 @@
setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy());
updateSourceFrame();
if (mControl != null) {
+ boolean changed = false;
final Point position = getWindowFrameSurfacePosition();
if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
- if (mWin.getWindowFrames().didFrameSizeChange()) {
- mWin.applyWithNextDraw(mSetLeashPositionConsumer);
+ changed = true;
+ if (!mWin.getWindowFrames().didFrameSizeChange()) {
+ updateLeashPosition(-1 /* frameNumber */);
+ } else if (mWin.mInRelayout) {
+ updateLeashPosition(mWin.getFrameNumber());
} else {
- mSetLeashPositionConsumer.accept(mWin.getPendingTransaction());
+ mWin.mPendingPositionChanged = this;
}
+ }
+ final Insets insetsHint = mSource.calculateInsets(
+ mWin.getBounds(), true /* ignoreVisibility */);
+ if (!insetsHint.equals(mControl.getInsetsHint())) {
+ changed = true;
+ mControl.setInsetsHint(insetsHint);
+ }
+ if (changed) {
mStateController.notifyControlChanged(mControlTarget);
}
}
}
+ 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();
@@ -274,6 +292,14 @@
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)
*/
@@ -329,7 +355,8 @@
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
updateVisibility();
- mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition);
+ mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition,
+ mSource.calculateInsets(mWin.getBounds(), true /* ignoreVisibility */));
ProtoLog.d(WM_DEBUG_IME,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
}
@@ -404,7 +431,7 @@
// to the client in case that the client applies its transaction sooner than ours
// that we could unexpectedly overwrite the surface state.
return new InsetsSourceControl(mControl.getType(), null /* leash */,
- mControl.getSurfacePosition());
+ mControl.getSurfacePosition(), mControl.getInsetsHint());
}
return mControl;
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index a971794..b6057c6 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -248,6 +248,16 @@
return result;
}
+ public void addProvidersToTransition() {
+ for (int i = mProviders.size() - 1; i >= 0; --i) {
+ final InsetsSourceProvider p = mProviders.valueAt(i);
+ if (p == null) continue;
+ final WindowContainer wc = p.mWin;
+ if (wc == null) continue;
+ mDisplayContent.mAtmService.getTransitionController().collect(wc);
+ }
+ }
+
/**
* @return The provider of a specific type.
*/
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 5d6d513..4ab5cd6 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -16,6 +16,12 @@
package com.android.server.wm;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
+import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
+
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
@@ -26,6 +32,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
@@ -35,9 +42,11 @@
class NonAppWindowAnimationAdapter implements AnimationAdapter {
- private final WindowState mWindow;
+ private final WindowContainer mWindowContainer;
private RemoteAnimationTarget mTarget;
private SurfaceControl mCapturedLeash;
+ private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback;
+ private @SurfaceAnimator.AnimationType int mLastAnimationType;
private long mDurationHint;
private long mStatusBarTransitionDelay;
@@ -47,21 +56,46 @@
return false;
}
- NonAppWindowAnimationAdapter(WindowState w,
- long durationHint, long statusBarTransitionDelay) {
- mWindow = w;
+ NonAppWindowAnimationAdapter(WindowContainer w, long durationHint,
+ long statusBarTransitionDelay) {
+ mWindowContainer = w;
mDurationHint = durationHint;
mStatusBarTransitionDelay = statusBarTransitionDelay;
}
+ static RemoteAnimationTarget[] startNonAppWindowAnimations(WindowManagerService service,
+ DisplayContent displayContent, @WindowManager.TransitionOldType int transit,
+ long durationHint, long statusBarTransitionDelay,
+ ArrayList<NonAppWindowAnimationAdapter> adaptersOut) {
+ final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+ if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+ startNonAppWindowAnimationsForKeyguardExit(
+ service, durationHint, statusBarTransitionDelay, targets, adaptersOut);
+ } else if (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_WALLPAPER_CLOSE) {
+ final boolean shouldAttachNavBarToApp =
+ displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
+ && service.getRecentsAnimationController() == null
+ && displayContent.getFixedRotationAnimationController() == null;
+ if (shouldAttachNavBarToApp) {
+ startNavigationBarWindowAnimation(
+ displayContent, durationHint, statusBarTransitionDelay, targets,
+ adaptersOut);
+ }
+ }
+ return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+ }
+
/**
* Creates and starts remote animations for all the visible non app windows.
*
* @return RemoteAnimationTarget[] targets for all the visible non app windows
*/
- public static RemoteAnimationTarget[] startNonAppWindowAnimationsForKeyguardExit(
- WindowManagerService service, long durationHint, long statusBarTransitionDelay) {
- final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+ private static void startNonAppWindowAnimationsForKeyguardExit(WindowManagerService service,
+ long durationHint, long statusBarTransitionDelay,
+ ArrayList<RemoteAnimationTarget> targets,
+ ArrayList<NonAppWindowAnimationAdapter> adaptersOut) {
final WindowManagerPolicy policy = service.mPolicy;
service.mRoot.forAllWindows(nonAppWindow -> {
@@ -69,12 +103,30 @@
&& nonAppWindow.wouldBeVisibleIfPolicyIgnored() && !nonAppWindow.isVisible()) {
final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
nonAppWindow, durationHint, statusBarTransitionDelay);
+ adaptersOut.add(nonAppAdapter);
nonAppWindow.startAnimation(nonAppWindow.getPendingTransaction(),
nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
targets.add(nonAppAdapter.createRemoteAnimationTarget());
}
}, true /* traverseTopToBottom */);
- return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+ }
+
+ /**
+ * Creates and starts remote animation for the navigation bar windows.
+ *
+ * @return RemoteAnimationTarget[] targets for all the visible non app windows
+ */
+ private static void startNavigationBarWindowAnimation(DisplayContent displayContent,
+ long durationHint, long statusBarTransitionDelay,
+ ArrayList<RemoteAnimationTarget> targets,
+ ArrayList<NonAppWindowAnimationAdapter> adaptersOut) {
+ final WindowState navWindow = displayContent.getDisplayPolicy().getNavigationBar();
+ final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter(
+ navWindow.mToken, durationHint, statusBarTransitionDelay);
+ adaptersOut.add(nonAppAdapter);
+ navWindow.mToken.startAnimation(navWindow.mToken.getPendingTransaction(),
+ nonAppAdapter, false /* hidden */, ANIMATION_TYPE_WINDOW_ANIMATION);
+ targets.add(nonAppAdapter.createRemoteAnimationTarget());
}
/**
@@ -82,16 +134,39 @@
*/
RemoteAnimationTarget createRemoteAnimationTarget() {
mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false,
- new Rect(), null, mWindow.getPrefixOrderIndex(), mWindow.getLastSurfacePosition(),
- mWindow.getBounds(), null, mWindow.getWindowConfiguration(), true, null, null,
- null);
+ new Rect(), null, mWindowContainer.getPrefixOrderIndex(),
+ mWindowContainer.getLastSurfacePosition(), mWindowContainer.getBounds(), null,
+ mWindowContainer.getWindowConfiguration(), true, null, null, null,
+ mWindowContainer.getWindowType());
return mTarget;
}
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
mCapturedLeash = animationLeash;
+ mCapturedLeashFinishCallback = finishCallback;
+ mLastAnimationType = type;
+ }
+
+ /**
+ * @return the callback to call to clean up when the animation has finished.
+ */
+ SurfaceAnimator.OnAnimationFinishedCallback getLeashFinishedCallback() {
+ return mCapturedLeashFinishCallback;
+ }
+
+ /**
+ * @return the type of animation.
+ */
+ @SurfaceAnimator.AnimationType
+ int getLastAnimationType() {
+ return mLastAnimationType;
+ }
+
+ WindowContainer getWindowContainer() {
+ return mWindowContainer;
}
@Override
@@ -120,8 +195,8 @@
@Override
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
- pw.print("token=");
- pw.println(mWindow.mToken);
+ pw.print("windowContainer=");
+ pw.println(mWindowContainer);
if (mTarget != null) {
pw.print(prefix);
pw.println("Target:");
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 914e456..64ff108 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -20,6 +20,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.graphics.Matrix.MSCALE_X;
+import static android.graphics.Matrix.MSCALE_Y;
+import static android.graphics.Matrix.MSKEW_X;
+import static android.graphics.Matrix.MSKEW_Y;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
@@ -229,7 +233,8 @@
}
@Override
- public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+ public void setFinishTaskBounds(int taskId, Rect destinationBounds, Rect windowCrop,
+ float[] float9) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
"setFinishTaskBounds(%d): bounds=%s", taskId, destinationBounds);
final long token = Binder.clearCallingIdentity();
@@ -239,6 +244,8 @@
final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
if (taskAdapter.mTask.mTaskId == taskId) {
taskAdapter.mFinishBounds.set(destinationBounds);
+ taskAdapter.mFinishWindowCrop.set(windowCrop);
+ taskAdapter.mFinishTransform = float9;
break;
}
}
@@ -1084,6 +1091,9 @@
private final Rect mLocalBounds = new Rect();
// The bounds of the target when animation is finished
private final Rect mFinishBounds = new Rect();
+ // Bounds and transform for the final transaction.
+ private final Rect mFinishWindowCrop = new Rect();
+ private float[] mFinishTransform;
TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) {
mTask = task;
@@ -1120,13 +1130,31 @@
void onCleanup() {
if (!mFinishBounds.isEmpty()) {
- // Apply any pending bounds changes
- final SurfaceControl taskSurface = mTask.getSurfaceControl();
- mTask.getPendingTransaction()
- .setPosition(taskSurface, mFinishBounds.left, mFinishBounds.top)
- .setWindowCrop(taskSurface, mFinishBounds.width(), mFinishBounds.height())
+ final SurfaceControl taskSurface = mTask.mSurfaceControl;
+ final Transaction pendingTransaction = mTask.getPendingTransaction();
+ if (mFinishTransform != null) {
+ pendingTransaction
+ .setMatrix(taskSurface,
+ mFinishTransform[MSCALE_X], mFinishTransform[MSKEW_Y],
+ mFinishTransform[MSKEW_X], mFinishTransform[MSCALE_Y]);
+ }
+ float left = mFinishBounds.left;
+ float top = mFinishBounds.top;
+ if (!mFinishWindowCrop.isEmpty()) {
+ pendingTransaction.setWindowCrop(taskSurface, mFinishWindowCrop);
+ if (mFinishTransform != null) {
+ // adjust the position for insets.
+ left -= mFinishWindowCrop.left * mFinishTransform[MSCALE_X];
+ top -= mFinishWindowCrop.top * mFinishTransform[MSCALE_Y];
+ }
+ }
+ pendingTransaction
+ .setPosition(taskSurface, left, top)
.apply();
mTask.mLastRecentsAnimationBounds.set(mFinishBounds);
+ // reset the variables
+ mFinishTransform = null;
+ mFinishWindowCrop.setEmpty();
mFinishBounds.setEmpty();
} else if (!mTask.isAttached()) {
// Apply the task's pending transaction in case it is detached and its transaction
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 6fc585e..f851e35 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -16,9 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
@@ -41,6 +38,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FastPrintWriter;
@@ -60,10 +58,13 @@
private static final long TIMEOUT_MS = 2000;
private final WindowManagerService mService;
+ private final DisplayContent mDisplayContent;
private final RemoteAnimationAdapter mRemoteAnimationAdapter;
private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
new ArrayList<>();
+ @VisibleForTesting
+ final ArrayList<NonAppWindowAnimationAdapter> mPendingNonAppAnimations = new ArrayList<>();
private final Rect mTmpRect = new Rect();
private final Handler mHandler;
private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -72,9 +73,10 @@
private boolean mCanceled;
private boolean mLinkedToDeathOfRunner;
- RemoteAnimationController(WindowManagerService service,
+ RemoteAnimationController(WindowManagerService service, DisplayContent displayContent,
RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) {
mService = service;
+ mDisplayContent = displayContent;
mRemoteAnimationAdapter = remoteAnimationAdapter;
mHandler = handler;
}
@@ -224,12 +226,12 @@
private RemoteAnimationTarget[] createNonAppWindowAnimations(
@WindowManager.TransitionOldType int transit) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createNonAppWindowAnimations()");
- return (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER)
- ? NonAppWindowAnimationAdapter.startNonAppWindowAnimationsForKeyguardExit(mService,
- mRemoteAnimationAdapter.getDuration(),
- mRemoteAnimationAdapter.getStatusBarTransitionDelay())
- : new RemoteAnimationTarget[0];
+ return NonAppWindowAnimationAdapter.startNonAppWindowAnimations(mService,
+ mDisplayContent,
+ transit,
+ mRemoteAnimationAdapter.getDuration(),
+ mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
+ mPendingNonAppAnimations);
}
private void onAnimationFinished() {
@@ -267,6 +269,15 @@
mPendingWallpaperAnimations.remove(i);
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\twallpaper=%s", adapter.getToken());
}
+
+ for (int i = mPendingNonAppAnimations.size() - 1; i >= 0; i--) {
+ final NonAppWindowAnimationAdapter adapter = mPendingNonAppAnimations.get(i);
+ adapter.getLeashFinishedCallback().onAnimationFinished(
+ adapter.getLastAnimationType(), adapter);
+ mPendingNonAppAnimations.remove(i);
+ ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tnonApp=%s",
+ adapter.getWindowContainer());
+ }
} catch (Exception e) {
Slog.e(TAG, "Failed to finish remote animation", e);
throw e;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 1212302..422d4e7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -273,7 +273,7 @@
// Whether tasks have moved and we need to rank the tasks before next OOM scoring
private boolean mTaskLayersChanged = true;
private int mTmpTaskLayerRank;
- private final LockedScheduler mRankTaskLayersScheduler;
+ private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
private boolean mTmpBoolean;
private RemoteException mTmpRemoteException;
@@ -451,12 +451,6 @@
mTaskSupervisor = mService.mTaskSupervisor;
mTaskSupervisor.mRootWindowContainer = this;
mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl("Display-off");
- mRankTaskLayersScheduler = new LockedScheduler(mService) {
- @Override
- public void execute() {
- rankTaskLayersIfNeeded();
- }
- };
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
@@ -2660,16 +2654,18 @@
}
void invalidateTaskLayers() {
- mTaskLayersChanged = true;
- mRankTaskLayersScheduler.scheduleIfNeeded();
+ if (!mTaskLayersChanged) {
+ mTaskLayersChanged = true;
+ mService.mH.post(mRankTaskLayersRunnable);
+ }
}
/** Generate oom-score-adjustment rank for all tasks in the system based on z-order. */
- void rankTaskLayersIfNeeded() {
- if (!mTaskLayersChanged) {
- return;
+ void rankTaskLayers() {
+ if (mTaskLayersChanged) {
+ mTaskLayersChanged = false;
+ mService.mH.removeCallbacks(mRankTaskLayersRunnable);
}
- mTaskLayersChanged = false;
mTmpTaskLayerRank = 0;
// Only rank for leaf tasks because the score of activity is based on immediate parent.
forAllLeafTasks(task -> {
@@ -2818,7 +2814,7 @@
* @param launchParams The resolved launch params to use.
* @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
* @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
- * @return The roott task to use for the launch or INVALID_TASK_ID.
+ * @return The root task to use for the launch or INVALID_TASK_ID.
*/
Task getLaunchRootTask(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable Task candidateTask, boolean onTop,
@@ -2887,7 +2883,7 @@
// Falling back to default task container
taskDisplayArea = taskDisplayArea.mDisplayContent.getDefaultTaskDisplayArea();
rootTask = taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
- activityType, onTop);
+ launchParams, activityType, onTop);
if (rootTask != null) {
return rootTask;
}
@@ -2942,7 +2938,8 @@
}
}
- return container.getOrCreateRootTask(r, options, candidateTask, activityType, onTop);
+ return container.getOrCreateRootTask(
+ r, options, candidateTask, launchParams, activityType, onTop);
}
/** @return true if activity record is null or can be launched on provided display. */
@@ -3668,32 +3665,14 @@
}
}
- /**
- * Helper class to schedule the runnable if it hasn't scheduled on display thread inside window
- * manager lock.
- */
- abstract static class LockedScheduler implements Runnable {
- private final ActivityTaskManagerService mService;
- private boolean mScheduled;
-
- LockedScheduler(ActivityTaskManagerService service) {
- mService = service;
- }
-
+ private class RankTaskLayersRunnable implements Runnable {
@Override
public void run() {
synchronized (mService.mGlobalLock) {
- mScheduled = false;
- execute();
- }
- }
-
- abstract void execute();
-
- void scheduleIfNeeded() {
- if (!mScheduled) {
- mService.mH.post(this);
- mScheduled = true;
+ if (mTaskLayersChanged) {
+ mTaskLayersChanged = false;
+ rankTaskLayers();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index 1e8b8a5..4cc369f 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -102,6 +102,10 @@
* window in the new orientation.
*/
void finish(Transaction t, WindowContainer win) {
+ if (win.mSurfaceControl == null || !win.mSurfaceControl.isValid()) {
+ return;
+ }
+
mTransform.reset();
t.setMatrix(win.mSurfaceControl, mTransform, mFloat9);
t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y);
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 6c46135..0708569 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -52,8 +52,8 @@
int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
if (!DEBUG_ENABLE_SHELL_DRAWER) {
- return mService.mPolicy.addSplashScreen(activity.token, packageName, theme,
- compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+ return mService.mPolicy.addSplashScreen(activity.token, activity.mUserId, packageName,
+ theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
overrideConfig, displayId);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a4b4726..09a8e4f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2382,11 +2382,11 @@
}
@Override
- void migrateToNewSurfaceControl() {
- super.migrateToNewSurfaceControl();
+ void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
+ super.migrateToNewSurfaceControl(t);
mLastSurfaceSize.x = 0;
mLastSurfaceSize.y = 0;
- updateSurfaceSize(getPendingTransaction());
+ updateSurfaceSize(t);
}
void updateSurfaceSize(SurfaceControl.Transaction transaction) {
@@ -4911,9 +4911,15 @@
task.mLastNonFullscreenBounds = lastNonFullscreenBounds;
task.setBounds(lastNonFullscreenBounds);
task.mWindowLayoutAffinity = windowLayoutAffinity;
+ if (activities.size() > 0) {
+ // We need to add the task into hierarchy before adding child to it.
+ final DisplayContent dc =
+ taskSupervisor.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
+ dc.getDefaultTaskDisplayArea().addChild(task, POSITION_BOTTOM);
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- task.addChild(activities.get(activityNdx));
+ for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+ task.addChild(activities.get(activityNdx));
+ }
}
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Restored task=" + task);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 91aa48e..88e9ae9 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -57,6 +57,7 @@
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.wm.LaunchParamsController.LaunchParams;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -962,6 +963,22 @@
}
}
+ @Override
+ void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
+ super.migrateToNewSurfaceControl(t);
+ if (mAppAnimationLayer == null) {
+ return;
+ }
+
+ // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
+ t.reparent(mAppAnimationLayer, mSurfaceControl);
+ t.reparent(mBoostedAppAnimationLayer, mSurfaceControl);
+ t.reparent(mHomeAppAnimationLayer, mSurfaceControl);
+ t.reparent(mSplitScreenDividerAnchor, mSurfaceControl);
+ reassignLayer(t);
+ scheduleAnimation();
+ }
+
void onRootTaskRemoved(Task rootTask) {
if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
Slog.v(TAG_ROOT_TASK, "onRootTaskRemoved: detaching " + rootTask + " from displayId="
@@ -1072,11 +1089,17 @@
* @see #getOrCreateRootTask(int, int, boolean)
*/
Task getOrCreateRootTask(@Nullable ActivityRecord r,
- @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
- boolean onTop) {
- // First preference is the windowing mode in the activity options if set.
- int windowingMode = (options != null)
- ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+ @Nullable ActivityOptions options, @Nullable Task candidateTask,
+ @Nullable LaunchParams launchParams, int activityType, boolean onTop) {
+ int windowingMode = WINDOWING_MODE_UNDEFINED;
+ if (launchParams != null) {
+ // If launchParams isn't null, windowing mode is already resolved.
+ windowingMode = launchParams.mWindowingMode;
+ } else if (options != null) {
+ // If launchParams is null and options isn't let's use the windowing mode in the
+ // options.
+ windowingMode = options.getLaunchWindowingMode();
+ }
// Validate that our desired windowingMode will work under the current conditions.
// UNDEFINED windowing mode is a valid result and means that the new root task will inherit
// it's display's windowing mode.
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 385dc79..565804f 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -153,7 +153,9 @@
// }
try {
mTaskOrganizer.removeStartingWindow(task.mTaskId, firstWindowLeash, mainFrame,
- prepareAnimation);
+ /* TODO(183004107) Revert this when jankiness is solved
+ prepareAnimation); */ false);
+
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
}
@@ -310,7 +312,7 @@
boolean taskAppearedSent = t.mTaskAppearedSent;
if (taskAppearedSent) {
if (t.getSurfaceControl() != null) {
- t.migrateToNewSurfaceControl();
+ t.migrateToNewSurfaceControl(t.getSyncTransaction());
}
t.mTaskAppearedSent = false;
}
@@ -884,6 +886,7 @@
mPendingTaskEvents.remove(pending);
}
mPendingTaskEvents.add(pending);
+ mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
return true;
}
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index 855dd7e..b8d2feb 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -328,10 +328,19 @@
// mWriteQueue.add(new TaskWriteQueueItem(task));
final int taskId = task.mTaskId;
- if (mService.mRootWindowContainer.anyTaskForId(taskId,
+ final boolean persistedTask = task.hasActivity();
+ if (persistedTask && mRecentTasks.getTask(taskId) != null) {
+ // The persisted task is added into hierarchy and will also be
+ // added to recent tasks later. So this task should not exist
+ // in recent tasks before it is added.
+ Slog.wtf(TAG, "Existing persisted task with taskId " + taskId
+ + " found");
+ } else if (!persistedTask
+ && mService.mRootWindowContainer.anyTaskForId(taskId,
MATCH_ATTACHED_TASK_OR_RECENT_TASKS) != null) {
// Should not happen.
- Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
+ Slog.wtf(TAG, "Existing task with taskId " + taskId
+ + " found");
} else if (userId != task.mUserId) {
// Should not happen.
Slog.wtf(TAG, "Task with userId " + task.mUserId + " found in "
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index aadb272..75be444 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -411,8 +412,9 @@
private static boolean reportIfNotTop(WindowContainer wc) {
// Organized tasks need to be reported anyways because Core won't show() their surfaces
// and we can't rely on onTaskAppeared because it isn't in sync.
+ // Also report wallpaper so it can be handled properly during display change/rotation.
// TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
- return wc.isOrganized();
+ return wc.isOrganized() || isWallpaper(wc);
}
/** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
@@ -430,7 +432,7 @@
}
private static boolean isWallpaper(WindowContainer wc) {
- return wc instanceof WallpaperWindowToken;
+ return wc.asWallpaperToken() != null;
}
/**
@@ -576,7 +578,8 @@
final ArrayList<WindowContainer> tmpList = new ArrayList<>();
// Build initial set of top-level participants by removing any participants that are no-ops
- // or children of other participants or are otherwise invalid.
+ // or children of other participants or are otherwise invalid; however, keep around a list
+ // of participants that should always be reported even if they aren't top.
for (WindowContainer wc : participants) {
// Don't include detached windows.
if (!wc.isAttached()) continue;
@@ -584,7 +587,11 @@
final ChangeInfo changeInfo = changes.get(wc);
// Reject no-ops
- if (!changeInfo.hasChanged(wc)) continue;
+ if (!changeInfo.hasChanged(wc)) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Rejecting as no-op: %s", wc);
+ continue;
+ }
// Search through ancestors to find the top-most participant (if one exists)
WindowContainer topParent = null;
@@ -651,10 +658,21 @@
/** Gets the leash surface for a window container */
private static SurfaceControl getLeashSurface(WindowContainer wc) {
+ final DisplayContent asDC = wc.asDisplayContent();
+ if (asDC != null) {
+ // DisplayContent is the "root", so we use the windowing layer instead to avoid
+ // hardware-screen-level surfaces.
+ return asDC.getWindowingLayer();
+ }
return wc.getSurfaceControl();
}
private static SurfaceControl getOrigParentSurface(WindowContainer wc) {
+ if (wc.asDisplayContent() != null) {
+ // DisplayContent is the "root", so we reinterpret it's wc as the window layer
+ // making the parent surface the displaycontent's surface.
+ return wc.getSurfaceControl();
+ }
return wc.getParent().getSurfaceControl();
}
@@ -744,6 +762,7 @@
change.setEndRelOffset(target.getBounds().left - target.getParent().getBounds().left,
target.getBounds().top - target.getParent().getBounds().top);
change.setFlags(info.getChangeFlags(target));
+ change.setRotation(info.mRotation, target.getWindowConfiguration().getRotation());
final Task task = target.asTask();
if (task != null) {
final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
@@ -773,12 +792,14 @@
int mWindowingMode;
final Rect mAbsoluteBounds = new Rect();
boolean mShowWallpaper;
+ int mRotation = ROTATION_UNDEFINED;
ChangeInfo(@NonNull WindowContainer origState) {
mVisible = origState.isVisibleRequested();
mWindowingMode = origState.getWindowingMode();
mAbsoluteBounds.set(origState.getBounds());
mShowWallpaper = origState.showWallpaper();
+ mRotation = origState.getWindowConfiguration().getRotation();
}
@VisibleForTesting
@@ -797,7 +818,8 @@
// if mWindowingMode is 0, this container wasn't attached at collect time, so
// assume no change in windowing-mode.
|| (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode)
- || !newState.getBounds().equals(mAbsoluteBounds);
+ || !newState.getBounds().equals(mAbsoluteBounds)
+ || mRotation != newState.getWindowConfiguration().getRotation();
}
@TransitionInfo.TransitionMode
@@ -826,8 +848,7 @@
// checks to use requested visibility.
flags |= FLAG_TRANSLUCENT;
}
- if (wc instanceof ActivityRecord
- && wc.asActivityRecord().mUseTransferredAnimation) {
+ if (wc.asActivityRecord() != null && wc.asActivityRecord().mUseTransferredAnimation) {
flags |= FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
}
if (isWallpaper(wc)) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8d85958..a5843d4 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -29,6 +29,7 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
+import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -389,12 +390,12 @@
mParent.onChildAdded(this);
}
if (!mReparenting) {
+ onSyncReparent(oldParent, mParent);
if (mParent != null && mParent.mDisplayContent != null
&& mDisplayContent != mParent.mDisplayContent) {
onDisplayChanged(mParent.mDisplayContent);
}
onParentChanged(mParent, oldParent);
- onSyncReparent(oldParent, mParent);
}
}
@@ -460,8 +461,7 @@
* This is used to revoke control of the SurfaceControl from a client process that was
* previously organizing this WindowContainer.
*/
- void migrateToNewSurfaceControl() {
- SurfaceControl.Transaction t = getPendingTransaction();
+ void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
t.remove(mSurfaceControl);
// Clear the last position so the new SurfaceControl will get correct position
mLastSurfacePosition.set(0, 0);
@@ -3084,6 +3084,11 @@
return null;
}
+ /** Cheap way of doing cast and instanceof. */
+ DisplayContent asDisplayContent() {
+ return null;
+ }
+
/**
* @return {@code true} if window container is manage by a
* {@link android.window.WindowOrganizer}
@@ -3309,4 +3314,11 @@
mListeners.remove(listener);
unregisterConfigurationChangeListener(listener);
}
+
+ /**
+ * Returns the {@link WindowManager.LayoutParams.WindowType}.
+ */
+ @WindowManager.LayoutParams.WindowType int getWindowType() {
+ return INVALID_WINDOW_TYPE;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index ffd6d21..9245f8c 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 updating resizing window.
+ * @return true if the width or height has changed since last reported to the client.
*/
boolean didFrameSizeChange() {
return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height());
@@ -135,13 +135,6 @@
}
/**
- * @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/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 74337c2..0840441 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -44,7 +44,7 @@
static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
static final boolean DEBUG_WALLPAPER = false;
static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
- static final boolean DEBUG_DRAG = false;
+ static final boolean DEBUG_DRAG = true;
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = false;
static final boolean DEBUG_WINDOW_TRACE = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 53ebfb2..8148f15 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -451,24 +451,15 @@
public abstract int getInputMethodWindowVisibleHeight(int displayId);
/**
- * Notifies WindowManagerService that the current IME window status is being changed.
+ * Notifies WindowManagerService that the expected back-button behavior might have changed.
*
* <p>Only {@link com.android.server.inputmethod.InputMethodManagerService} is the expected and
* tested caller of this method.</p>
*
- * @param imeToken token to track the active input method. Corresponding IME windows can be
- * identified by checking {@link android.view.WindowManager.LayoutParams#token}.
- * Note that there is no guarantee that the corresponding window is already
- * created
- * @param imeWindowVisible whether the active IME thinks that its window should be visible or
- * hidden, no matter how WindowManagerService will react / has reacted
- * to corresponding API calls. Note that this state is not guaranteed
- * to be synchronized with state in WindowManagerService.
* @param dismissImeOnBackKeyPressed {@code true} if the software keyboard is shown and the back
* key is expected to dismiss the software keyboard.
*/
- public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
- boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed);
+ public abstract void setDismissImeOnBackKeyPressed(boolean dismissImeOnBackKeyPressed);
/**
* Notifies WindowManagerService that the current IME window status is being changed.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b95674e..57394d6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -84,8 +84,6 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
-import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
-import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
@@ -233,7 +231,7 @@
import android.view.IPinnedTaskListener;
import android.view.IRecentsAnimationRunner;
import android.view.IRotationWatcher;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.ISystemGestureExclusionListener;
import android.view.IWallpaperVisibilityListener;
import android.view.IWindow;
@@ -1602,11 +1600,17 @@
ProtoLog.w(WM_ERROR, "Attempted to add window with exiting application token "
+ ".%s Aborting.", token);
return WindowManagerGlobal.ADD_APP_EXITING;
- } else if (type == TYPE_APPLICATION_STARTING && activity.mStartingWindow != null) {
- ProtoLog.w(WM_ERROR,
- "Attempted to add starting window to token with already existing"
- + " starting window");
- return WindowManagerGlobal.ADD_DUPLICATE_ADD;
+ } else if (type == TYPE_APPLICATION_STARTING) {
+ if (activity.mStartingWindow != null) {
+ ProtoLog.w(WM_ERROR, "Attempted to add starting window to "
+ + "token with already existing starting window");
+ return WindowManagerGlobal.ADD_DUPLICATE_ADD;
+ }
+ if (activity.mStartingData == null) {
+ ProtoLog.w(WM_ERROR, "Attempted to add starting window to "
+ + "token but already cleaned");
+ return WindowManagerGlobal.ADD_DUPLICATE_ADD;
+ }
}
} else if (rootType == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) {
@@ -2234,6 +2238,11 @@
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;
@@ -4139,12 +4148,19 @@
.notifyOnActivityRotation(displayContent.mDisplayId);
}
- if (!rotationChanged || forceRelayout) {
- displayContent.setLayoutNeeded();
- layoutNeeded = true;
- }
- if (rotationChanged || alwaysSendConfiguration) {
- displayContent.sendNewConfiguration();
+ final boolean pendingRemoteRotation = rotationChanged
+ && (displayContent.getDisplayRotation().isWaitingForRemoteRotation()
+ || mAtmService.getTransitionController().isCollecting());
+ // Even if alwaysSend, we are waiting for a transition or remote to provide
+ // rotated configuration, so we can't update configuration yet.
+ if (!pendingRemoteRotation) {
+ if (!rotationChanged || forceRelayout) {
+ displayContent.setLayoutNeeded();
+ layoutNeeded = true;
+ }
+ if (rotationChanged || alwaysSendConfiguration) {
+ displayContent.sendNewConfiguration();
+ }
}
}
@@ -7142,10 +7158,10 @@
* @param displayId the display for the request
* @param behindClient token for a window, used to filter the search to windows behind it
* @param taskId specifies the id of a task the result must belong to or -1 to match any task
- * @param callbacks to receive responses
+ * @param listener to receive the response
*/
public void requestScrollCapture(int displayId, @Nullable IBinder behindClient, int taskId,
- IScrollCaptureCallbacks callbacks) {
+ IScrollCaptureResponseListener listener) {
if (!checkCallingPermission(READ_FRAME_BUFFER, "requestScrollCapture()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
@@ -7158,7 +7174,7 @@
ProtoLog.e(WM_ERROR,
"Invalid displayId for requestScrollCapture: %d", displayId);
responseBuilder.setDescription(String.format("bad displayId: %d", displayId));
- callbacks.onScrollCaptureResponse(responseBuilder.build());
+ listener.onScrollCaptureResponse(responseBuilder.build());
return;
}
WindowState topWindow = null;
@@ -7168,19 +7184,19 @@
WindowState targetWindow = dc.findScrollCaptureTargetWindow(topWindow, taskId);
if (targetWindow == null) {
responseBuilder.setDescription("findScrollCaptureTargetWindow returned null");
- callbacks.onScrollCaptureResponse(responseBuilder.build());
+ listener.onScrollCaptureResponse(responseBuilder.build());
return;
}
try {
// Forward to the window for handling, which will respond using the callback.
- targetWindow.mClient.requestScrollCapture(callbacks);
+ targetWindow.mClient.requestScrollCapture(listener);
} catch (RemoteException e) {
ProtoLog.w(WM_ERROR,
"requestScrollCapture: caught exception dispatching to window."
+ "token=%s", targetWindow.mClient.asBinder());
responseBuilder.setWindowTitle(targetWindow.getName());
responseBuilder.setDescription(String.format("caught exception: %s", e));
- callbacks.onScrollCaptureResponse(responseBuilder.build());
+ listener.onScrollCaptureResponse(responseBuilder.build());
}
}
} catch (RemoteException e) {
@@ -7715,8 +7731,7 @@
}
@Override
- public void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
- boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed) {
+ public void setDismissImeOnBackKeyPressed(boolean dismissImeOnBackKeyPressed) {
mPolicy.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed);
}
@@ -8633,14 +8648,16 @@
final WindowState win = windowForClientLocked(session, window, false);
if (win == null) {
Slog.w(TAG, "Failed to generate DisplayHash. Invalid window");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_MISSING_WINDOW);
+ mDisplayHashController.sendDisplayHashError(callback,
+ DISPLAY_HASH_ERROR_MISSING_WINDOW);
return;
}
DisplayContent displayContent = win.getDisplayContent();
if (displayContent == null) {
Slog.w(TAG, "Failed to generate DisplayHash. Window is not on a display");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+ mDisplayHashController.sendDisplayHashError(callback,
+ DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
return;
}
@@ -8650,7 +8667,8 @@
if (boundsInDisplay.isEmpty()) {
Slog.w(TAG, "Failed to generate DisplayHash. Bounds are not on screen");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+ mDisplayHashController.sendDisplayHashError(callback,
+ DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
return;
}
}
@@ -8660,23 +8678,13 @@
// be covering it with the same uid. We want to make sure we include content that's
// covering to ensure we get as close as possible to what the user sees
final int uid = session.mUid;
- SurfaceControl.LayerCaptureArgs args =
+ SurfaceControl.LayerCaptureArgs.Builder args =
new SurfaceControl.LayerCaptureArgs.Builder(displaySurfaceControl)
.setUid(uid)
- .setSourceCrop(boundsInDisplay)
- .build();
+ .setSourceCrop(boundsInDisplay);
- SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
- SurfaceControl.captureLayers(args);
- if (screenshotHardwareBuffer == null
- || screenshotHardwareBuffer.getHardwareBuffer() == null) {
- Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
- return;
- }
-
- mDisplayHashController.generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(),
- boundsInWindow, hashAlgorithm, callback);
+ mDisplayHashController.generateDisplayHash(args, boundsInWindow,
+ hashAlgorithm, callback);
}
boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
@@ -8694,10 +8702,4 @@
return snapshot != null && snapshot.hasImeSurface();
}
}
-
- private void sendDisplayHashError(RemoteCallback callback, int errorCode) {
- Bundle bundle = new Bundle();
- bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
- callback.sendResult(bundle);
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9973664..e3d549b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -246,6 +246,21 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
try {
+ if (transition != null) {
+ // First check if we have a display rotation transition and if so, update it.
+ final DisplayContent dc = DisplayRotation.getDisplayFromTransition(transition);
+ if (dc != null && transition.mChanges.get(dc).mRotation != dc.getRotation()) {
+ // Go through all tasks and collect them before the rotation
+ // TODO(shell-transitions): move collect() to onConfigurationChange once
+ // wallpaper handling is synchronized.
+ dc.forAllTasks(task -> {
+ if (task.isVisible()) transition.collect(task);
+ });
+ dc.getInsetsStateController().addProvidersToTransition();
+ dc.sendNewConfiguration();
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index c5e24a9..bac1ab1 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -225,6 +225,7 @@
private static final int ACTIVITY_STATE_FLAG_IS_STOPPING_FINISHING = 1 << 19;
private static final int ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE = 1 << 20;
private static final int ACTIVITY_STATE_FLAG_HAS_RESUMED = 1 << 21;
+ private static final int ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK = 1 << 22;
private static final int ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER = 0x0000ffff;
/**
@@ -479,7 +480,7 @@
}
void setLastActivityFinishTimeIfNeeded(long finishTime) {
- if (finishTime <= mLastActivityFinishTime || !hasVisibleActivities()) {
+ if (finishTime <= mLastActivityFinishTime || !hasActivityInVisibleTask()) {
return;
}
mLastActivityFinishTime = finishTime;
@@ -516,7 +517,7 @@
private boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed,
boolean isCheckingForFgsStart) {
return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName,
- appSwitchAllowed, isCheckingForFgsStart, hasVisibleActivities(),
+ appSwitchAllowed, isCheckingForFgsStart, hasActivityInVisibleTask(),
mInstrumentingWithBackgroundActivityStartPrivileges,
mAtm.getLastStopAppSwitchesTime(),
mLastActivityLaunchTime, mLastActivityFinishTime);
@@ -653,6 +654,10 @@
return (mActivityStateFlags & ACTIVITY_STATE_FLAG_IS_VISIBLE) != 0;
}
+ boolean hasActivityInVisibleTask() {
+ return (mActivityStateFlags & ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK) != 0;
+ }
+
@HotPath(caller = HotPath.LRU_UPDATE)
public boolean hasActivitiesOrRecentTasks() {
return mHasActivities || mHasRecentTasks;
@@ -996,11 +1001,14 @@
if (r.isVisible()) {
stateFlags |= ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE;
}
+ final Task task = r.getTask();
+ if (task != null && task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
+ stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
+ }
if (r.mVisibleRequested) {
if (r.isState(RESUMED)) {
stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
}
- final Task task = r.getTask();
if (task != null && minTaskLayer > 0) {
final int layer = task.mLayerRank;
if (layer >= 0 && minTaskLayer > layer) {
@@ -1048,7 +1056,7 @@
/** Called when the process has some oom related changes and it is going to update oom-adj. */
private void prepareOomAdjustment() {
- mAtm.mRootWindowContainer.rankTaskLayersIfNeeded();
+ mAtm.mRootWindowContainer.rankTaskLayers();
mAtm.mTaskSupervisor.computeProcessActivityStateBatch();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7ebc1cc..6d88387 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -726,6 +726,8 @@
*/
private InsetsState mFrozenInsetsState;
+ @Nullable InsetsSourceProvider mPendingPositionChanged;
+
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
@@ -772,12 +774,6 @@
updateSurfacePosition(t);
};
- private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
- if (mSurfaceControl != null && mSurfaceControl.isValid()) {
- t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
- }
- };
-
/**
* @see #setSurfaceTranslationY(int)
*/
@@ -2133,8 +2129,6 @@
: getTask().getWindowConfiguration().hasMovementAnimations();
if (mToken.okToAnimate()
&& (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
- && !mWindowFrames.didFrameSizeChange()
- && !surfaceInsetsChanging()
&& !isDragResizing()
&& hasMovementAnimation
&& !mWinAnimator.mLastHidden
@@ -5324,17 +5318,13 @@
// prior to the rotation.
if (!mSurfaceAnimator.hasLeash() && mPendingSeamlessRotate == null
&& !mLastSurfacePosition.equals(mSurfacePosition)) {
- final boolean frameSizeChanged = mWindowFrames.isFrameSizeChangeReported();
- final boolean surfaceInsetsChanged = surfaceInsetsChanging();
- final boolean surfaceSizeChanged = frameSizeChanged || surfaceInsetsChanged;
+ t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
- if (surfaceInsetsChanged) {
+ if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) {
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
- }
- if (surfaceSizeChanged) {
- applyWithNextDraw(mSetSurfacePositionConsumer);
- } else {
- mSetSurfacePositionConsumer.accept(t);
+ t.deferTransactionUntil(mSurfaceControl,
+ mWinAnimator.mSurfaceController.mSurfaceControl,
+ getFrameNumber());
}
}
}
@@ -5902,4 +5892,9 @@
void setSurfaceTranslationY(int translationY) {
mSurfaceTranslationY = translationY;
}
+
+ @Override
+ @WindowManager.LayoutParams.WindowType int getWindowType() {
+ return mAttrs.type;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 8867aa7..5276d9c8 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -764,4 +764,9 @@
forAllWindows(WindowState::clearFrozenInsetsState, true /* traverseTopToBottom */);
}
}
+
+ @Override
+ @WindowManager.LayoutParams.WindowType int getWindowType() {
+ return windowType;
+ }
}
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index a73f6c6..6cb4a63 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -123,16 +123,9 @@
}
static void android_server_SystemServer_fdtrackAbort(JNIEnv*, jobject) {
- raise(BIONIC_SIGNAL_FDTRACK);
-
- // Wait for a bit to allow fdtrack to dump backtraces to logcat.
- std::this_thread::sleep_for(5s);
-
- // Abort on a different thread to avoid ART dumping runtime stacks.
- std::thread([]() {
- LOG_ALWAYS_FATAL("b/140703823: aborting due to fd leak: check logs for fd "
- "backtraces");
- }).join();
+ sigval val;
+ val.sival_int = 1;
+ sigqueue(getpid(), BIONIC_SIGNAL_FDTRACK, val);
}
static jlong android_server_SystemServer_startIncrementalService(JNIEnv* env, jclass klass,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index be06d03..3a674c4 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -131,6 +131,7 @@
jmethodID getDeviceAlias;
jmethodID getTouchCalibrationForInputDevice;
jmethodID getContextForDisplay;
+ jmethodID notifyDropWindow;
} gServiceClassInfo;
static struct {
@@ -335,6 +336,7 @@
bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(bool enabled) override;
+ void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
/* --- PointerControllerPolicyInterface implementation --- */
@@ -905,6 +907,20 @@
checkAndClearExceptionFromCallback(env, "notifyFocusChanged");
}
+void NativeInputManager::notifyDropWindow(const sp<IBinder>& token, float x, float y) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ ALOGD("notifyDropWindow");
+#endif
+ ATRACE_CALL();
+
+ JNIEnv* env = jniEnv();
+ ScopedLocalFrame localFrame(env);
+
+ jobject tokenObj = javaObjectForIBinder(env, token);
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyDropWindow, tokenObj, x, y);
+ checkAndClearExceptionFromCallback(env, "notifyDropWindow");
+}
+
void NativeInputManager::notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
const std::vector<float>& values) {
@@ -2350,6 +2366,8 @@
GET_METHOD_ID(gServiceClassInfo.notifyFocusChanged, clazz,
"notifyFocusChanged", "(Landroid/os/IBinder;Landroid/os/IBinder;)V");
+ GET_METHOD_ID(gServiceClassInfo.notifyDropWindow, clazz, "notifyDropWindow",
+ "(Landroid/os/IBinder;FF)V");
GET_METHOD_ID(gServiceClassInfo.notifySensorEvent, clazz, "notifySensorEvent", "(IIIJ[F)V");
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index ef2d0ba..f60b354 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -32,8 +32,6 @@
#include "com_android_server_vibrator_VibratorManagerService.h"
namespace V1_0 = android::hardware::vibrator::V1_0;
-namespace V1_1 = android::hardware::vibrator::V1_1;
-namespace V1_2 = android::hardware::vibrator::V1_2;
namespace V1_3 = android::hardware::vibrator::V1_3;
namespace aidl = android::hardware::vibrator;
@@ -85,10 +83,11 @@
class VibratorControllerWrapper {
public:
VibratorControllerWrapper(JNIEnv* env, int32_t vibratorId, jobject callbackListener)
- : mHal(std::move(findVibrator(vibratorId))),
+ : mHal(findVibrator(vibratorId)),
mVibratorId(vibratorId),
mCallbackListener(env->NewGlobalRef(callbackListener)) {
- LOG_ALWAYS_FATAL_IF(mHal == nullptr, "Unable to find reference to vibrator hal");
+ LOG_ALWAYS_FATAL_IF(mHal == nullptr,
+ "Failed to connect to vibrator HAL, or vibratorId is invalid");
LOG_ALWAYS_FATAL_IF(mCallbackListener == nullptr,
"Unable to create global reference to vibration callback handler");
}
@@ -130,15 +129,15 @@
}
}
-static jlong vibratorInit(JNIEnv* env, jclass /* clazz */, jint vibratorId,
- jobject callbackListener) {
+static jlong vibratorNativeInit(JNIEnv* env, jclass /* clazz */, jint vibratorId,
+ jobject callbackListener) {
std::unique_ptr<VibratorControllerWrapper> wrapper =
std::make_unique<VibratorControllerWrapper>(env, vibratorId, callbackListener);
wrapper->hal()->init();
return reinterpret_cast<jlong>(wrapper.release());
}
-static jlong vibratorGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+static jlong vibratorGetNativeFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeWrapper));
}
@@ -286,25 +285,46 @@
wrapper->hal()->alwaysOnDisable(static_cast<int32_t>(id));
}
+static float vibratorGetResonantFrequency(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+ VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
+ if (wrapper == nullptr) {
+ ALOGE("vibratorGetResonantFrequency failed because native wrapper was not initialized");
+ return NAN;
+ }
+ auto result = wrapper->hal()->getResonantFrequency();
+ return result.isOk() ? static_cast<jfloat>(result.value()) : NAN;
+}
+
+static float vibratorGetQFactor(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+ VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
+ if (wrapper == nullptr) {
+ ALOGE("vibratorGetQFactor failed because native wrapper was not initialized");
+ return NAN;
+ }
+ auto result = wrapper->hal()->getQFactor();
+ return result.isOk() ? static_cast<jfloat>(result.value()) : NAN;
+}
+
static const JNINativeMethod method_table[] = {
- {"vibratorInit",
+ {"nativeInit",
"(ILcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;)J",
- (void*)vibratorInit},
- {"vibratorGetFinalizer", "()J", (void*)vibratorGetFinalizer},
- {"vibratorIsAvailable", "(J)Z", (void*)vibratorIsAvailable},
- {"vibratorOn", "(JJJ)V", (void*)vibratorOn},
- {"vibratorOff", "(J)V", (void*)vibratorOff},
- {"vibratorSetAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
- {"vibratorPerformEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
- {"vibratorPerformComposedEffect",
- "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J",
+ (void*)vibratorNativeInit},
+ {"getNativeFinalizer", "()J", (void*)vibratorGetNativeFinalizer},
+ {"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
+ {"on", "(JJJ)V", (void*)vibratorOn},
+ {"off", "(J)V", (void*)vibratorOff},
+ {"setAmplitude", "(JI)V", (void*)vibratorSetAmplitude},
+ {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
+ {"performComposedEffect", "(J[Landroid/os/VibrationEffect$Composition$PrimitiveEffect;J)J",
(void*)vibratorPerformComposedEffect},
- {"vibratorGetSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
- {"vibratorGetSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
- {"vibratorSetExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
- {"vibratorGetCapabilities", "(J)J", (void*)vibratorGetCapabilities},
- {"vibratorAlwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
- {"vibratorAlwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable},
+ {"getSupportedEffects", "(J)[I", (void*)vibratorGetSupportedEffects},
+ {"getSupportedPrimitives", "(J)[I", (void*)vibratorGetSupportedPrimitives},
+ {"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
+ {"getCapabilities", "(J)J", (void*)vibratorGetCapabilities},
+ {"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
+ {"alwaysOnDisable", "(JJ)V", (void*)vibratorAlwaysOnDisable},
+ {"getResonantFrequency", "(J)F", (void*)vibratorGetResonantFrequency},
+ {"getQFactor", "(J)F", (void*)vibratorGetQFactor},
};
int register_android_server_vibrator_VibratorController(JavaVM* jvm, JNIEnv* env) {
@@ -320,7 +340,8 @@
sPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F");
sPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I");
- return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorController",
+ return jniRegisterNativeMethods(env,
+ "com/android/server/vibrator/VibratorController$NativeWrapper",
method_table, NELEM(method_table));
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 48ae8d6..aed13b2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -19,6 +19,8 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.TEXT;
@@ -38,7 +40,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
-import android.util.Log;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -455,8 +456,7 @@
try {
trustAgentInfo.options.saveToXml(out);
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyManagerService.LOG_TAG,
- "Failed to save TrustAgent options", e);
+ Slog.e(LOG_TAG, e, "Failed to save TrustAgent options");
}
out.endTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS);
}
@@ -629,8 +629,7 @@
String tag = parser.getName();
if (TAG_POLICIES.equals(tag)) {
if (shouldOverridePolicies) {
- Log.d(DevicePolicyManagerService.LOG_TAG,
- "Overriding device admin policies from XML.");
+ Slog.d(LOG_TAG, "Overriding device admin policies from XML.");
info.readPoliciesFromXml(parser);
}
} else if (TAG_PASSWORD_QUALITY.equals(tag)) {
@@ -726,16 +725,14 @@
if (type == TypedXmlPullParser.TEXT) {
shortSupportMessage = parser.getText();
} else {
- Log.w(DevicePolicyManagerService.LOG_TAG,
- "Missing text when loading short support message");
+ Slog.w(LOG_TAG, "Missing text when loading short support message");
}
} else if (TAG_LONG_SUPPORT_MESSAGE.equals(tag)) {
type = parser.next();
if (type == TypedXmlPullParser.TEXT) {
longSupportMessage = parser.getText();
} else {
- Log.w(DevicePolicyManagerService.LOG_TAG,
- "Missing text when loading long support message");
+ Slog.w(LOG_TAG, "Missing text when loading long support message");
}
} else if (TAG_PARENT_ADMIN.equals(tag)) {
Preconditions.checkState(!isParent);
@@ -748,8 +745,7 @@
if (type == TypedXmlPullParser.TEXT) {
organizationName = parser.getText();
} else {
- Log.w(DevicePolicyManagerService.LOG_TAG,
- "Missing text when loading organization name");
+ Slog.w(LOG_TAG, "Missing text when loading organization name");
}
} else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) {
isLogoutEnabled = parser.getAttributeBoolean(null, ATTR_VALUE, false);
@@ -758,16 +754,14 @@
if (type == TypedXmlPullParser.TEXT) {
startUserSessionMessage = parser.getText();
} else {
- Log.w(DevicePolicyManagerService.LOG_TAG,
- "Missing text when loading start session message");
+ Slog.w(LOG_TAG, "Missing text when loading start session message");
}
} else if (TAG_END_USER_SESSION_MESSAGE.equals(tag)) {
type = parser.next();
if (type == TypedXmlPullParser.TEXT) {
endUserSessionMessage = parser.getText();
} else {
- Log.w(DevicePolicyManagerService.LOG_TAG,
- "Missing text when loading end session message");
+ Slog.w(LOG_TAG, "Missing text when loading end session message");
}
} else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
mCrossProfileCalendarPackages = readPackageList(parser, tag);
@@ -802,16 +796,14 @@
if (type == TypedXmlPullParser.TEXT) {
mOrganizationId = parser.getText();
} else {
- Log.w(DevicePolicyManagerService.LOG_TAG,
- "Missing Organization ID.");
+ Slog.w(LOG_TAG, "Missing Organization ID.");
}
} else if (TAG_ENROLLMENT_SPECIFIC_ID.equals(tag)) {
type = parser.next();
if (type == TypedXmlPullParser.TEXT) {
mEnrollmentSpecificId = parser.getText();
} else {
- Log.w(DevicePolicyManagerService.LOG_TAG,
- "Missing Enrollment-specific ID.");
+ Slog.w(LOG_TAG, "Missing Enrollment-specific ID.");
}
} else if (TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS.equals(tag)) {
mAdminCanGrantSensorsPermissions = parser.getAttributeBoolean(null, ATTR_VALUE,
@@ -820,7 +812,7 @@
mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE,
USB_DATA_SIGNALING_ENABLED_DEFAULT);
} else {
- Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
+ Slog.w(LOG_TAG, "Unknown admin tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
}
}
@@ -842,12 +834,10 @@
if (packageName != null) {
result.add(packageName);
} else {
- Slog.w(DevicePolicyManagerService.LOG_TAG,
- "Package name missing under " + outerTag);
+ Slog.w(LOG_TAG, "Package name missing under %s", outerTag);
}
} else {
- Slog.w(DevicePolicyManagerService.LOG_TAG,
- "Unknown tag under " + tag + ": " + outerTag);
+ Slog.w(LOG_TAG, "Unknown tag under %s: ", tag, outerTag);
}
}
return result;
@@ -868,8 +858,7 @@
if (tag.equals(tagDAM)) {
result.add(parser.getAttributeValue(null, ATTR_VALUE));
} else {
- Slog.e(DevicePolicyManagerService.LOG_TAG,
- "Expected tag " + tag + " but found " + tagDAM);
+ Slog.e(LOG_TAG, "Expected tag %s but found %s", tag, tagDAM);
}
}
}
@@ -891,8 +880,7 @@
final TrustAgentInfo trustAgentInfo = getTrustAgentInfo(parser, tag);
result.put(component, trustAgentInfo);
} else {
- Slog.w(DevicePolicyManagerService.LOG_TAG,
- "Unknown tag under " + tag + ": " + tagDAM);
+ Slog.w(LOG_TAG, "Unknown tag under %s: %s", tag, tagDAM);
}
}
return result;
@@ -912,8 +900,7 @@
if (TAG_TRUST_AGENT_COMPONENT_OPTIONS.equals(tagDAM)) {
result.options = PersistableBundle.restoreFromXml(parser);
} else {
- Slog.w(DevicePolicyManagerService.LOG_TAG,
- "Unknown tag under " + tag + ": " + tagDAM);
+ Slog.w(LOG_TAG, "Unknown tag under %s: %s", tag, tagDAM);
}
}
return result;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
index d812b8f..8027e5b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CertificateMonitor.java
@@ -16,6 +16,8 @@
package com.android.server.devicepolicy;
+import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
+
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -33,7 +35,7 @@
import android.security.Credentials;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -47,7 +49,6 @@
import java.util.List;
public class CertificateMonitor {
- protected static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
protected static final int MONITORING_CERT_NOTIFICATION_ID = SystemMessage.NOTE_SSL_CERT_INFO;
private final DevicePolicyManagerService mService;
@@ -78,16 +79,16 @@
X509Certificate cert = parseCert(certBuffer);
pemCert = Credentials.convertToPem(cert);
} catch (CertificateException | IOException ce) {
- Log.e(LOG_TAG, "Problem converting cert", ce);
+ Slog.e(LOG_TAG, "Problem converting cert", ce);
return null;
}
try (KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(userHandle)) {
return keyChainConnection.getService().installCaCertificate(pemCert);
} catch (RemoteException e) {
- Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
+ Slog.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
} catch (InterruptedException e1) {
- Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
+ Slog.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
Thread.currentThread().interrupt();
}
return null;
@@ -99,9 +100,9 @@
keyChainConnection.getService().deleteCaCertificate(aliases[i]);
}
} catch (RemoteException e) {
- Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
+ Slog.e(LOG_TAG, "from CaCertUninstaller: ", e);
} catch (InterruptedException ie) {
- Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
+ Slog.w(LOG_TAG, "CaCertUninstaller: ", ie);
Thread.currentThread().interrupt();
}
}
@@ -137,7 +138,8 @@
};
private void updateInstalledCertificates(final UserHandle userHandle) {
- if (!mInjector.getUserManager().isUserUnlocked(userHandle.getIdentifier())) {
+ final int userId = userHandle.getIdentifier();
+ if (!mInjector.getUserManager().isUserUnlocked(userId)) {
return;
}
@@ -145,7 +147,8 @@
try {
installedCerts = getInstalledCaCertificates(userHandle);
} catch (RemoteException | RuntimeException e) {
- Log.e(LOG_TAG, "Could not retrieve certificates from KeyChain service", e);
+ Slog.e(LOG_TAG, e, "Could not retrieve certificates from KeyChain service for user %d",
+ userId);
return;
}
mService.onInstalledCertificatesChanged(userHandle, installedCerts);
@@ -167,7 +170,7 @@
try {
userContext = mInjector.createContextAsUser(userHandle);
} catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Create context as " + userHandle + " failed", e);
+ Slog.e(LOG_TAG, e, "Create context as %s failed", userHandle);
return null;
}
@@ -183,7 +186,6 @@
smallIconId = R.drawable.stat_sys_certificate_info;
parentUserId = mService.getProfileParentId(userHandle.getIdentifier());
} else if (mService.getDeviceOwnerUserId() == userHandle.getIdentifier()) {
- final String ownerName = mService.getDeviceOwnerName();
contentText = resources.getString(R.string.ssl_ca_cert_noti_managed,
mService.getDeviceOwnerName());
smallIconId = R.drawable.stat_sys_certificate_info;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
index 3067d45..00e0292 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
@@ -46,19 +46,11 @@
final Object mLock = new Object();
final Context mContext;
- private final DevicePolicyManagerService mService;
private final DevicePolicyManagerService.Injector mInjector;
private final DevicePolicyConstants mConstants;
private final Handler mHandler; // needed?
- static void debug(String format, Object... args) {
- if (!DEBUG) {
- return;
- }
- Slog.d(TAG, String.format(format, args));
- }
-
private class DevicePolicyServiceConnection
extends PersistentConnection<IDeviceAdminService> {
public DevicePolicyServiceConnection(int userId, @NonNull ComponentName componentName) {
@@ -88,7 +80,6 @@
public DeviceAdminServiceController(DevicePolicyManagerService service,
DevicePolicyConstants constants) {
- mService = service;
mInjector = service.mInjector;
mContext = mInjector.mContext;
mHandler = new Handler(BackgroundThread.get().getLooper());
@@ -122,8 +113,9 @@
synchronized (mLock) {
final ServiceInfo service = findService(packageName, userId);
if (service == null) {
- debug("Owner package %s on u%d has no service.",
- packageName, userId);
+ if (DEBUG) {
+ Slog.d(TAG, "Owner package %s on u%d has no service.", packageName, userId);
+ }
disconnectServiceOnUserLocked(userId, actionForLog);
return;
}
@@ -134,14 +126,17 @@
// Note even when we're already connected to the same service, the binding
// would have died at this point due to a package update. So we disconnect
// anyway and re-connect.
- debug("Disconnecting from existing service connection.",
- packageName, userId);
+ if (DEBUG) {
+ Slog.d("Disconnecting from existing service connection.", packageName,
+ userId);
+ }
disconnectServiceOnUserLocked(userId, actionForLog);
}
- debug("Owner package %s on u%d has service %s for %s",
- packageName, userId,
+ if (DEBUG) {
+ Slog.d("Owner package %s on u%d has service %s for %s", packageName, userId,
service.getComponentName().flattenToShortString(), actionForLog);
+ }
final DevicePolicyServiceConnection conn =
new DevicePolicyServiceConnection(
@@ -172,8 +167,10 @@
private void disconnectServiceOnUserLocked(int userId, @NonNull String actionForLog) {
final DevicePolicyServiceConnection conn = mConnections.get(userId);
if (conn != null) {
- debug("Stopping service for u%d if already running for %s.",
- userId, actionForLog);
+ if (DEBUG) {
+ Slog.d(TAG, "Stopping service for u%d if already running for %s.", userId,
+ actionForLog);
+ }
conn.unbind();
mConnections.remove(userId);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
index 464d6f5..2825eea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
@@ -48,6 +48,11 @@
private static final String BATTERY_THRESHOLD_CHARGING_KEY =
"battery_threshold_charging";
+ // TODO(b/182994391): Replace with more generic solution to override the supervision
+ // component.
+ private static final String USE_TEST_ADMIN_AS_SUPERVISION_COMPONENT_KEY =
+ "use_test_admin_as_supervision_component";
+
/**
* The back-off before re-connecting, when a service binding died, due to the owner
* crashing repeatedly.
@@ -79,6 +84,12 @@
*/
public final int BATTERY_THRESHOLD_CHARGING;
+ /**
+ * Whether to default to considering the current DO/PO as the supervision component
+ * if they are a testOnly admin.
+ */
+ public final boolean USE_TEST_ADMIN_AS_SUPERVISION_COMPONENT;
+
private DevicePolicyConstants(String settings) {
@@ -88,7 +99,7 @@
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
- Slog.e(TAG, "Bad device policy settings: " + settings);
+ Slog.e(TAG, "Bad device policy settings: %s", settings);
}
long dasDiedServiceReconnectBackoffSec = parser.getLong(
@@ -110,6 +121,9 @@
int batteryThresholdCharging = parser.getInt(
BATTERY_THRESHOLD_CHARGING_KEY, 20);
+ boolean useTestAdminAsSupervisionComponent = parser.getBoolean(
+ USE_TEST_ADMIN_AS_SUPERVISION_COMPONENT_KEY, false);
+
// Set minimum: 5 seconds.
dasDiedServiceReconnectBackoffSec = Math.max(5, dasDiedServiceReconnectBackoffSec);
@@ -128,6 +142,7 @@
dasDiedServiceStableConnectionThresholdSec;
BATTERY_THRESHOLD_NOT_CHARGING = batteryThresholdNotCharging;
BATTERY_THRESHOLD_CHARGING = batteryThresholdCharging;
+ USE_TEST_ADMIN_AS_SUPERVISION_COMPONENT = useTestAdminAsSupervisionComponent;
}
public static DevicePolicyConstants loadFromString(String settings) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index c0b2ed4..52cdce6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -179,11 +179,11 @@
*/
static boolean store(DevicePolicyData policyData, JournaledFile file, boolean isFdeDevice) {
FileOutputStream stream = null;
+ File chooseForWrite = null;
try {
- File chooseForWrite = file.chooseForWrite();
+ chooseForWrite = file.chooseForWrite();
if (VERBOSE_LOG) {
- Slog.v(TAG, "Storing data for user " + policyData.mUserId + " on "
- + chooseForWrite);
+ Slog.v(TAG, "Storing data for user %d on %s ", policyData.mUserId, chooseForWrite);
}
stream = new FileOutputStream(chooseForWrite, false);
TypedXmlSerializer out = Xml.resolveSerializer(stream);
@@ -195,7 +195,7 @@
policyData.mRestrictionsProvider.flattenToString());
}
if (policyData.mUserSetupComplete) {
- if (VERBOSE_LOG) Slog.v(TAG, "setting " + ATTR_SETUP_COMPLETE + " to true");
+ if (VERBOSE_LOG) Slog.v(TAG, "setting %s to true", ATTR_SETUP_COMPLETE);
out.attributeBoolean(null, ATTR_SETUP_COMPLETE, true);
}
if (policyData.mPaired) {
@@ -216,8 +216,8 @@
if (policyData.mFactoryResetFlags != 0) {
if (VERBOSE_LOG) {
- Slog.v(TAG, "Storing factory reset flags for user " + policyData.mUserId + ": "
- + factoryResetFlagsToString(policyData.mFactoryResetFlags));
+ Slog.v(TAG, "Storing factory reset flags for user %d: %s", policyData.mUserId,
+ factoryResetFlagsToString(policyData.mFactoryResetFlags));
}
out.attributeInt(null, ATTR_FACTORY_RESET_FLAGS, policyData.mFactoryResetFlags);
}
@@ -382,7 +382,7 @@
file.commit();
return true;
} catch (XmlPullParserException | IOException e) {
- Slog.w(TAG, "failed writing file", e);
+ Slog.w(TAG, e, "failed writing file %s", chooseForWrite);
try {
if (stream != null) {
stream.close();
@@ -404,10 +404,8 @@
ComponentName ownerComponent) {
FileInputStream stream = null;
File file = journaledFile.chooseForRead();
- if (VERBOSE_LOG) {
- Slog.v(TAG, "Loading data for user " + policy.mUserId + " from " + file);
- }
-
+ if (VERBOSE_LOG) Slog.v(TAG, "Loading data for user %d from %s", policy.mUserId, file);
+ boolean needsRewrite = false;
try {
stream = new FileInputStream(file);
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
@@ -454,8 +452,8 @@
policy.mFactoryResetFlags = parser.getAttributeInt(null, ATTR_FACTORY_RESET_FLAGS, 0);
if (VERBOSE_LOG) {
- Slog.v(TAG, "Restored factory reset flags for user " + policy.mUserId + ": "
- + factoryResetFlagsToString(policy.mFactoryResetFlags));
+ Slog.v(TAG, "Restored factory reset flags for user %d: %s", policy.mUserId,
+ factoryResetFlagsToString(policy.mFactoryResetFlags));
}
policy.mFactoryResetReason = parser.getAttributeValue(null, ATTR_FACTORY_RESET_REASON);
@@ -488,7 +486,7 @@
policy.mAdminMap.put(ap.info.getComponent(), ap);
}
} catch (RuntimeException e) {
- Slog.w(TAG, "Failed loading admin " + name, e);
+ Slog.w(TAG, e, "Failed loading admin %s", name);
}
} else if ("delegation".equals(tag)) {
// Parse delegation info.
@@ -560,7 +558,7 @@
policy.mAppsSuspended =
parser.getAttributeBoolean(null, ATTR_VALUE, false);
} else {
- Slog.w(TAG, "Unknown tag: " + tag);
+ Slog.w(TAG, "Unknown tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
}
}
@@ -568,7 +566,7 @@
// Don't be noisy, this is normal if we haven't defined any policies.
} catch (NullPointerException | NumberFormatException | XmlPullParserException | IOException
| IndexOutOfBoundsException e) {
- Slog.w(TAG, "failed parsing " + file, e);
+ Slog.w(TAG, e, "failed parsing %s", file);
}
try {
if (stream != null) {
@@ -592,8 +590,8 @@
}
}
if (!haveOwner) {
- Slog.w(TAG, "Previous password owner " + mPasswordOwner
- + " no longer active; disabling");
+ Slog.w(TAG, "Previous password owner %s no longer active; disabling",
+ mPasswordOwner);
mPasswordOwner = -1;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2855c70..a8131e4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -101,6 +101,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.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
@@ -1056,7 +1058,7 @@
service.removeCredentialManagementApp();
}
} catch (RemoteException | InterruptedException | IllegalStateException e) {
- Log.e(LOG_TAG, "Unable to remove the credential management app");
+ Slog.e(LOG_TAG, "Unable to remove the credential management app");
}
});
}
@@ -1106,7 +1108,7 @@
* Used by {@code setDevicePolicySafetyChecker()} above and {@link OneTimeSafetyChecker}.
*/
void setDevicePolicySafetyCheckerUnchecked(DevicePolicySafetyChecker safetyChecker) {
- Slog.i(LOG_TAG, String.format("Setting DevicePolicySafetyChecker as %s", safetyChecker));
+ Slog.i(LOG_TAG, "Setting DevicePolicySafetyChecker as %s", safetyChecker);
mSafetyChecker = safetyChecker;
mInjector.setDevicePolicySafetyChecker(safetyChecker);
}
@@ -1149,18 +1151,17 @@
@OperationSafetyReason int reason) {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
- Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)",
+ Slog.i(LOG_TAG, "setNextOperationSafety(%s, %s)",
DevicePolicyManager.operationToString(operation),
- DevicePolicyManager.operationSafetyReasonToString(reason)));
+ DevicePolicyManager.operationSafetyReasonToString(reason));
mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason);
}
@Override
public boolean isSafeOperation(@OperationSafetyReason int reason) {
if (VERBOSE_LOG) {
- Slog.v(LOG_TAG, "checking isSafeOperation("
- + DevicePolicyManager.operationSafetyReasonToString(reason)
- + ") using mSafetyChecker " + mSafetyChecker);
+ Slog.v(LOG_TAG, "checking isSafeOperation(%s) using mSafetyChecker %s",
+ DevicePolicyManager.operationSafetyReasonToString(reason), mSafetyChecker);
}
return mSafetyChecker == null ? true : mSafetyChecker.isSafeOperation(reason);
}
@@ -1893,9 +1894,8 @@
return;
}
- Slog.i(LOG_TAG, String.format(
- "Migrating COMP to PO on a corp owned device; primary user: %d; profile: %d",
- doUserId, poUserId));
+ Slog.i(LOG_TAG, "Migrating COMP to PO on a corp owned device; primary user: %d; "
+ + "profile: %d", doUserId, poUserId);
Slog.i(LOG_TAG, "Giving the PO additional power...");
markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId);
@@ -1940,11 +1940,11 @@
}
}
- private void uninstallOrDisablePackage(String packageName, int userHandle) {
+ private void uninstallOrDisablePackage(String packageName, @UserIdInt int userId) {
final ApplicationInfo appInfo;
try {
appInfo = mIPackageManager.getApplicationInfo(
- packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+ packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
} catch (RemoteException e) {
// Shouldn't happen.
return;
@@ -1954,10 +1954,10 @@
return;
}
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- Slog.i(LOG_TAG, String.format(
- "Package %s is pre-installed, marking disabled until used", packageName));
+ Slog.i(LOG_TAG, "Package %s is pre-installed, marking disabled until used",
+ packageName);
mContext.getPackageManager().setApplicationEnabledSetting(packageName,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0 /* flags */);
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, /* flags= */ 0);
return;
}
@@ -1968,17 +1968,15 @@
final int status = intent.getIntExtra(
PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
- Slog.i(LOG_TAG, String.format(
- "Package %s uninstalled for user %d", packageName, userHandle));
+ Slog.i(LOG_TAG, "Package %s uninstalled for user %d", packageName, userId);
} else {
- Slog.e(LOG_TAG, String.format(
- "Failed to uninstall %s; status: %d", packageName, status));
+ Slog.e(LOG_TAG, "Failed to uninstall %s; status: %d", packageName, status);
}
}
};
- final PackageInstaller pi = mInjector.getPackageManager(userHandle).getPackageInstaller();
- pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender));
+ final PackageInstaller pi = mInjector.getPackageManager(userId).getPackageInstaller();
+ pi.uninstall(packageName, /* flags= */ 0, new IntentSender((IIntentSender) mLocalSender));
}
@GuardedBy("getLockObject()")
@@ -2177,7 +2175,7 @@
!mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
mOwners.writeDeviceOwner();
if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Device owner component filled in");
+ Slog.v(LOG_TAG, "Device owner component filled in");
}
}
}
@@ -2192,7 +2190,7 @@
// except for the "system controlled" ones.
if (mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()) {
if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Migrating DO user restrictions");
+ Slog.v(LOG_TAG, "Migrating DO user restrictions");
}
migrated = true;
@@ -2220,7 +2218,7 @@
final int userId = ui.id;
if (mOwners.getProfileOwnerUserRestrictionsNeedsMigration(userId)) {
if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Migrating PO user restrictions for user " + userId);
+ Slog.v(LOG_TAG, "Migrating PO user restrictions for user %d", userId);
}
migrated = true;
@@ -2243,7 +2241,7 @@
}
}
if (VERBOSE_LOG && migrated) {
- Log.v(LOG_TAG, "User restrictions migrated.");
+ Slog.v(LOG_TAG, "User restrictions migrated.");
}
}
@@ -2271,9 +2269,9 @@
}
if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "origRestrictions=" + origRestrictions);
- Log.v(LOG_TAG, "newBaseRestrictions=" + newBaseRestrictions);
- Log.v(LOG_TAG, "newOwnerRestrictions=" + newOwnerRestrictions);
+ Slog.v(LOG_TAG, "origRestrictions=%s", origRestrictions);
+ Slog.v(LOG_TAG, "newBaseRestrictions=%s", newBaseRestrictions);
+ Slog.v(LOG_TAG, "newOwnerRestrictions=%s", newOwnerRestrictions);
}
mUserManagerInternal.setBaseUserRestrictionsByDpmsForMigration(user.getIdentifier(),
newBaseRestrictions);
@@ -2768,9 +2766,7 @@
private JournaledFile makeJournaledFile(@UserIdInt int userId, String fileName) {
final String base = new File(getPolicyFileDirectory(userId), fileName)
.getAbsolutePath();
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Opening " + base);
- }
+ if (VERBOSE_LOG) Slog.v(LOG_TAG, "Opening %s", base);
return new JournaledFile(new File(base), new File(base + ".tmp"));
}
@@ -3082,6 +3078,13 @@
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
+ boolean enableEnterpriseNetworkSlice = true;
+ synchronized (getLockObject()) {
+ ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
+ enableEnterpriseNetworkSlice = owner != null ? owner.mNetworkSlicingEnabled : true;
+ }
+ updateNetworkPreferenceForUser(userId, enableEnterpriseNetworkSlice);
+
startOwnerService(userId, "start-user");
}
@@ -3097,6 +3100,7 @@
@Override
void handleStopUser(int userId) {
+ updateNetworkPreferenceForUser(userId, false);
stopOwnerService(userId, "stop-user");
}
@@ -4920,8 +4924,8 @@
}
if (!validationErrors.isEmpty()) {
- Log.w(LOG_TAG, "Failed to reset password due to constraint violation: "
- + validationErrors.get(0));
+ Slog.w(LOG_TAG, "Failed to reset password due to constraint violation: %s",
+ validationErrors.get(0));
return false;
}
}
@@ -5349,7 +5353,7 @@
});
if (alias == null) {
- Log.w(LOG_TAG, "Problem installing cert");
+ Slog.w(LOG_TAG, "Problem installing cert");
return false;
}
@@ -5422,12 +5426,12 @@
.write();
return true;
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Installing certificate", e);
+ Slog.e(LOG_TAG, "Installing certificate", e);
} finally {
keyChainConnection.close();
}
} catch (InterruptedException e) {
- Log.w(LOG_TAG, "Interrupted while installing certificate", e);
+ Slog.w(LOG_TAG, "Interrupted while installing certificate", e);
Thread.currentThread().interrupt();
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -5472,12 +5476,12 @@
.write();
return keyChain.removeKeyPair(alias);
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Removing keypair", e);
+ Slog.e(LOG_TAG, "Removing keypair", e);
} finally {
keyChainConnection.close();
}
} catch (InterruptedException e) {
- Log.w(LOG_TAG, "Interrupted while removing keypair", e);
+ Slog.w(LOG_TAG, "Interrupted while removing keypair", e);
Thread.currentThread().interrupt();
} finally {
Binder.restoreCallingIdentity(id);
@@ -5488,16 +5492,17 @@
@Override
public boolean hasKeyPair(String callerPackage, String alias) {
final CallerIdentity caller = getCallerIdentity(callerPackage);
- Preconditions.checkCallAuthorization(canManageCertificates(caller));
+ Preconditions.checkCallAuthorization(canManageCertificates(caller)
+ || isCredentialManagementApp(caller, alias));
return mInjector.binderWithCleanCallingIdentity(() -> {
try (KeyChainConnection keyChainConnection =
KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
return keyChainConnection.getService().containsKeyPair(alias);
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Querying keypair", e);
+ Slog.e(LOG_TAG, "Querying keypair", e);
} catch (InterruptedException e) {
- Log.w(LOG_TAG, "Interrupted while querying keypair", e);
+ Slog.w(LOG_TAG, "Interrupted while querying keypair", e);
Thread.currentThread().interrupt();
}
return false;
@@ -5539,7 +5544,7 @@
}
return false;
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Querying grant to wifi auth. ", e);
+ Slog.e(LOG_TAG, "Querying grant to wifi auth.", e);
return false;
}
});
@@ -5580,11 +5585,11 @@
keyChain.setGrant(granteeUid, alias, hasGrant);
return true;
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Setting grant for package.", e);
+ Slog.e(LOG_TAG, "Setting grant for package.", e);
return false;
}
} catch (InterruptedException e) {
- Log.w(LOG_TAG, "Interrupted while setting key grant", e);
+ Slog.w(LOG_TAG, "Interrupted while setting key grant", e);
Thread.currentThread().interrupt();
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -5621,9 +5626,9 @@
}
return result;
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Querying keypair grants", e);
+ Slog.e(LOG_TAG, "Querying keypair grants", e);
} catch (InterruptedException e) {
- Log.w(LOG_TAG, "Interrupted while querying keypair grants", e);
+ Slog.w(LOG_TAG, "Interrupted while querying keypair grants", e);
Thread.currentThread().interrupt();
}
return Collections.emptyList();
@@ -5748,7 +5753,7 @@
// As the caller will be granted access to the key, ensure no UID was specified, as
// it will not have the desired effect.
if (keySpec.getUid() != KeyStore.UID_SELF) {
- Log.e(LOG_TAG, "Only the caller can be granted access to the generated keypair.");
+ Slog.e(LOG_TAG, "Only the caller can be granted access to the generated keypair.");
logGenerateKeyPairFailure(caller, isCredentialManagementApp);
return false;
}
@@ -5774,8 +5779,8 @@
final int generationResult = keyChain.generateKeyPair(algorithm,
new ParcelableKeyGenParameterSpec(keySpec));
if (generationResult != KeyChain.KEY_GEN_SUCCESS) {
- Log.e(LOG_TAG, String.format(
- "KeyChain failed to generate a keypair, error %d.", generationResult));
+ Slog.e(LOG_TAG, "KeyChain failed to generate a keypair, error %d.",
+ generationResult);
logGenerateKeyPairFailure(caller, isCredentialManagementApp);
switch (generationResult) {
case KeyChain.KEY_GEN_STRONGBOX_UNAVAILABLE:
@@ -5814,7 +5819,7 @@
attestationChain.shallowCopyFrom(new KeymasterCertificateChain(encodedCerts));
} catch (CertificateException e) {
logGenerateKeyPairFailure(caller, isCredentialManagementApp);
- Log.e(LOG_TAG, "While retrieving certificate chain.", e);
+ Slog.e(LOG_TAG, "While retrieving certificate chain.", e);
return false;
}
@@ -5829,9 +5834,9 @@
return true;
}
} catch (RemoteException e) {
- Log.e(LOG_TAG, "KeyChain error while generating a keypair", e);
+ Slog.e(LOG_TAG, "KeyChain error while generating a keypair", e);
} catch (InterruptedException e) {
- Log.w(LOG_TAG, "Interrupted while generating keypair", e);
+ Slog.w(LOG_TAG, "Interrupted while generating keypair", e);
Thread.currentThread().interrupt();
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -5889,10 +5894,10 @@
.write();
return true;
} catch (InterruptedException e) {
- Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
+ Slog.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
Thread.currentThread().interrupt();
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Failed setting keypair certificate", e);
+ Slog.e(LOG_TAG, "Failed setting keypair certificate", e);
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -5966,7 +5971,7 @@
} catch (Exception e) {
// Caller could throw RuntimeException or RemoteException back across processes. Catch
// everything just to be sure.
- Log.e(LOG_TAG, "error while responding to callback", e);
+ Slog.e(LOG_TAG, "error while responding to callback", e);
}
}
@@ -6323,7 +6328,7 @@
boolean isUserSelectable) {
// Should not be user selectable
if (isUserSelectable) {
- Log.e(LOG_TAG, "The credential management app is not allowed to install a "
+ Slog.e(LOG_TAG, "The credential management app is not allowed to install a "
+ "user selectable key pair");
return false;
}
@@ -6523,8 +6528,8 @@
// Persist the request so the device is automatically factory-reset on next start if
// the system crashes or reboots before the {@code DevicePolicySafetyChecker} calls
// its callback.
- Slog.i(LOG_TAG, String.format("Persisting factory reset request as it could be "
- + "delayed by %s", mSafetyChecker));
+ Slog.i(LOG_TAG, "Persisting factory reset request as it could be delayed by %s",
+ mSafetyChecker);
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
policy.setDelayedFactoryReset(reason, wipeExtRequested, wipeEuicc,
@@ -6638,8 +6643,8 @@
int userId = admin != null ? admin.getUserHandle().getIdentifier()
: caller.getUserId();
- Slog.i(LOG_TAG, String.format("wipeDataWithReason(%s): admin=%s, user=%d",
- wipeReasonForUser, admin, userId));
+ Slog.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
+ userId);
if (calledByProfileOwnerOnOrgOwnedDevice) {
// When wipeData is called on the parent instance, it implies wiping the entire device.
if (calledOnParentInstance) {
@@ -7439,7 +7444,7 @@
try {
mInjector.getIWindowManager().refreshScreenCaptureDisabled(userHandle);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
+ Slog.w(LOG_TAG, "Unable to notify WindowManager.", e);
}
});
}
@@ -8805,11 +8810,15 @@
final ComponentName doComponent = mOwners.getDeviceOwnerComponent();
final ComponentName poComponent =
mOwners.getProfileOwnerComponent(userHandle.getIdentifier());
- // Return test only admin by default.
- if (isAdminTestOnlyLocked(doComponent, userHandle.getIdentifier())) {
- return doComponent;
- } else if (isAdminTestOnlyLocked(poComponent, userHandle.getIdentifier())) {
- return poComponent;
+ // Return test only admin if configured to do so.
+ // TODO(b/182994391): Replace with more generic solution to override the supervision
+ // component.
+ if (mConstants.USE_TEST_ADMIN_AS_SUPERVISION_COMPONENT) {
+ if (isAdminTestOnlyLocked(doComponent, userHandle.getIdentifier())) {
+ return doComponent;
+ } else if (isAdminTestOnlyLocked(poComponent, userHandle.getIdentifier())) {
+ return poComponent;
+ }
}
final String supervisor = mContext.getResources().getString(
com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
@@ -8817,6 +8826,9 @@
return null;
}
final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+ if (supervisorComponent == null) {
+ return null;
+ }
if (supervisorComponent.equals(doComponent) || supervisorComponent.equals(
poComponent)) {
return supervisorComponent;
@@ -8903,19 +8915,18 @@
// thrown but null data can be returned; if the appInfo for the specified package cannot
// be found then return false to prevent crashing the app.
if (appInfo == null) {
- Log.w(LOG_TAG,
- String.format("appInfo could not be found for package %s", packageName));
+ Slog.w(LOG_TAG, "appInfo could not be found for package %s", packageName);
return false;
} else if (uid != appInfo.uid) {
String message = String.format("Package %s (uid=%d) does not match provided uid %d",
packageName, appInfo.uid, uid);
- Log.w(LOG_TAG, message);
+ Slog.w(LOG_TAG, message);
throw new SecurityException(message);
}
} catch (RemoteException e) {
// If an exception is caught obtaining the appInfo just return false to prevent crashing
// apps due to an internal error.
- Log.e(LOG_TAG, "Exception caught obtaining appInfo for package " + packageName, e);
+ Slog.e(LOG_TAG, e, "Exception caught obtaining appInfo for package %s", packageName);
return false;
}
return true;
@@ -8931,7 +8942,7 @@
String message = String.format(
"Calling uid %d, pid %d cannot check device identifier access for package %s "
+ "(uid=%d, pid=%d)", callingUid, callingPid, packageName, uid, pid);
- Log.w(LOG_TAG, message);
+ Slog.w(LOG_TAG, message);
throw new SecurityException(message);
}
}
@@ -8939,14 +8950,15 @@
/**
* Canonical name for a given package.
*/
- private String getApplicationLabel(String packageName, int userHandle) {
+ private String getApplicationLabel(String packageName, @UserIdInt int userId) {
return mInjector.binderWithCleanCallingIdentity(() -> {
final Context userContext;
try {
- UserHandle handle = new UserHandle(userHandle);
- userContext = mContext.createPackageContextAsUser(packageName, 0, handle);
+ UserHandle userHandle = UserHandle.of(userId);
+ userContext = mContext.createPackageContextAsUser(packageName, /* flags= */ 0,
+ userHandle);
} catch (PackageManager.NameNotFoundException nnfe) {
- Log.w(LOG_TAG, packageName + " is not installed for user " + userHandle, nnfe);
+ Slog.w(LOG_TAG, nnfe, "%s is not installed for user %d", packageName, userId);
return null;
}
ApplicationInfo appInfo = userContext.getApplicationInfo();
@@ -9565,9 +9577,8 @@
}
result.add(info.options);
} else {
- Log.w(LOG_TAG, "Ignoring admin " + active.info
- + " because it has trust options but doesn't declare "
- + "KEYGUARD_DISABLE_TRUST_AGENTS");
+ Slog.w(LOG_TAG, "Ignoring admin %s because it has trust options but doesn't"
+ + " declare KEYGUARD_DISABLE_TRUST_AGENTS", active.info);
}
} else if (disablesTrust) {
allAdminsHaveOptions = false;
@@ -9705,7 +9716,7 @@
userIdToCheck);
systemService = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
} catch (RemoteException e) {
- Log.i(LOG_TAG, "Can't talk to package managed", e);
+ Slog.i(LOG_TAG, "Can't talk to package managed", e);
}
if (!systemService && !permittedList.contains(enabledPackage)) {
return false;
@@ -10186,7 +10197,7 @@
user = userInfo.getUserHandle();
}
} catch (UserManager.CheckedUserOperationException e) {
- Log.e(LOG_TAG, "Couldn't createUserEvenWhenDisallowed", e);
+ Slog.e(LOG_TAG, "Couldn't createUserEvenWhenDisallowed", e);
}
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -10257,8 +10268,8 @@
}
} catch (RemoteException e) {
// Does not happen, same process
- Slog.wtf(LOG_TAG, String.format("Failed to install admin package %s for user %d",
- adminPkg, userId), e);
+ Slog.wtf(LOG_TAG, e, "Failed to install admin package %s for user %d",
+ adminPkg, userId);
}
// Set admin.
@@ -10307,7 +10318,7 @@
manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
/* managedUser= */ userId, /* adminExtras= */ null, /* showDisclaimer= */ true);
} else {
- Log.i(LOG_TAG, "User " + userId + " added on DO mode; setting ShowNewUserDisclaimer");
+ Slog.i(LOG_TAG, "User %d added on DO mode; setting ShowNewUserDisclaimer", userId);
setShowNewUserDisclaimer(userId, DevicePolicyData.NEW_USER_DISCLAIMER_NEEDED);
}
}
@@ -10363,8 +10374,8 @@
? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
: UserManager.DISALLOW_REMOVE_USER;
if (isAdminAffectedByRestriction(who, restriction, caller.getUserId())) {
- Log.w(LOG_TAG, "The device owner cannot remove a user because "
- + restriction + " is enabled, and was not set by the device owner");
+ Slog.w(LOG_TAG, "The device owner cannot remove a user because %s is enabled, and "
+ + "was not set by the device owner", restriction);
return false;
}
return mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle.getIdentifier());
@@ -10401,7 +10412,7 @@
}
return mInjector.getIActivityManager().switchUser(userId);
} catch (RemoteException e) {
- Log.e(LOG_TAG, "Couldn't switch user", e);
+ Slog.e(LOG_TAG, "Couldn't switch user", e);
return false;
} finally {
mInjector.binderRestoreCallingIdentity(id);
@@ -10419,19 +10430,19 @@
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
- Log.w(LOG_TAG, "Managed profile cannot be started in background");
+ Slog.w(LOG_TAG, "Managed profile cannot be started in background");
return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
final long id = mInjector.binderClearCallingIdentity();
try {
if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) {
- Log.w(LOG_TAG, "Cannot start user " + userId + ", too many users in background");
+ Slog.w(LOG_TAG, "Cannot start user %d, too many users in background", userId);
return UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS;
}
if (mInjector.getIActivityManager().startUserInBackground(userId)) {
- Log.i(LOG_TAG, "Started used " + userId + " in background");
+ Slog.i(LOG_TAG, "Started used %d in background", userId);
return UserManager.USER_OPERATION_SUCCESS;
} else {
return UserManager.USER_OPERATION_ERROR_UNKNOWN;
@@ -10454,7 +10465,7 @@
final int userId = userHandle.getIdentifier();
if (isManagedProfile(userId)) {
- Log.w(LOG_TAG, "Managed profile cannot be stopped");
+ Slog.w(LOG_TAG, "Managed profile cannot be stopped");
return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
@@ -10477,14 +10488,14 @@
}
if (isManagedProfile(callingUserId)) {
- Log.w(LOG_TAG, "Managed profile cannot be logout");
+ Slog.w(LOG_TAG, "Managed profile cannot be logout");
return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
final long id = mInjector.binderClearCallingIdentity();
try {
if (!mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM)) {
- Log.w(LOG_TAG, "Failed to switch to primary user");
+ Slog.w(LOG_TAG, "Failed to switch to primary user");
// This should never happen as target user is UserHandle.USER_SYSTEM
return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
@@ -11261,8 +11272,8 @@
}
if (isCrossProfileQuickContactDisabled(managedUserId)) {
if (VERBOSE_LOG) {
- Log.v(LOG_TAG,
- "Cross-profile contacts access disabled for user " + managedUserId);
+ Slog.v(LOG_TAG, "Cross-profile contacts access disabled for user %d",
+ managedUserId);
}
return;
}
@@ -11275,7 +11286,7 @@
/**
* @return true if cross-profile QuickContact is disabled
*/
- private boolean isCrossProfileQuickContactDisabled(int userId) {
+ private boolean isCrossProfileQuickContactDisabled(@UserIdInt int userId) {
return getCrossProfileCallerIdDisabledForUser(userId)
&& getCrossProfileContactsSearchDisabledForUser(userId);
}
@@ -11284,23 +11295,17 @@
* @return the user ID of the managed user that is linked to the current user, if any.
* Otherwise -1.
*/
- public int getManagedUserId(int callingUserId) {
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "getManagedUserId: callingUserId=" + callingUserId);
- }
+ public int getManagedUserId(@UserIdInt int callingUserId) {
+ if (VERBOSE_LOG) Slog.v(LOG_TAG, "getManagedUserId: callingUserId=%d", callingUserId);
for (UserInfo ui : mUserManager.getProfiles(callingUserId)) {
if (ui.id == callingUserId || !ui.isManagedProfile()) {
continue; // Caller user self, or not a managed profile. Skip.
}
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Managed user=" + ui.id);
- }
+ if (VERBOSE_LOG) Slog.v(LOG_TAG, "Managed user=%d", ui.id);
return ui.id;
}
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Managed user not found.");
- }
+ if (VERBOSE_LOG) Slog.v(LOG_TAG, "Managed user not found.");
return -1;
}
@@ -11399,21 +11404,22 @@
if (!mHasFeature) {
return;
}
-
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(isProfileOwner(caller),
"Caller is not profile owner; only profile owner may control the network slicing");
-
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
caller.getUserId());
if (requiredAdmin != null && requiredAdmin.mNetworkSlicingEnabled != enabled) {
requiredAdmin.mNetworkSlicingEnabled = enabled;
saveSettingsLocked(caller.getUserId());
- // TODO(b/178655595) notify CS the change.
- // TODO(b/178655595) DevicePolicyEventLogger metrics
}
}
+ updateNetworkPreferenceForUser(caller.getUserId(), enabled);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_NETWORK_SLICING_ENABLED)
+ .setBoolean(enabled)
+ .write();
}
@Override
@@ -11423,11 +11429,8 @@
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
- Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
- permission.READ_NETWORK_DEVICE_CONFIG) || isProfileOwner(caller),
- "Caller is not profile owner and not granted"
- + " READ_NETWORK_DEVICE_CONFIG permission");
+ Preconditions.checkCallAuthorization(isProfileOwner(caller),
+ "Caller is not profile owner");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(userHandle);
if (requiredAdmin != null) {
@@ -11599,7 +11602,7 @@
// Some settings are no supported any more. However we do not want to throw a
// SecurityException to avoid breaking apps.
if (GLOBAL_SETTINGS_DEPRECATED.contains(setting)) {
- Log.i(LOG_TAG, "Global setting no longer supported: " + setting);
+ Slog.i(LOG_TAG, "Global setting no longer supported: %s", setting);
return;
}
@@ -12133,6 +12136,16 @@
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
mConstants = loadConstants();
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ final Intent intent = new Intent(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_CONSTANTS_CHANGED);
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final List<UserInfo> users = mUserManager.getAliveUsers();
+ for (int i = 0; i < users.size(); i++) {
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(users.get(i).id));
+ }
+ });
}
}
@@ -12335,7 +12348,7 @@
synchronized (getLockObject()) {
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING,
UserHandle.of(userId))) {
- Log.e(LOG_TAG, "printing is enabled");
+ Slog.e(LOG_TAG, "printing is enabled for user %d", userId);
return null;
}
String ownerPackage = mOwners.getProfileOwnerPackage(userId);
@@ -12348,22 +12361,22 @@
try {
return pm.getPackageInfo(packageName, 0);
} catch (NameNotFoundException e) {
- Log.e(LOG_TAG, "getPackageInfo error", e);
+ Slog.e(LOG_TAG, "getPackageInfo error", e);
return null;
}
});
if (packageInfo == null) {
- Log.e(LOG_TAG, "packageInfo is inexplicably null");
+ Slog.e(LOG_TAG, "packageInfo is inexplicably null");
return null;
}
ApplicationInfo appInfo = packageInfo.applicationInfo;
if (appInfo == null) {
- Log.e(LOG_TAG, "appInfo is inexplicably null");
+ Slog.e(LOG_TAG, "appInfo is inexplicably null");
return null;
}
CharSequence appLabel = pm.getApplicationLabel(appInfo);
if (appLabel == null) {
- Log.e(LOG_TAG, "appLabel is inexplicably null");
+ Slog.e(LOG_TAG, "appLabel is inexplicably null");
return null;
}
return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
@@ -12417,9 +12430,7 @@
Objects.requireNonNull(intent);
Objects.requireNonNull(parentHandle);
final int userId = parentHandle.getIdentifier();
- Slog.i(LOG_TAG,
- String.format("Sending %s broadcast to manifest receivers.",
- intent.getAction()));
+ Slog.i(LOG_TAG, "Sending %s broadcast to manifest receivers.", intent.getAction());
try {
final List<ResolveInfo> receivers = mIPackageManager.queryIntentReceivers(
intent, /* resolvedType= */ null,
@@ -12429,9 +12440,8 @@
if (checkCrossProfilePackagePermissions(packageName, userId,
requiresPermission)
|| checkModifyQuietModePermission(packageName, userId)) {
- Slog.i(LOG_TAG,
- String.format("Sending %s broadcast to %s.", intent.getAction(),
- packageName));
+ Slog.i(LOG_TAG, "Sending %s broadcast to %s.", intent.getAction(),
+ packageName);
final Intent packageIntent = new Intent(intent)
.setComponent(receiver.getComponentInfo().getComponentName())
.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
@@ -12439,9 +12449,8 @@
}
}
} catch (RemoteException ex) {
- Slog.w(LOG_TAG,
- String.format("Cannot get list of broadcast receivers for %s because: %s.",
- intent.getAction(), ex));
+ Slog.w(LOG_TAG, "Cannot get list of broadcast receivers for %s because: %s.",
+ intent.getAction(), ex);
}
}
@@ -12459,9 +12468,8 @@
android.Manifest.permission.MODIFY_QUIET_MODE, uid, /* owningUid= */
-1, /* exported= */ true);
} catch (NameNotFoundException ex) {
- Slog.w(LOG_TAG,
- String.format("Cannot find the package %s to check for permissions.",
- packageName));
+ Slog.w(LOG_TAG, "Cannot find the package %s to check for permissions.",
+ packageName);
return false;
}
}
@@ -12490,9 +12498,8 @@
return crossProfileAppsService.verifyPackageHasInteractAcrossProfilePermission(
packageName, userId);
} catch (NameNotFoundException ex) {
- Slog.w(LOG_TAG,
- String.format("Cannot find the package %s to check for permissions.",
- packageName));
+ Slog.w(LOG_TAG, "Cannot find the package %s to check for permissions.",
+ packageName);
return false;
}
}
@@ -12566,8 +12573,8 @@
// TODO(b/178494483): use EventLog instead
// TODO(b/178494483): log metrics?
if (VERBOSE_LOG) {
- Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b",
- DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+ Slog.v(LOG_TAG, "notifyUnsafeOperationStateChanged(): %s=%b",
+ DevicePolicyManager.operationSafetyReasonToString(reason), isSafe);
}
Preconditions.checkArgument(mSafetyChecker == checker,
"invalid checker: should be %s, was %s", mSafetyChecker, checker);
@@ -12849,7 +12856,7 @@
runningUserIds = mInjector.getIActivityManager().getRunningUserIds();
} catch (RemoteException e) {
// Shouldn't happen.
- Log.e(LOG_TAG, "Could not retrieve the list of running users", e);
+ Slog.e(LOG_TAG, "Could not retrieve the list of running users", e);
return;
}
// Send broadcasts to corresponding profile owners if any.
@@ -13210,9 +13217,8 @@
final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
? UserHandle.USER_SYSTEM
: callingUserId;
- Slog.i(LOG_TAG,
- String.format("Calling user %d, device owner will be set on user %d",
- callingUserId, deviceOwnerUserId));
+ Slog.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
+ callingUserId, deviceOwnerUserId);
// hasIncompatibleAccountsOrNonAdb doesn't matter since the caller is not adb.
return checkDeviceOwnerProvisioningPreConditionLocked(/* owner unknown */ null,
deviceOwnerUserId, callingUserId, /* isAdb= */ false,
@@ -13242,9 +13248,7 @@
UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle);
if (mUserManager.getUserInfo(callingUserId).isProfile()) {
- Slog.i(LOG_TAG,
- String.format("Calling user %d is a profile, cannot add another.",
- callingUserId));
+ Slog.i(LOG_TAG, "Calling user %d is a profile, cannot add another.", callingUserId);
// The check is called from inside a managed profile. A managed profile cannot
// be provisioned from within another managed profile.
return CODE_CANNOT_ADD_MANAGED_PROFILE;
@@ -13257,16 +13261,15 @@
// Do not allow adding a managed profile if there's a restriction.
if (addingProfileRestricted) {
- Slog.i(LOG_TAG, String.format(
- "Adding a profile is restricted: User %s Has device owner? %b",
- callingUserHandle, hasDeviceOwner));
+ Slog.i(LOG_TAG, "Adding a profile is restricted: User %s Has device owner? %b",
+ callingUserHandle, hasDeviceOwner);
return CODE_CANNOT_ADD_MANAGED_PROFILE;
}
// Bail out if we are trying to provision a work profile but one already exists.
if (!mUserManager.canAddMoreManagedProfiles(
callingUserId, /* allowedToRemoveOne= */ false)) {
- Slog.i(LOG_TAG, String.format("A work profile already exists."));
+ Slog.i(LOG_TAG, "A work profile already exists.");
return CODE_CANNOT_ADD_MANAGED_PROFILE;
}
} finally {
@@ -13754,9 +13757,8 @@
who.flattenToString(), userId));
}
- Slog.i(LOG_TAG, String.format(
- "Marking %s as profile owner on organization-owned device for user %d",
- who.flattenToString(), userId));
+ Slog.i(LOG_TAG, "Marking %s as profile owner on organization-owned device for user %d",
+ who.flattenToString(), userId);
// First, set restriction on removing the profile.
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -14169,7 +14171,7 @@
try { // force stop the package before uninstalling
mInjector.getIActivityManager().forceStopPackage(packageName, userId);
} catch (RemoteException re) {
- Log.e(LOG_TAG, "Failure talking to ActivityManager while force stopping package");
+ Slog.e(LOG_TAG, "Failure talking to ActivityManager while force stopping package");
}
final Uri packageURI = Uri.parse("package:" + packageName);
final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
@@ -14425,7 +14427,7 @@
}
synchronized (getLockObject()) {
if (owner == null || !isAdminTestOnlyLocked(owner, userId)) {
- Log.w(LOG_TAG,
+ Slog.w(LOG_TAG,
"Non test-only owner can't be installed with existing accounts.");
return true;
}
@@ -14439,20 +14441,20 @@
boolean compatible = true;
for (Account account : accounts) {
if (hasAccountFeatures(am, account, feature_disallow)) {
- Log.e(LOG_TAG, account + " has " + feature_disallow[0]);
+ Slog.e(LOG_TAG, "%s has %s", account, feature_disallow[0]);
compatible = false;
break;
}
if (!hasAccountFeatures(am, account, feature_allow)) {
- Log.e(LOG_TAG, account + " doesn't have " + feature_allow[0]);
+ Slog.e(LOG_TAG, "%s doesn't have %s", account, feature_allow[0]);
compatible = false;
break;
}
}
if (compatible) {
- Log.w(LOG_TAG, "All accounts are compatible");
+ Slog.w(LOG_TAG, "All accounts are compatible");
} else {
- Log.e(LOG_TAG, "Found incompatible accounts");
+ Slog.e(LOG_TAG, "Found incompatible accounts");
}
return !compatible;
});
@@ -14462,7 +14464,7 @@
try {
return am.hasFeatures(account, features, null, null).getResult();
} catch (Exception e) {
- Log.w(LOG_TAG, "Failed to get account feature", e);
+ Slog.w(LOG_TAG, "Failed to get account feature", e);
return false;
}
}
@@ -14696,24 +14698,25 @@
private void sendNetworkLoggingNotificationLocked() {
ensureLocked();
- final ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
- if (activeAdmin == null || !activeAdmin.isNetworkLoggingEnabled) {
+ // Send a network logging notification if the admin is a device owner, not profile owner.
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) {
return;
}
- if (activeAdmin.numNetworkLoggingNotifications
+ if (deviceOwner.numNetworkLoggingNotifications
>= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
return;
}
final long now = System.currentTimeMillis();
- if (now - activeAdmin.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
+ if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
return;
}
- activeAdmin.numNetworkLoggingNotifications++;
- if (activeAdmin.numNetworkLoggingNotifications
+ deviceOwner.numNetworkLoggingNotifications++;
+ if (deviceOwner.numNetworkLoggingNotifications
>= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
- activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
+ deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
} else {
- activeAdmin.lastNetworkLoggingNotificationTimeMs = now;
+ deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
}
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
@@ -14733,7 +14736,7 @@
.bigText(mContext.getString(R.string.network_logging_notification_text)))
.build();
mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification);
- saveSettingsLocked(activeAdmin.getUserHandle().getIdentifier());
+ saveSettingsLocked(deviceOwner.getUserHandle().getIdentifier());
}
/**
@@ -14761,8 +14764,8 @@
0, // flags
targetUserId);
if (info == null || info.serviceInfo == null) {
- Log.e(LOG_TAG, "Fail to look up the service: " + rawIntent
- + " or user " + targetUserId + " is not running");
+ Slog.e(LOG_TAG, "Fail to look up the service: %s or user %d is not running", rawIntent,
+ targetUserId);
return null;
}
if (!expectedPackageName.equals(info.serviceInfo.packageName)) {
@@ -15256,7 +15259,7 @@
return mInjector.binderWithCleanCallingIdentity(
() -> tm.addDevicePolicyOverrideApn(mContext, apnSetting));
} else {
- Log.w(LOG_TAG, "TelephonyManager is null when trying to add override apn");
+ Slog.w(LOG_TAG, "TelephonyManager is null when trying to add override apn");
return Telephony.Carriers.INVALID_APN_ID;
}
}
@@ -15280,7 +15283,7 @@
return mInjector.binderWithCleanCallingIdentity(
() -> tm.modifyDevicePolicyOverrideApn(mContext, apnId, apnSetting));
} else {
- Log.w(LOG_TAG, "TelephonyManager is null when trying to modify override apn");
+ Slog.w(LOG_TAG, "TelephonyManager is null when trying to modify override apn");
return false;
}
}
@@ -15323,7 +15326,7 @@
return mInjector.binderWithCleanCallingIdentity(
() -> tm.getDevicePolicyOverrideApns(mContext));
}
- Log.w(LOG_TAG, "TelephonyManager is null when trying to get override apns");
+ Slog.w(LOG_TAG, "TelephonyManager is null when trying to get override apns");
return Collections.emptyList();
}
@@ -15476,10 +15479,7 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
- String currentMode = mInjector.settingsGlobalGetString(PRIVATE_DNS_MODE);
- if (currentMode == null) {
- currentMode = ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
- }
+ final String currentMode = ConnectivityManager.getPrivateDnsMode(mContext);
switch (currentMode) {
case ConnectivityManager.PRIVATE_DNS_MODE_OFF:
return PRIVATE_DNS_MODE_OFF;
@@ -15826,8 +15826,8 @@
return false;
}
if (!isPackageAllowedToAccessCalendarForUser(packageName, workProfileUserId)) {
- Log.d(LOG_TAG, String.format("Package %s is not allowed to access cross-profile"
- + "calendar APIs", packageName));
+ Slog.d(LOG_TAG, "Package %s is not allowed to access cross-profile calendar APIs",
+ packageName);
return false;
}
final Intent intent = new Intent(
@@ -15841,7 +15841,7 @@
try {
mContext.startActivityAsUser(intent, UserHandle.of(workProfileUserId));
} catch (ActivityNotFoundException e) {
- Log.e(LOG_TAG, "View event activity not found", e);
+ Slog.e(LOG_TAG, "View event activity not found", e);
return false;
}
return true;
@@ -15855,7 +15855,7 @@
packageName, UserHandle.getUserId(callingUid));
return packageUid == callingUid;
} catch (NameNotFoundException e) {
- Log.d(LOG_TAG, "Calling package not found", e);
+ Slog.d(LOG_TAG, "Calling package not found", e);
return false;
}
});
@@ -15965,8 +15965,8 @@
final long deadline = admin.mProfileOffDeadline;
final int result = makeSuspensionReasons(admin.mSuspendPersonalApps,
deadline != 0 && mInjector.systemCurrentTimeMillis() > deadline);
- Slog.d(LOG_TAG, String.format("getPersonalAppsSuspendedReasons user: %d; result: %d",
- mInjector.userHandleGetCallingUserId(), result));
+ Slog.d(LOG_TAG, "getPersonalAppsSuspendedReasons user: %d; result: %d",
+ mInjector.userHandleGetCallingUserId(), result);
return result;
}
}
@@ -16050,9 +16050,8 @@
updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
suspendedExplicitly = profileOwner.mSuspendPersonalApps;
suspendedByTimeout = deadlineState == PROFILE_OFF_DEADLINE_REACHED;
- Slog.d(LOG_TAG, String.format(
- "Personal apps suspended explicitly: %b, deadline state: %d",
- suspendedExplicitly, deadlineState));
+ Slog.d(LOG_TAG, "Personal apps suspended explicitly: %b, deadline state: %d",
+ suspendedExplicitly, deadlineState);
final int notificationState =
unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState;
updateProfileOffDeadlineNotificationLocked(
@@ -16153,8 +16152,8 @@
if (getUserData(userId).mAppsSuspended == suspended) {
return;
}
- Slog.i(LOG_TAG, String.format("%s personal apps for user %d",
- suspended ? "Suspending" : "Unsuspending", userId));
+ Slog.i(LOG_TAG, "%s personal apps for user %d", suspended ? "Suspending" : "Unsuspending",
+ userId);
if (suspended) {
suspendPersonalAppsInPackageManager(userId);
@@ -16374,8 +16373,7 @@
Preconditions.checkArgument(!TextUtils.isEmpty(organizationId),
"Enterprise ID may not be empty.");
- Log.i(LOG_TAG,
- String.format("Setting Enterprise ID to %s for user %d", organizationId, userId));
+ Slog.i(LOG_TAG, "Setting Enterprise ID to %s for user %d", organizationId, userId);
final String ownerPackage;
synchronized (getLockObject()) {
@@ -16956,6 +16954,20 @@
}
}
+ private void updateNetworkPreferenceForUser(int userId, boolean enableEnterprise) {
+ 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 */));
+ }
+
@Override
public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
if (!mHasFeature) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
index 457255b..28a6987 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
@@ -68,17 +68,16 @@
IResultReceiver receiver = new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
- Slog.i(TAG, String.format("Factory reset confirmed by %s, proceeding",
- mSafetyChecker));
+ Slog.i(TAG, "Factory reset confirmed by %s, proceeding", mSafetyChecker);
try {
factoryResetInternalUnchecked();
} catch (IOException e) {
// Shouldn't happen
- Slog.wtf(TAG, "IOException calling underlying systems", e);
+ Slog.wtf(TAG, e, "IOException calling underlying systems");
}
}
};
- Slog.i(TAG, String.format("Delaying factory reset until %s confirms", mSafetyChecker));
+ Slog.i(TAG, "Delaying factory reset until %s confirms", mSafetyChecker);
mSafetyChecker.onFactoryReset(receiver);
return false;
}
@@ -113,9 +112,9 @@
}
private void factoryResetInternalUnchecked() throws IOException {
- Slog.i(TAG, String.format("factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b, "
+ Slog.i(TAG, "factoryReset(): reason=%s, shutdown=%b, force=%b, wipeEuicc=%b, "
+ "wipeAdoptableStorage=%b, wipeFRP=%b", mReason, mShutdown, mForce, mWipeEuicc,
- mWipeAdoptableStorage, mWipeFactoryResetProtection));
+ mWipeAdoptableStorage, mWipeFactoryResetProtection);
UserManager um = mContext.getSystemService(UserManager.class);
if (!mForce && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 37dbfc1..0b9ece4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -18,6 +18,8 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
+import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -36,6 +38,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -105,7 +108,9 @@
result.remove(pkg);
}
- Slog.i(LOG_TAG, "Packages subject to suspension: " + String.join(",", result));
+ if (Log.isLoggable(LOG_TAG, Log.INFO)) {
+ Slog.i(LOG_TAG, "Packages subject to suspension: %s", String.join(",", result));
+ }
return result.toArray(new String[0]);
}
@@ -118,7 +123,7 @@
for (final ResolveInfo resolveInfo : matchingActivities) {
if (resolveInfo.activityInfo == null
|| TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) {
- Slog.wtf(LOG_TAG, "Could not find package name for launcher app" + resolveInfo);
+ Slog.wtf(LOG_TAG, "Could not find package name for launcher app %s", resolveInfo);
continue;
}
final String packageName = resolveInfo.activityInfo.packageName;
@@ -129,7 +134,8 @@
result.add(packageName);
}
} catch (PackageManager.NameNotFoundException e) {
- Slog.e(LOG_TAG, "Could not find application info for launcher app: " + packageName);
+ Slog.e(LOG_TAG, "Could not find application info for launcher app: %s",
+ packageName);
}
}
return result;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
index 543f381..fa6ef00 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportManager.java
@@ -25,6 +25,8 @@
import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED;
import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED;
+import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
+
import android.annotation.IntDef;
import android.app.Notification;
import android.app.PendingIntent;
@@ -58,7 +60,6 @@
* Class managing bugreport collection upon device owner's request.
*/
public class RemoteBugreportManager {
- private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
@@ -220,7 +221,7 @@
mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished);
} catch (IntentFilter.MalformedMimeTypeException e) {
// should never happen, as setting a constant
- Slog.w(LOG_TAG, "Failed to set type " + BUGREPORT_MIMETYPE, e);
+ Slog.w(LOG_TAG, e, "Failed to set type %s", BUGREPORT_MIMETYPE);
}
final IntentFilter filterConsent = new IntentFilter();
filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 8f12b2e..9869b07 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -255,13 +255,12 @@
binder::Status BinderIncrementalService::isFileFullyLoaded(int32_t storageId,
const std::string& path,
int32_t* _aidl_return) {
- *_aidl_return = mImpl.isFileFullyLoaded(storageId, path);
+ *_aidl_return = (int)mImpl.isFileFullyLoaded(storageId, path);
return ok();
}
binder::Status BinderIncrementalService::isFullyLoaded(int32_t storageId, int32_t* _aidl_return) {
- *_aidl_return = mImpl.getLoadingProgress(storageId, /*stopOnFirstIncomplete=*/true)
- .blocksRemainingOrError();
+ *_aidl_return = (int)mImpl.isMountFullyLoaded(storageId);
return ok();
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 1fcc284..34253f9 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -74,11 +74,21 @@
// If DL was up and not crashing for 10mins, we consider it healthy and reset all delays.
static constexpr auto healthyDataLoaderUptime = 10min;
+
+ // For healthy DLs, we'll retry every ~5secs for ~10min
+ static constexpr auto bindRetryInterval = 5s;
+ static constexpr auto bindGracePeriod = 10min;
+
+ static constexpr auto bindingTimeout = 1min;
+
// 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
static constexpr auto minBindDelay = 10s;
static constexpr auto maxBindDelay = 10000s;
static constexpr auto bindDelayMultiplier = 10;
static constexpr auto bindDelayJitterDivider = 10;
+
+ // Max interval after system invoked the DL when readlog collection can be enabled.
+ static constexpr auto readLogsMaxInterval = 2h;
};
static const Constants& constants() {
@@ -86,6 +96,10 @@
return c;
}
+static bool isPageAligned(IncFsSize s) {
+ return (s & (Constants::blockSize - 1)) == 0;
+}
+
template <base::LogSeverity level = base::ERROR>
bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) {
auto cstr = path::c_str(name);
@@ -283,6 +297,14 @@
::rmdir(path::c_str(root));
}
+void IncrementalService::IncFsMount::setReadLogsEnabled(bool value) {
+ if (value) {
+ flags |= StorageFlags::ReadLogsEnabled;
+ } else {
+ flags &= ~StorageFlags::ReadLogsEnabled;
+ }
+}
+
IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir)
: mVold(sm.getVoldService()),
mDataLoaderManager(sm.getDataLoaderManager()),
@@ -293,6 +315,7 @@
mTimedQueue(sm.getTimedQueue()),
mProgressUpdateJobQueue(sm.getProgressUpdateJobQueue()),
mFs(sm.getFs()),
+ mClock(sm.getClock()),
mIncrementalDir(rootDir) {
CHECK(mVold) << "Vold service is unavailable";
CHECK(mDataLoaderManager) << "DataLoaderManagerService is unavailable";
@@ -302,6 +325,7 @@
CHECK(mTimedQueue) << "TimedQueue is unavailable";
CHECK(mProgressUpdateJobQueue) << "mProgressUpdateJobQueue is unavailable";
CHECK(mFs) << "Fs is unavailable";
+ CHECK(mClock) << "Clock is unavailable";
mJobQueue.reserve(16);
mJobProcessor = std::thread([this]() {
@@ -397,7 +421,7 @@
}
bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
- if (ifs.dataLoaderStub->params().packageName == Constants::systemPackage) {
+ if (ifs.dataLoaderStub->isSystemDataLoader()) {
return true;
}
@@ -649,7 +673,7 @@
return storageId;
}
-bool IncrementalService::startLoading(StorageId storage,
+bool IncrementalService::startLoading(StorageId storageId,
content::pm::DataLoaderParamsParcel&& dataLoaderParams,
const DataLoaderStatusListener& statusListener,
StorageHealthCheckParams&& healthCheckParams,
@@ -657,12 +681,12 @@
const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
// Per Uid timeouts.
if (!perUidReadTimeouts.empty()) {
- setUidReadTimeouts(storage, perUidReadTimeouts);
+ setUidReadTimeouts(storageId, perUidReadTimeouts);
}
// Re-initialize DataLoader.
std::unique_lock l(mLock);
- const auto ifs = getIfsLocked(storage);
+ const auto ifs = getIfsLocked(storageId);
if (!ifs) {
return false;
}
@@ -677,6 +701,32 @@
std::move(healthCheckParams), &healthListener);
CHECK(dataLoaderStub);
+ if (dataLoaderStub->isSystemDataLoader()) {
+ // Readlogs from system dataloader (adb) can always be collected.
+ ifs->startLoadingTs = TimePoint::max();
+ } else {
+ // Assign time when installation wants the DL to start streaming.
+ const auto startLoadingTs = mClock->now();
+ ifs->startLoadingTs = startLoadingTs;
+ // Setup a callback to disable the readlogs after max interval.
+ addTimedJob(*mTimedQueue, storageId, Constants::readLogsMaxInterval,
+ [this, storageId, startLoadingTs]() {
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ LOG(WARNING) << "Can't disable the readlogs, invalid storageId: "
+ << storageId;
+ return;
+ }
+ if (ifs->startLoadingTs != startLoadingTs) {
+ LOG(INFO) << "Can't disable the readlogs, timestamp mismatch (new "
+ "installation?): "
+ << storageId;
+ return;
+ }
+ setStorageParams(*ifs, storageId, /*enableReadLogs=*/false);
+ });
+ }
+
return dataLoaderStub->requestStart();
}
@@ -726,11 +776,16 @@
LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
return -EINVAL;
}
+ return setStorageParams(*ifs, storageId, enableReadLogs);
+}
- const auto& params = ifs->dataLoaderStub->params();
+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 disabled for storageId: " << storageId;
+ if (!ifs.readLogsAllowed()) {
+ LOG(ERROR) << "setStorageParams failed, readlogs disallowed for storageId: "
+ << storageId;
return -EPERM;
}
@@ -751,9 +806,19 @@
<< " check failed: " << status.toString8();
return fromBinderStatus(status);
}
+
+ // 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;
+ return -EPERM;
+ }
}
- if (auto status = applyStorageParams(*ifs, enableReadLogs); !status.isOk()) {
+ if (auto status = applyStorageParams(ifs, enableReadLogs); !status.isOk()) {
LOG(ERROR) << "applyStorageParams failed: " << status.toString8();
return fromBinderStatus(status);
}
@@ -940,25 +1005,53 @@
int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id,
incfs::NewFileParams params, std::span<const uint8_t> data) {
- if (auto ifs = getIfs(storage)) {
- std::string normPath = normalizePathToStorage(*ifs, storage, path);
- if (normPath.empty()) {
- LOG(ERROR) << "Internal error: storageId " << storage
- << " failed to normalize: " << path;
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return -EINVAL;
+ }
+ if (data.size() > params.size) {
+ LOG(ERROR) << "Bad data size - bigger than file size";
+ return -EINVAL;
+ }
+ if (!data.empty() && data.size() != params.size) {
+ // Writing a page is an irreversible operation, and it can't be updated with additional
+ // data later. Check that the last written page is complete, or we may break the file.
+ if (!isPageAligned(data.size())) {
+ LOG(ERROR) << "Bad data size - tried to write half a page?";
return -EINVAL;
}
- if (auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); err) {
- LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err;
- return err;
+ }
+ const std::string normPath = normalizePathToStorage(*ifs, storage, path);
+ if (normPath.empty()) {
+ LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path;
+ return -EINVAL;
+ }
+ if (auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); err) {
+ LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err;
+ 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 (!data.empty()) {
if (auto err = setFileContent(ifs, id, path, data); err) {
+ (void)mIncFs->unlink(ifs->control, normPath);
return err;
}
}
- return 0;
}
- return -EINVAL;
+ return 0;
}
int IncrementalService::makeDir(StorageId storageId, std::string_view path, int mode) {
@@ -1647,7 +1740,7 @@
}
const auto entryUncompressed = entry.method == kCompressStored;
- const auto entryPageAligned = (entry.offset & (constants().blockSize - 1)) == 0;
+ const auto entryPageAligned = isPageAligned(entry.offset);
if (!extractNativeLibs) {
// ensure the file is properly aligned and unpacked
@@ -1887,35 +1980,30 @@
return 0;
}
-int IncrementalService::isFileFullyLoaded(StorageId storage, std::string_view filePath) const {
+incfs::LoadingState IncrementalService::isFileFullyLoaded(StorageId storage,
+ std::string_view filePath) const {
std::unique_lock l(mLock);
const auto ifs = getIfsLocked(storage);
if (!ifs) {
LOG(ERROR) << "isFileFullyLoaded failed, invalid storageId: " << storage;
- return -EINVAL;
+ return incfs::LoadingState(-EINVAL);
}
const auto storageInfo = ifs->storages.find(storage);
if (storageInfo == ifs->storages.end()) {
LOG(ERROR) << "isFileFullyLoaded failed, no storage: " << storage;
- return -EINVAL;
+ return incfs::LoadingState(-EINVAL);
}
l.unlock();
- return isFileFullyLoadedFromPath(*ifs, filePath);
+ return mIncFs->isFileFullyLoaded(ifs->control, filePath);
}
-int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs,
- std::string_view filePath) const {
- const auto [filledBlocks, totalBlocks] = mIncFs->countFilledBlocks(ifs.control, filePath);
- if (filledBlocks < 0) {
- LOG(ERROR) << "isFileFullyLoadedFromPath failed to get filled blocks count for: "
- << filePath << " errno: " << filledBlocks;
- return filledBlocks;
+incfs::LoadingState IncrementalService::isMountFullyLoaded(StorageId storage) const {
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ LOG(ERROR) << "isMountFullyLoaded failed, invalid storageId: " << storage;
+ return incfs::LoadingState(-EINVAL);
}
- if (totalBlocks < filledBlocks) {
- LOG(ERROR) << "isFileFullyLoadedFromPath failed to get total num of blocks";
- return -EINVAL;
- }
- return totalBlocks - filledBlocks;
+ return mIncFs->isEverythingFullyLoaded(ifs->control);
}
IncrementalService::LoadingProgress IncrementalService::getLoadingProgress(
@@ -2213,6 +2301,10 @@
return dataloader;
}
+bool IncrementalService::DataLoaderStub::isSystemDataLoader() const {
+ return (params().packageName == Constants::systemPackage);
+}
+
bool IncrementalService::DataLoaderStub::requestCreate() {
return setTargetStatus(IDataLoaderStatusListener::DATA_LOADER_CREATED);
}
@@ -2241,17 +2333,44 @@
<< status << " (current " << mCurrentStatus << ")";
}
-Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() {
+std::optional<Milliseconds> IncrementalService::DataLoaderStub::needToBind() {
std::unique_lock lock(mMutex);
+
+ const auto now = mService.mClock->now();
+ const bool healthy = (mPreviousBindDelay == 0ms);
+
+ if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_BINDING &&
+ now - mCurrentStatusTs <= Constants::bindingTimeout) {
+ LOG(INFO) << "Binding still in progress. "
+ << (healthy ? "The DL is healthy/freshly bound, ok to retry for a few times."
+ : "Already unhealthy, don't do anything.");
+ // Binding still in progress.
+ if (!healthy) {
+ // Already unhealthy, don't do anything.
+ return {};
+ }
+ // The DL is healthy/freshly bound, ok to retry for a few times.
+ if (now - mPreviousBindTs <= Constants::bindGracePeriod) {
+ // Still within grace period.
+ if (now - mCurrentStatusTs >= Constants::bindRetryInterval) {
+ // Retry interval passed, retrying.
+ mCurrentStatusTs = now;
+ mPreviousBindDelay = 0ms;
+ return 0ms;
+ }
+ return {};
+ }
+ // fallthrough, mark as unhealthy, and retry with delay
+ }
+
const auto previousBindTs = mPreviousBindTs;
- const auto now = Clock::now();
mPreviousBindTs = now;
const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms);
if (previousBindTs.time_since_epoch() == Clock::duration::zero() ||
nonCrashingInterval > Constants::healthyDataLoaderUptime) {
mPreviousBindDelay = 0ms;
- return mPreviousBindDelay;
+ return 0ms;
}
constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay);
@@ -2264,12 +2383,16 @@
const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
-
return mPreviousBindDelay;
}
bool IncrementalService::DataLoaderStub::bind() {
- const auto bindDelay = updateBindDelay();
+ const auto maybeBindDelay = needToBind();
+ if (!maybeBindDelay) {
+ LOG(DEBUG) << "Skipping bind to " << mParams.packageName << " because of pending bind.";
+ return true;
+ }
+ const auto bindDelay = *maybeBindDelay;
if (bindDelay > 1s) {
LOG(INFO) << "Delaying bind to " << mParams.packageName << " by "
<< bindDelay.count() / 1000 << "s";
@@ -2279,7 +2402,21 @@
auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(),
this, &result);
if (!status.isOk() || !result) {
- LOG(ERROR) << "Failed to bind a data loader for mount " << id();
+ const bool healthy = (bindDelay == 0ms);
+ LOG(ERROR) << "Failed to bind a data loader for mount " << id()
+ << (healthy ? ", retrying." : "");
+
+ // Internal error, retry for healthy/new DLs.
+ // Let needToBind migrate it to unhealthy after too many retries.
+ if (healthy) {
+ if (mService.addTimedJob(*mService.mTimedQueue, id(), Constants::bindRetryInterval,
+ [this]() { fsmStep(); })) {
+ // Mark as binding so that we know it's not the DL's fault.
+ setCurrentStatus(IDataLoaderStatusListener::DATA_LOADER_BINDING);
+ return true;
+ }
+ }
+
return false;
}
return true;
@@ -2339,7 +2476,14 @@
// Do nothing, this is a reset state.
break;
case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: {
- return destroy();
+ switch (currentStatus) {
+ case IDataLoaderStatusListener::DATA_LOADER_BINDING:
+ setCurrentStatus(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+ return true;
+ default:
+ return destroy();
+ }
+ break;
}
case IDataLoaderStatusListener::DATA_LOADER_STARTED: {
switch (currentStatus) {
@@ -2353,6 +2497,7 @@
switch (currentStatus) {
case IDataLoaderStatusListener::DATA_LOADER_DESTROYED:
case IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE:
+ case IDataLoaderStatusListener::DATA_LOADER_BINDING:
return bind();
case IDataLoaderStatusListener::DATA_LOADER_BOUND:
return create();
@@ -2372,7 +2517,8 @@
fromServiceSpecificError(-EINVAL, "onStatusChange came to invalid DataLoaderStub");
}
if (id() != mountId) {
- LOG(ERROR) << "Mount ID mismatch: expected " << id() << ", but got: " << mountId;
+ LOG(ERROR) << "onStatusChanged: mount ID mismatch: expected " << id()
+ << ", but got: " << mountId;
return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch.");
}
if (newStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
@@ -2396,11 +2542,13 @@
}
oldStatus = mCurrentStatus;
- mCurrentStatus = newStatus;
targetStatus = mTargetStatus;
-
listener = mStatusListener;
+ // Change the status.
+ mCurrentStatus = newStatus;
+ mCurrentStatusTs = mService.mClock->now();
+
if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
// For unavailable, unbind from DataLoader to ensure proper re-commit.
@@ -2428,7 +2576,8 @@
"reportStreamHealth came to invalid DataLoaderStub");
}
if (id() != mountId) {
- LOG(ERROR) << "Mount ID mismatch: expected " << id() << ", but got: " << mountId;
+ LOG(ERROR) << "reportStreamHealth: mount ID mismatch: expected " << id()
+ << ", but got: " << mountId;
return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch.");
}
{
@@ -2694,6 +2843,8 @@
void IncrementalService::DataLoaderStub::onDump(int fd) {
dprintf(fd, " dataLoader: {\n");
dprintf(fd, " currentStatus: %d\n", mCurrentStatus);
+ dprintf(fd, " currentStatusTs: %lldmcs\n",
+ (long long)(elapsedMcs(mCurrentStatusTs, Clock::now())));
dprintf(fd, " targetStatus: %d\n", mTargetStatus);
dprintf(fd, " targetStatusTs: %lldmcs\n",
(long long)(elapsedMcs(mTargetStatusTs, Clock::now())));
@@ -2709,7 +2860,7 @@
dprintf(fd, " lastPendingReads: \n");
const auto control = mService.mIncFs->openMount(mHealthPath);
for (auto&& pendingRead : mLastPendingReads) {
- dprintf(fd, " fileId: %s\n", mService.mIncFs->toString(pendingRead.id).c_str());
+ dprintf(fd, " fileId: %s\n", IncFsWrapper::toString(pendingRead.id).c_str());
const auto metadata = mService.mIncFs->getMetadata(control, pendingRead.id);
dprintf(fd, " metadataHex: %s\n", toHexString(metadata).c_str());
dprintf(fd, " blockIndex: %d\n", pendingRead.block);
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 14e5a77..aa80bd4 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -164,7 +164,8 @@
std::string_view newPath);
int unlink(StorageId storage, std::string_view path);
- int isFileFullyLoaded(StorageId storage, std::string_view filePath) const;
+ incfs::LoadingState isFileFullyLoaded(StorageId storage, std::string_view filePath) const;
+ incfs::LoadingState isMountFullyLoaded(StorageId storage) const;
LoadingProgress getLoadingProgress(StorageId storage, bool stopOnFirstIncomplete) const;
@@ -231,6 +232,7 @@
MountId id() const { return mId.load(std::memory_order_relaxed); }
const content::pm::DataLoaderParamsParcel& params() const { return mParams; }
+ bool isSystemDataLoader() const;
void setHealthListener(StorageHealthCheckParams&& healthCheckParams,
const StorageHealthListener* healthListener);
long elapsedMsSinceOldestPendingRead();
@@ -267,7 +269,10 @@
BootClockTsUs getOldestTsFromLastPendingReads();
Milliseconds elapsedMsSinceKernelTs(TimePoint now, BootClockTsUs kernelTsUs);
- Milliseconds updateBindDelay();
+ // If the stub has to bind to the DL.
+ // Returns {} if bind operation is already in progress.
+ // Or bind delay in ms.
+ std::optional<Milliseconds> needToBind();
void registerForPendingReads();
void unregisterFromPendingReads();
@@ -283,6 +288,7 @@
std::condition_variable mStatusCondition;
int mCurrentStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
+ TimePoint mCurrentStatusTs = {};
int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
TimePoint mTargetStatusTs = {};
@@ -326,6 +332,7 @@
StorageMap storages;
BindMap bindPoints;
DataLoaderStubPtr dataLoaderStub;
+ TimePoint startLoadingTs = {};
std::atomic<int> nextStorageDirNo{0};
const IncrementalService& incrementalService;
@@ -344,12 +351,7 @@
void disallowReadLogs() { flags &= ~StorageFlags::ReadLogsAllowed; }
int32_t readLogsAllowed() const { return (flags & StorageFlags::ReadLogsAllowed); }
- void setReadLogsEnabled(bool value) {
- if (value)
- flags |= StorageFlags::ReadLogsEnabled;
- else
- flags &= ~StorageFlags::ReadLogsEnabled;
- }
+ void setReadLogsEnabled(bool value);
int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); }
static void cleanupFilesystem(std::string_view root);
@@ -407,9 +409,10 @@
IncFsMount::StorageMap::const_iterator storageIt,
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 isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const;
LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path,
bool stopOnFirstIncomplete) const;
@@ -443,6 +446,7 @@
const std::unique_ptr<TimedQueueWrapper> mTimedQueue;
const std::unique_ptr<TimedQueueWrapper> mProgressUpdateJobQueue;
const std::unique_ptr<FsWrapper> mFs;
+ const std::unique_ptr<ClockWrapper> mClock;
const std::string mIncrementalDir;
mutable std::mutex mLock;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index d613289..7e85f9d 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -134,6 +134,10 @@
} mLooper;
};
+std::string IncFsWrapper::toString(FileId fileId) {
+ return incfs::toString(fileId);
+}
+
class RealIncFs final : public IncFsWrapper {
public:
RealIncFs() = default;
@@ -173,9 +177,16 @@
FileId getFileId(const Control& control, std::string_view path) const final {
return incfs::getFileId(control, path);
}
- std::string toString(FileId fileId) const final { return incfs::toString(fileId); }
std::pair<IncFsBlockIndex, IncFsBlockIndex> countFilledBlocks(
const Control& control, std::string_view path) const final {
+ if (incfs::features() & Features::v2) {
+ const auto counts = incfs::getBlockCount(control, path);
+ if (!counts) {
+ return {-errno, -errno};
+ }
+ return {counts->filledDataBlocks + counts->filledHashBlocks,
+ counts->totalDataBlocks + counts->totalHashBlocks};
+ }
const auto fileId = incfs::getFileId(control, path);
const auto fd = incfs::openForSpecialOps(control, fileId);
int res = fd.get();
@@ -197,6 +208,13 @@
}
return {filledBlockCount, totalBlocksCount};
}
+ incfs::LoadingState isFileFullyLoaded(const Control& control,
+ std::string_view path) const final {
+ return incfs::isFullyLoaded(control, path);
+ }
+ incfs::LoadingState isEverythingFullyLoaded(const Control& control) const final {
+ return incfs::isEverythingFullyLoaded(control);
+ }
ErrorCode link(const Control& control, std::string_view from, std::string_view to) const final {
return incfs::link(control, from, to);
}
@@ -209,6 +227,10 @@
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);
+ }
WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final {
return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer);
@@ -329,6 +351,14 @@
}
};
+class RealClockWrapper final : public ClockWrapper {
+public:
+ RealClockWrapper() = default;
+ ~RealClockWrapper() = default;
+
+ TimePoint now() const final { return Clock::now(); }
+};
+
RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env)
: mServiceManager(std::move(serviceManager)), mJvm(RealJniWrapper::getJvm(env)) {}
@@ -388,6 +418,10 @@
return std::make_unique<RealFsWrapper>();
}
+std::unique_ptr<ClockWrapper> RealServiceManager::getClock() {
+ return std::make_unique<RealClockWrapper>();
+}
+
static JavaVM* getJavaVm(JNIEnv* env) {
CHECK(env);
JavaVM* jvm = nullptr;
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 245bb31..a787db5 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)>;
+ static std::string toString(FileId fileId);
+
virtual ~IncFsWrapper() = default;
virtual Features features() const = 0;
virtual void listExistingMounts(const ExistingMountCallback& cb) const = 0;
@@ -99,14 +101,18 @@
virtual incfs::RawMetadata getMetadata(const Control& control, FileId fileid) const = 0;
virtual incfs::RawMetadata getMetadata(const Control& control, std::string_view path) const = 0;
virtual FileId getFileId(const Control& control, std::string_view path) const = 0;
- virtual std::string toString(FileId fileId) const = 0;
virtual std::pair<IncFsBlockIndex, IncFsBlockIndex> countFilledBlocks(
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 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 WaitResult waitForPendingReads(
const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0;
@@ -158,6 +164,12 @@
virtual void listFilesRecursive(std::string_view directoryPath, FileCallback onFile) const = 0;
};
+class ClockWrapper {
+public:
+ virtual ~ClockWrapper() = default;
+ virtual TimePoint now() const = 0;
+};
+
class ServiceManagerWrapper {
public:
virtual ~ServiceManagerWrapper() = default;
@@ -170,6 +182,7 @@
virtual std::unique_ptr<TimedQueueWrapper> getTimedQueue() = 0;
virtual std::unique_ptr<TimedQueueWrapper> getProgressUpdateJobQueue() = 0;
virtual std::unique_ptr<FsWrapper> getFs() = 0;
+ virtual std::unique_ptr<ClockWrapper> getClock() = 0;
};
// --- Real stuff ---
@@ -187,6 +200,7 @@
std::unique_ptr<TimedQueueWrapper> getTimedQueue() final;
std::unique_ptr<TimedQueueWrapper> getProgressUpdateJobQueue() final;
std::unique_ptr<FsWrapper> getFs() final;
+ std::unique_ptr<ClockWrapper> getClock() final;
private:
template <class INTERFACE>
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 5236983..fb3a8a0 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -248,6 +248,27 @@
}
return binder::Status::ok();
}
+ binder::Status bindToDataLoaderNotOkWithNoDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(bindDelayMs == 0) << bindDelayMs;
+ *_aidl_return = false;
+ return binder::Status::ok();
+ }
+ binder::Status bindToDataLoaderBindingWithNoDelay(int32_t mountId,
+ const DataLoaderParamsParcel& params,
+ int bindDelayMs,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ CHECK(bindDelayMs == 0) << bindDelayMs;
+ *_aidl_return = true;
+ if (listener) {
+ listener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_BINDING);
+ }
+ return binder::Status::ok();
+ }
binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId,
const DataLoaderParamsParcel& params,
int bindDelayMs,
@@ -341,16 +362,20 @@
MOCK_CONST_METHOD2(getMetadata, RawMetadata(const Control& control, FileId fileid));
MOCK_CONST_METHOD2(getMetadata, RawMetadata(const Control& control, std::string_view path));
MOCK_CONST_METHOD2(getFileId, FileId(const Control& control, std::string_view path));
- MOCK_CONST_METHOD1(toString, std::string(FileId fileId));
MOCK_CONST_METHOD2(countFilledBlocks,
std::pair<IncFsBlockIndex, IncFsBlockIndex>(const Control& control,
std::string_view path));
+ MOCK_CONST_METHOD2(isFileFullyLoaded,
+ incfs::LoadingState(const Control& control, std::string_view path));
+ MOCK_CONST_METHOD1(isEverythingFullyLoaded, incfs::LoadingState(const Control& control));
MOCK_CONST_METHOD3(link,
ErrorCode(const Control& control, std::string_view from,
std::string_view to));
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(waitForPendingReads,
WaitResult(const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer));
@@ -358,7 +383,10 @@
ErrorCode(const Control& control,
const std::vector<PerUidReadTimeouts>& perUidReadTimeouts));
- MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); }
+ MockIncFs() {
+ ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return());
+ ON_CALL(*this, reserveSpace(_, _, _)).WillByDefault(Return(0));
+ }
void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); }
@@ -557,6 +585,21 @@
}
};
+class MockClockWrapper : public ClockWrapper {
+public:
+ MOCK_CONST_METHOD0(now, TimePoint());
+
+ void start() { ON_CALL(*this, now()).WillByDefault(Invoke(this, &MockClockWrapper::getClock)); }
+ template <class Delta>
+ void advance(Delta delta) {
+ mClock += delta;
+ }
+
+ TimePoint getClock() const { return mClock; }
+
+ TimePoint mClock = Clock::now();
+};
+
class MockStorageHealthListener : public os::incremental::BnStorageHealthListener {
public:
MOCK_METHOD2(onHealthStatus, binder::Status(int32_t storageId, int32_t status));
@@ -594,7 +637,7 @@
std::unique_ptr<MockLooperWrapper> looper,
std::unique_ptr<MockTimedQueueWrapper> timedQueue,
std::unique_ptr<MockTimedQueueWrapper> progressUpdateJobQueue,
- std::unique_ptr<MockFsWrapper> fs)
+ std::unique_ptr<MockFsWrapper> fs, std::unique_ptr<MockClockWrapper> clock)
: mVold(std::move(vold)),
mDataLoaderManager(std::move(dataLoaderManager)),
mIncFs(std::move(incfs)),
@@ -603,7 +646,8 @@
mLooper(std::move(looper)),
mTimedQueue(std::move(timedQueue)),
mProgressUpdateJobQueue(std::move(progressUpdateJobQueue)),
- mFs(std::move(fs)) {}
+ mFs(std::move(fs)),
+ mClock(std::move(clock)) {}
std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); }
std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final {
return std::move(mDataLoaderManager);
@@ -619,6 +663,7 @@
return std::move(mProgressUpdateJobQueue);
}
std::unique_ptr<FsWrapper> getFs() final { return std::move(mFs); }
+ std::unique_ptr<ClockWrapper> getClock() final { return std::move(mClock); }
private:
std::unique_ptr<MockVoldService> mVold;
@@ -630,6 +675,7 @@
std::unique_ptr<MockTimedQueueWrapper> mTimedQueue;
std::unique_ptr<MockTimedQueueWrapper> mProgressUpdateJobQueue;
std::unique_ptr<MockFsWrapper> mFs;
+ std::unique_ptr<MockClockWrapper> mClock;
};
// --- IncrementalServiceTest ---
@@ -657,6 +703,8 @@
mProgressUpdateJobQueue = progressUpdateJobQueue.get();
auto fs = std::make_unique<NiceMock<MockFsWrapper>>();
mFs = fs.get();
+ auto clock = std::make_unique<NiceMock<MockClockWrapper>>();
+ mClock = clock.get();
mIncrementalService = std::make_unique<
IncrementalService>(MockServiceManager(std::move(vold),
std::move(dataloaderManager),
@@ -664,12 +712,13 @@
std::move(jni), std::move(looper),
std::move(timedQueue),
std::move(progressUpdateJobQueue),
- std::move(fs)),
+ std::move(fs), std::move(clock)),
mRootDir.path);
mDataLoaderParcel.packageName = "com.test";
mDataLoaderParcel.arguments = "uri";
mDataLoaderManager->unbindFromDataLoaderSuccess();
mIncrementalService->onSystemReady();
+ mClock->start();
setupSuccess();
}
@@ -724,6 +773,7 @@
NiceMock<MockTimedQueueWrapper>* mTimedQueue = nullptr;
NiceMock<MockTimedQueueWrapper>* mProgressUpdateJobQueue = nullptr;
NiceMock<MockFsWrapper>* mFs = nullptr;
+ NiceMock<MockClockWrapper>* mClock = nullptr;
NiceMock<MockDataLoader>* mDataLoader = nullptr;
std::unique_ptr<IncrementalService> mIncrementalService;
TemporaryDir mRootDir;
@@ -853,6 +903,119 @@
mDataLoaderManager->setDataLoaderStatusDestroyed();
}
+TEST_F(IncrementalServiceTest, testDataLoaderOnRestart) {
+ mIncFs->waitForPendingReadsSuccess();
+ mIncFs->openMountSuccess();
+
+ constexpr auto bindRetryInterval = 5s;
+
+ EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(10);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
+ EXPECT_CALL(*mDataLoader, start(_)).Times(6);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(3);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+
+ // First binds to DataLoader fails... because it's restart.
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderNotOkWithNoDelay));
+
+ // Request DL start.
+ mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
+
+ // Retry callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, bindRetryInterval);
+ auto retryCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Expecting the same bindToDataLoaderNotOkWithNoDelay call.
+ mClock->advance(5s);
+
+ retryCallback();
+ // Retry callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, bindRetryInterval);
+ retryCallback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Returning "binding" so that we can retry.
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderBindingWithNoDelay));
+
+ // Expecting bindToDataLoaderBindingWithNoDelay call.
+ mClock->advance(5s);
+
+ retryCallback();
+ // No retry callback.
+ ASSERT_EQ(mTimedQueue->mAfter, 0ms);
+ ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+
+ // Should not change the bindToDataLoader call count
+ ASSERT_NE(nullptr, mLooper->mCallback);
+ ASSERT_NE(nullptr, mLooper->mCallbackData);
+ auto looperCb = mLooper->mCallback;
+ auto looperCbData = mLooper->mCallbackData;
+ looperCb(-1, -1, looperCbData);
+
+ // Expecting the same bindToDataLoaderBindingWithNoDelay call.
+ mClock->advance(5s);
+
+ // Use pending reads callback to trigger binding.
+ looperCb(-1, -1, looperCbData);
+
+ // No retry callback.
+ ASSERT_EQ(mTimedQueue->mAfter, 0ms);
+ ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+
+ // Now we are out of 10m "retry" budget, let's finally bind.
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager, &MockDataLoaderManager::bindToDataLoaderOk));
+ mClock->advance(11min);
+
+ // Use pending reads callback to trigger binding.
+ looperCb(-1, -1, looperCbData);
+
+ // No retry callback.
+ ASSERT_EQ(mTimedQueue->mAfter, 0ms);
+ ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+
+ // And test the rest of the backoff.
+ // Simulated crash/other connection breakage.
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+ ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(mDataLoaderManager,
+ &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
+ mDataLoaderManager->setDataLoaderStatusDestroyed();
+}
+
TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) {
mDataLoader->initializeCreateOkNoStatus();
EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
@@ -963,7 +1126,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(4);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(5);
sp<NiceMock<MockStorageHealthListener>> listener{new NiceMock<MockStorageHealthListener>};
NiceMock<MockStorageHealthListener>* listenerMock = listener.get();
@@ -1136,6 +1299,147 @@
ASSERT_EQ(mDataLoader->setStorageParams(true), -EPERM);
}
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndTimedOut) {
+ mVold->setIncFsMountOptionsSuccess();
+ mAppOpsManager->checkPermissionSuccess();
+
+ const auto readLogsMaxInterval = 2h;
+
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // Enabling and then disabling readlogs.
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(2);
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, false)).Times(1);
+ // After setIncFsMountOptions succeeded expecting to start watching.
+ EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
+ // Not expecting callback removal.
+ EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Disable readlogs callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, readLogsMaxInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+ // Now advance clock for 1hr.
+ mClock->advance(1h);
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+ // Now call the timed callback, it should turn off the readlogs.
+ callback();
+ // Now advance clock for 2hrs.
+ mClock->advance(readLogsMaxInterval);
+ ASSERT_EQ(mDataLoader->setStorageParams(true), -EPERM);
+}
+
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndNoTimedOutForSystem) {
+ mVold->setIncFsMountOptionsSuccess();
+ mAppOpsManager->checkPermissionSuccess();
+
+ const auto readLogsMaxInterval = 2h;
+
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // Enabling and then disabling readlogs.
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(3);
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, false)).Times(0);
+ // After setIncFsMountOptions succeeded expecting to start watching.
+ EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
+ // Not expecting callback removal.
+ EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
+ // System data loader.
+ 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), {}, {},
+ {}, {}));
+
+ // No readlogs callback.
+ ASSERT_EQ(mTimedQueue->mAfter, 0ms);
+ ASSERT_EQ(mTimedQueue->mWhat, nullptr);
+
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+ // Now advance clock for 1hr.
+ mClock->advance(1h);
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+ // Now advance clock for 2hrs.
+ mClock->advance(readLogsMaxInterval);
+ ASSERT_EQ(mDataLoader->setStorageParams(true), 0);
+}
+
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndNewInstall) {
+ mVold->setIncFsMountOptionsSuccess();
+ mAppOpsManager->checkPermissionSuccess();
+
+ const auto readLogsMaxInterval = 2h;
+
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ // Enabling and then disabling readlogs.
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(3);
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, false)).Times(1);
+ // After setIncFsMountOptions succeeded expecting to start watching.
+ EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1);
+ // Not expecting callback removal.
+ EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+
+ auto dataLoaderParcel = mDataLoaderParcel;
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(dataLoaderParcel), {}, {},
+ {}, {}));
+
+ // Disable readlogs callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, readLogsMaxInterval);
+ auto callback = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+ // Now advance clock for 1.5hrs.
+ mClock->advance(90min);
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+
+ // New installation.
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+ {}, {}));
+
+ // New callback present.
+ ASSERT_EQ(storageId, mTimedQueue->mId);
+ ASSERT_EQ(mTimedQueue->mAfter, readLogsMaxInterval);
+ auto callback2 = mTimedQueue->mWhat;
+ mTimedQueue->clearJob(storageId);
+
+ // Old callback should not disable readlogs (setIncFsMountOptions should be called only once).
+ callback();
+ // Advance clock for another 1.5hrs.
+ mClock->advance(90min);
+ // Still success even it's 3hrs past first install.
+ ASSERT_GE(mDataLoader->setStorageParams(true), 0);
+
+ // New one should disable.
+ callback2();
+ // And timeout.
+ mClock->advance(90min);
+ ASSERT_EQ(mDataLoader->setStorageParams(true), -EPERM);
+}
+
TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChanged) {
mVold->setIncFsMountOptionsSuccess();
mAppOpsManager->checkPermissionSuccess();
@@ -1261,51 +1565,37 @@
ASSERT_EQ(res, 0);
}
-TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithNoFile) {
- mIncFs->countFilledBlocksFails();
- mFs->hasNoFile();
-
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedNoData) {
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ .Times(1)
+ .WillOnce(Return(incfs::LoadingState::MissingBlocks));
+ ASSERT_GT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
}
-TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithFailedRanges) {
- mIncFs->countFilledBlocksFails();
- mFs->hasFiles();
-
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedError) {
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
- ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
-}
-
-TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccessWithEmptyRanges) {
- mIncFs->countFilledBlocksEmpty();
- mFs->hasFiles();
-
- TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
- IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
- ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ .Times(1)
+ .WillOnce(Return(incfs::LoadingState(-1)));
+ ASSERT_LT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
}
TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) {
- mIncFs->countFilledBlocksFullyLoaded();
- mFs->hasFiles();
-
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
- ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ .Times(1)
+ .WillOnce(Return(incfs::LoadingState::Full));
+ ASSERT_EQ(0, (int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
}
TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithNoFile) {
@@ -1519,7 +1809,7 @@
EXPECT_CALL(*mDataLoader, start(_)).Times(1);
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0);
- EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
+ EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
int storageId =
@@ -1546,6 +1836,9 @@
// Empty storage.
mIncFs->countFilledBlocksEmpty();
+ // Mark DataLoader as 'system' so that readlogs don't pollute the timed queue.
+ mDataLoaderParcel.packageName = "android";
+
TemporaryDir tempDir;
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5fbf1c4..6f71e99 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -116,6 +116,7 @@
import com.android.server.clipboard.ClipboardService;
import com.android.server.compat.PlatformCompat;
import com.android.server.compat.PlatformCompatNative;
+import com.android.server.connectivity.PacProxyService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -389,8 +390,6 @@
private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
private static final String BLOCK_MAP_FILE = "/cache/recovery/block.map";
- private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
-
// maximum number of binder threads used for system_server
// will be higher than the system default
private static final int sMaxBinderThreads = 31;
@@ -1315,6 +1314,7 @@
ConsumerIrService consumerIr = null;
MmsServiceBroker mmsService = null;
HardwarePropertiesManagerService hardwarePropertiesService = null;
+ PacProxyService pacProxyService = null;
boolean disableSystemTextClassifier = SystemProperties.getBoolean(
"config.disable_systemtextclassifier", false);
@@ -1325,7 +1325,7 @@
false);
boolean enableLeftyService = SystemProperties.getBoolean("config.enable_lefty", false);
- boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
+ boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1");
boolean isWatch = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WATCH);
@@ -1660,8 +1660,7 @@
t.traceEnd();
final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
- final boolean hasGsi = SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
- if (hasPdb && !hasGsi) {
+ if (hasPdb) {
t.traceBegin("StartPersistentDataBlock");
mSystemServiceManager.startService(PersistentDataBlockService.class);
t.traceEnd();
@@ -1874,6 +1873,15 @@
t.traceEnd();
}
+ t.traceBegin("StartPacProxyService");
+ try {
+ pacProxyService = new PacProxyService(context);
+ ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
+ } catch (Throwable e) {
+ reportWtf("starting PacProxyService", e);
+ }
+ t.traceEnd();
+
t.traceBegin("StartConnectivityService");
// This has to be called after NetworkManagementService, NetworkStatsService
// and NetworkPolicyManager because ConnectivityService needs to take these
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
index 87b2c84..4c5bbeb 100644
--- a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
@@ -48,6 +48,8 @@
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
/**
* Handles per-user requests received by
@@ -60,6 +62,11 @@
implements RemoteMusicRecognitionService.Callbacks {
private static final String TAG = MusicRecognitionManagerPerUserService.class.getSimpleName();
+ private static final String MUSIC_RECOGNITION_MANAGER_ATTRIBUTION_TAG =
+ "MusicRecognitionManagerService";
+ private static final String KEY_MUSIC_RECOGNITION_SERVICE_ATTRIBUTION_TAG =
+ "android.media.musicrecognition.attributiontag";
+
// Number of bytes per sample of audio (which is a short).
private static final int BYTES_PER_SAMPLE = 2;
private static final int MAX_STREAMING_SECONDS = 24;
@@ -68,18 +75,24 @@
@GuardedBy("mLock")
private RemoteMusicRecognitionService mRemoteService;
private final AppOpsManager mAppOpsManager;
+ private final String mAttributionMessage;
- private String mAttributionTag;
- private String mAttributionMessage;
+ // Service info of the remote MusicRecognitionService (which the audio gets forwarded to).
private ServiceInfo mServiceInfo;
+ private CompletableFuture<String> mAttributionTagFuture;
MusicRecognitionManagerPerUserService(
@NonNull MusicRecognitionManagerService primary,
@NonNull Object lock, int userId) {
super(primary, lock, userId);
- mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
+
+ // When attributing audio-access, this establishes that audio access is performed by
+ // MusicRecognitionManager (on behalf of the receiving service, whose attribution tag,
+ // provided by mAttributionTagFuture, is used for the actual calls to startProxyOp(...).
+ mAppOpsManager = getContext().createAttributionContext(
+ MUSIC_RECOGNITION_MANAGER_ATTRIBUTION_TAG).getSystemService(AppOpsManager.class);
mAttributionMessage = String.format("MusicRecognitionManager.invokedByUid.%s", userId);
- mAttributionTag = null;
+ mAttributionTagFuture = null;
mServiceInfo = null;
}
@@ -126,10 +139,13 @@
new MusicRecognitionServiceCallback(clientCallback),
mMaster.isBindInstantServiceAllowed(),
mMaster.verbose);
+
try {
mServiceInfo =
getContext().getPackageManager().getServiceInfo(
- mRemoteService.getComponentName(), 0);
+ mRemoteService.getComponentName(), PackageManager.GET_META_DATA);
+ mAttributionTagFuture = mRemoteService.getAttributionTag();
+ Slog.i(TAG, "Remote service bound: " + mRemoteService.getComponentName());
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Service was not found.", e);
}
@@ -172,11 +188,13 @@
ParcelFileDescriptor audioSink = clientPipe.second;
ParcelFileDescriptor clientRead = clientPipe.first;
- mMaster.mExecutorService.execute(() -> {
- streamAudio(recognitionRequest, clientCallback, audioSink);
- });
+ mAttributionTagFuture.thenAcceptAsync(
+ tag -> {
+ streamAudio(tag, recognitionRequest, clientCallback, audioSink);
+ }, mMaster.mExecutorService);
+
// Send the pipe down to the lookup service while we write to it asynchronously.
- mRemoteService.writeAudioToPipe(clientRead, recognitionRequest.getAudioFormat());
+ mRemoteService.onAudioStreamStarted(clientRead, recognitionRequest.getAudioFormat());
}
/**
@@ -186,10 +204,12 @@
* @param clientCallback the callback to notify on errors.
* @param audioSink the sink to which to stream audio to.
*/
- private void streamAudio(@NonNull RecognitionRequest recognitionRequest,
- IMusicRecognitionManagerCallback clientCallback, ParcelFileDescriptor audioSink) {
+ private void streamAudio(@Nullable String attributionTag,
+ @NonNull RecognitionRequest recognitionRequest,
+ IMusicRecognitionManagerCallback clientCallback,
+ ParcelFileDescriptor audioSink) {
try {
- startRecordAudioOp();
+ startRecordAudioOp(attributionTag);
} catch (SecurityException e) {
// A security exception can occur if the MusicRecognitionService (receiving the audio)
// does not (or does no longer) hold the necessary permissions to record audio.
@@ -214,7 +234,7 @@
Slog.e(TAG, "Audio streaming stopped.", e);
} finally {
audioRecord.release();
- finishRecordAudioOp();
+ finishRecordAudioOp(attributionTag);
try {
clientCallback.onAudioStreamClosed();
} catch (RemoteException ignored) {
@@ -323,23 +343,32 @@
* Tracks that the RECORD_AUDIO operation started (attributes it to the service receiving the
* audio).
*/
- private void startRecordAudioOp() {
- mAppOpsManager.startProxyOp(
+ private void startRecordAudioOp(@Nullable String attributionTag) {
+ int status = mAppOpsManager.startProxyOp(
Objects.requireNonNull(AppOpsManager.permissionToOp(RECORD_AUDIO)),
mServiceInfo.applicationInfo.uid,
mServiceInfo.packageName,
- mAttributionTag,
+ attributionTag,
mAttributionMessage);
+ // The above should already throw a SecurityException. This is just a fallback.
+ if (status != AppOpsManager.MODE_ALLOWED) {
+ throw new SecurityException(String.format(
+ "Failed to obtain RECORD_AUDIO permission (status: %d) for "
+ + "receiving service: %s", status, mServiceInfo.getComponentName()));
+ }
+ Slog.i(TAG, String.format(
+ "Starting audio streaming. Attributing to %s (%d) with tag '%s'",
+ mServiceInfo.packageName, mServiceInfo.applicationInfo.uid, attributionTag));
}
/** Tracks that the RECORD_AUDIO operation finished. */
- private void finishRecordAudioOp() {
+ private void finishRecordAudioOp(@Nullable String attributionTag) {
mAppOpsManager.finishProxyOp(
Objects.requireNonNull(AppOpsManager.permissionToOp(RECORD_AUDIO)),
mServiceInfo.applicationInfo.uid,
mServiceInfo.packageName,
- mAttributionTag);
+ attributionTag);
}
/** Establishes an audio stream from the DSP audio source. */
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
index 6c7d673..99b4482 100644
--- a/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
@@ -20,15 +20,20 @@
import android.content.ComponentName;
import android.content.Context;
import android.media.AudioFormat;
+import android.media.musicrecognition.IMusicRecognitionAttributionTagCallback;
import android.media.musicrecognition.IMusicRecognitionService;
import android.media.musicrecognition.MusicRecognitionService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.text.format.DateUtils;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.server.musicrecognition.MusicRecognitionManagerPerUserService.MusicRecognitionServiceCallback;
+import java.util.concurrent.CompletableFuture;
+
+
/** Remote connection to an instance of {@link MusicRecognitionService}. */
public class RemoteMusicRecognitionService extends
AbstractMultiplePendingRequestsRemoteService<RemoteMusicRecognitionService,
@@ -81,9 +86,26 @@
* Sends the given descriptor to the app's {@link MusicRecognitionService} to read the
* audio.
*/
- public void writeAudioToPipe(@NonNull ParcelFileDescriptor fd,
+ public void onAudioStreamStarted(@NonNull ParcelFileDescriptor fd,
@NonNull AudioFormat audioFormat) {
scheduleAsyncRequest(
binder -> binder.onAudioStreamStarted(fd, audioFormat, mServerCallback));
}
+
+
+ /**
+ * Returns the name of the <attribution> tag defined in the remote service's manifest.
+ */
+ public CompletableFuture<String> getAttributionTag() {
+ CompletableFuture<String> attributionTagFuture = new CompletableFuture<String>();
+ scheduleAsyncRequest(
+ binder -> binder.getAttributionTag(
+ new IMusicRecognitionAttributionTagCallback.Stub() {
+ @Override
+ public void onAttributionTag(String tag) throws RemoteException {
+ attributionTagFuture.complete(tag);
+ }
+ }));
+ return attributionTagFuture;
+ }
}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
index 169b85e..b07fe19 100644
--- a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MANAGE_SMARTSPACE;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.content.Context.SMARTSPACE_SERVICE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -161,11 +162,13 @@
Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
}
- if (!(mServiceNameResolver.isTemporary(userId)
+ Context ctx = getContext();
+ if (!(ctx.checkCallingPermission(MANAGE_SMARTSPACE) == PERMISSION_GRANTED
+ || mServiceNameResolver.isTemporary(userId)
|| mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
- String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid();
+ String msg = "Permission Denial: Cannot call " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 334e53a..988c02b 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -23,14 +23,17 @@
android_test {
name: "PackageManagerServiceUnitTests",
- srcs: ["src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
static_libs: [
"androidx.test.rules",
"androidx.test.runner",
"junit",
+ "kotlin-test",
"services.core",
"servicestests-utils",
- "testng",
"truth-prebuilt",
],
platform_apis: true,
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 0fa9a1d..d5eda20 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
@@ -25,8 +25,8 @@
import android.util.ArraySet
import com.android.server.SystemConfig
import com.android.server.compat.PlatformCompat
-import com.android.server.pm.verify.domain.DomainVerificationCollector
import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.verify.domain.DomainVerificationCollector
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
index 8ef9239..9693f3b 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
@@ -19,6 +19,7 @@
import android.content.pm.verify.domain.DomainSet
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationState
import android.content.pm.verify.domain.DomainVerificationUserState
import android.os.Parcel
import android.os.Parcelable
@@ -74,7 +75,9 @@
DomainVerificationInfo(
UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
"com.test.pkg",
- massiveSet.withIndex().associate { it.value to it.index }
+ massiveSet.withIndex().associate {
+ it.value to DomainVerificationState.convertToInfoState(it.index)
+ }
)
},
unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) },
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 53f0ca2..7e25901 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
@@ -20,20 +20,21 @@
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
-import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationState
import android.os.Build
import android.os.Process
import android.util.ArraySet
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageSetting
+import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationEnforcer
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
-import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.spyThrowOnUnmocked
import com.android.server.testutils.whenever
@@ -50,6 +51,8 @@
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
@RunWith(Parameterized::class)
class DomainVerificationEnforcerTest {
@@ -81,47 +84,49 @@
whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
true
}
+ whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 }
})
}
}
- val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, DomainVerificationService> =
- {
- val callingUidInt = AtomicInteger(-1)
- val callingUserIdInt = AtomicInteger(-1)
+ val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger,
+ DomainVerificationService> = {
+ val callingUidInt = AtomicInteger(-1)
+ val callingUserIdInt = AtomicInteger(-1)
- val connection: DomainVerificationManagerInternal.Connection =
- mockThrowOnUnmocked {
- whenever(callingUid) { callingUidInt.get() }
- whenever(callingUserId) { callingUserIdInt.get() }
- whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
- whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
- whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
- whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
- whenever(schedule(anyInt(), any()))
- whenever(scheduleWriteSettings())
- whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
- whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
- true
- }
+ val connection: DomainVerificationManagerInternal.Connection =
+ mockThrowOnUnmocked {
+ whenever(callingUid) { callingUidInt.get() }
+ whenever(callingUserId) { callingUserIdInt.get() }
+ whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
+ whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
+ whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
+ whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
+ whenever(schedule(anyInt(), any()))
+ whenever(scheduleWriteSettings())
+ whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
+ whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
+ true
}
- val service = DomainVerificationService(
- it,
- mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
- mockThrowOnUnmocked {
- whenever(
- isChangeEnabled(
- anyLong(),
- any()
- )
- ) { true }
- }).apply {
- setConnection(connection)
+ whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 }
}
-
- Triple(callingUidInt, callingUserIdInt, service)
+ val service = DomainVerificationService(
+ it,
+ mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
+ mockThrowOnUnmocked {
+ whenever(
+ isChangeEnabled(
+ anyLong(),
+ any()
+ )
+ ) { true }
+ }).apply {
+ setConnection(connection)
}
+ Triple(callingUidInt, callingUserIdInt, service)
+ }
+
fun enforcer(
type: Type,
name: String,
@@ -175,7 +180,7 @@
service(Type.INTERNAL, "setStatusInternalPackageName") {
setDomainVerificationStatusInternal(
it.targetPackageName,
- DomainVerificationManager.STATE_SUCCESS,
+ DomainVerificationState.STATE_SUCCESS,
ArraySet(setOf("example.com"))
)
},
@@ -206,7 +211,7 @@
setDomainVerificationStatus(
it.targetDomainSetId,
setOf("example.com"),
- DomainVerificationManager.STATE_SUCCESS
+ DomainVerificationState.STATE_SUCCESS
)
},
service(Type.VERIFIER, "setStatusInternalUid") {
@@ -214,7 +219,7 @@
it.callingUid,
it.targetDomainSetId,
setOf("example.com"),
- DomainVerificationManager.STATE_SUCCESS
+ DomainVerificationState.STATE_SUCCESS
)
},
service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") {
@@ -475,24 +480,7 @@
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// User selector makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
- if (throws) {
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
- } else {
- allUids.forEach {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
-
- // User selector doesn't use QUERY_ALL, so the invisible package should always fail
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = false, callingUserId, targetUserId)
- }
- }
+ runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws)
}
val callingUserId = 0
@@ -529,24 +517,7 @@
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// User selector makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
- if (throws) {
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
- } else {
- allUids.forEach {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
-
- // User selector doesn't use QUERY_ALL, so the invisible package should always fail
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = false, callingUserId, targetUserId)
- }
- }
+ runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws)
}
val callingUserId = 0
@@ -590,24 +561,10 @@
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// Legacy makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
- if (throws) {
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
- } else {
- allUids.forEach {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
-
- // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = false, callingUserId, targetUserId)
- }
- }
+ // The legacy selector does a silent failure when the user IDs don't match, so it
+ // cannot verify the non-existent user ID check, as it will not throw an Exception.
+ runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws,
+ verifyUserIdCheck = false)
}
val callingUserId = 0
@@ -642,27 +599,29 @@
}
val target = params.construct(context)
+ // Legacy code can return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
+ // as an error code. This is distinct from the class level assertFails as unfortunately
+ // the same number, 0, is used in opposite contexts, where it does represent a failure
+ // for this legacy case, but not for the modern APIs.
+ fun assertFailsLegacy(block: () -> Any?) {
+ try {
+ val value = block()
+ if ((value as? Int)
+ != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
+ ) {
+ throw AssertionError("Expected call to return false, was $value")
+ }
+ } catch (e: SecurityException) {
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Any of these 2 exceptions are considered failures, which is expected
+ }
+ }
+
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// Legacy makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
- if (throws) {
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
- } else {
- allUids.forEach {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
-
- // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = false, callingUserId, targetUserId)
- }
- }
+ runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws,
+ assertFailsMethod = ::assertFailsLegacy)
}
val callingUserId = 0
@@ -704,17 +663,8 @@
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// Owner querent makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
- if (throws) {
- allUids.forEach {
- assertFails {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
- } else {
- allUids.forEach {
- runMethod(target, it, visible = true, callingUserId, targetUserId)
- }
- }
+ runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws,
+ verifyInvisiblePkg = false)
}
val callingUserId = 0
@@ -782,22 +732,88 @@
return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy)
}
+ private fun runCrossUserMethod(
+ allUids: Iterable<Int>,
+ target: Any,
+ callingUserId: Int,
+ targetUserId: Int,
+ throws: Boolean,
+ verifyUserIdCheck: Boolean = true,
+ verifyInvisiblePkg: Boolean = true,
+ assertFailsMethod: (() -> Any?) -> Unit = ::assertFails,
+ ) {
+ if (throws) {
+ allUids.forEach {
+ assertFailsMethod {
+ // When testing a non-user ID failure, send an invalid user ID.
+ // This ensures the failure occurs before the user ID check is run.
+ try {
+ runMethod(target, it, visible = true, callingUserId, 100)
+ } catch (e: SecurityException) {
+ if (verifyUserIdCheck) {
+ e.message?.let {
+ if (it.contains("user ID", ignoreCase = true)
+ || it.contains("100")) {
+ fail(
+ "Method should not check user existence before permissions"
+ )
+ }
+ }
+ }
+
+ // Rethrow to allow normal fail checking logic to run
+ throw e
+ }
+ }
+ }
+ } else {
+ allUids.forEach {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+
+ if (verifyInvisiblePkg) {
+ allUids.forEach {
+ assertFailsMethod {
+ runMethod(target, it, visible = false, callingUserId, targetUserId)
+ }
+ }
+ }
+
+ if (verifyUserIdCheck) {
+ // An invalid target user ID should always fail
+ allUids.forEach {
+ assertFailsWith(SecurityException::class) {
+ runMethod(target, it, visible = true, callingUserId, 100)
+ }
+ }
+
+ // An invalid calling user ID should always fail, although this cannot happen in prod
+ allUids.forEach {
+ assertFailsWith(SecurityException::class) {
+ runMethod(target, it, visible = true, 100, targetUserId)
+ }
+ }
+ }
+ }
+
private fun assertFails(block: () -> Any?) {
try {
val value = block()
- // Some methods return false rather than throwing, so check that as well
- if ((value as? Boolean) != false) {
- // Can also return default value if it's a legacy call
- if ((value as? Int)
- != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
- ) {
- throw AssertionError("Expected call to return false, was $value")
- }
+ // Some methods return false or an error rather than throwing, so check that as well
+ val valueAsBoolean = value as? Boolean
+ if (valueAsBoolean == false) {
+ // Expected failure, do not throw
+ return
+ }
+
+ val valueAsInt = value as? Int
+ if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) {
+ throw AssertionError("Expected call to return false, was $value")
}
} catch (e: SecurityException) {
} catch (e: PackageManager.NameNotFoundException) {
- } catch (e: DomainVerificationManager.InvalidDomainSetException) {
- // Any of these 3 exceptions are considered failures, which is expected
+ // Any of these 2 exceptions are considered failures, which is expected
}
}
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
new file mode 100644
index 0000000..8ae4c5a
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.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.server.pm.test.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager;
+
+import com.android.server.pm.verify.domain.DomainVerificationService;
+
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Proxies Kotlin calls to the Java layer such that null values can be passed for {@link NonNull}
+ * marked parameters, as Kotlin disallows this at the compiler leveling, preventing the null error
+ * codes from being tested.
+ */
+class DomainVerificationJavaUtil {
+
+ static int setStatusForceNullable(@NonNull DomainVerificationService service,
+ @Nullable UUID domainSetId, @Nullable Set<String> domains, int state)
+ throws PackageManager.NameNotFoundException {
+ return service.setDomainVerificationStatus(domainSetId, domains, state);
+ }
+
+ static int setUserSelectionForceNullable(@NonNull DomainVerificationService service,
+ @Nullable UUID domainSetId, @Nullable Set<String> domains, boolean enabled,
+ @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
+ return service.setDomainVerificationUserSelection(domainSetId, domains, enabled, userId);
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
index 9a3bd99..509824d 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
@@ -19,9 +19,9 @@
import android.content.pm.IntentFilterVerificationInfo
import android.content.pm.PackageManager
import android.util.ArraySet
-import com.android.server.pm.verify.domain.DomainVerificationLegacySettings
import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.readXml
import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.writeXml
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
import org.junit.Test
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
new file mode 100644
index 0000000..0e74b65
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -0,0 +1,372 @@
+/*
+ * 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.test.verify.domain
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationUserState
+import android.os.Build
+import android.os.PatternMatcher
+import android.os.Process
+import android.util.ArraySet
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.verify.domain.DomainVerificationService
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.anyString
+import java.util.UUID
+import kotlin.test.assertFailsWith
+
+class DomainVerificationManagerApiTest {
+
+ companion object {
+ private const val PKG_ONE = "com.test.one"
+ private const val PKG_TWO = "com.test.two"
+ private const val PKG_THREE = "com.test.three"
+
+ private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
+ private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
+ private val UUID_THREE = UUID.fromString("0b3260ed-07c4-4b45-840b-237f8fb8b433")
+ private val UUID_INVALID = UUID.fromString("ad33babc-490b-4965-9d78-7e91248b00f")
+
+ private val DOMAIN_BASE = DomainVerificationManagerApiTest::class.java.packageName
+ private val DOMAIN_1 = "one.$DOMAIN_BASE"
+ private val DOMAIN_2 = "two.$DOMAIN_BASE"
+ private val DOMAIN_3 = "three.$DOMAIN_BASE"
+ private val DOMAIN_4 = "four.$DOMAIN_BASE"
+ }
+
+ @Test
+ fun queryValidVerificationPackageNames() {
+ val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
+
+ val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
+ addPackages(pkgWithDomains, pkgWithoutDomains)
+ }
+
+ assertThat(service.queryValidVerificationPackageNames())
+ .containsExactly(pkgWithDomains.getName())
+ }
+
+ @Test
+ fun getDomainVerificationInfoId() {
+ val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
+
+ val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
+ addPackages(pkgWithDomains, pkgWithoutDomains)
+ }
+
+ assertThat(service.getDomainVerificationInfoId(PKG_ONE)).isEqualTo(UUID_ONE)
+ assertThat(service.getDomainVerificationInfoId(PKG_TWO)).isEqualTo(UUID_TWO)
+
+ assertThat(service.getDomainVerificationInfoId("invalid.pkg.name")).isEqualTo(null)
+ }
+
+ @Test
+ fun getDomainVerificationInfo() {
+ val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
+
+ val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
+ addPackages(pkgWithDomains, pkgWithoutDomains)
+ }
+
+ val infoOne = service.getDomainVerificationInfo(pkgWithDomains.getName())
+ assertThat(infoOne).isNotNull()
+ assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId)
+ assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName())
+ assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DomainVerificationInfo.STATE_NO_RESPONSE,
+ DOMAIN_2 to DomainVerificationInfo.STATE_NO_RESPONSE,
+ ))
+
+ assertThat(service.getDomainVerificationInfo(pkgWithoutDomains.getName())).isNull()
+
+ assertFailsWith(PackageManager.NameNotFoundException::class) {
+ service.getDomainVerificationInfo("invalid.pkg.name")
+ }
+ }
+
+ @Test
+ fun setStatus() {
+ val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
+
+ val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2)
+ val service = makeService(map::get).apply { addPackages(pkg1, pkg2) }
+
+ assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_2), 1100))
+ .isEqualTo(DomainVerificationManager.STATUS_OK)
+
+ 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)
+
+ assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null,
+ 1100)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+
+ assertThat(service.setStatus(UUID_ONE, emptySet(), 1100))
+ .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+
+ assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100))
+ .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
+
+ 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)
+
+ map.clear()
+ assertFailsWith(PackageManager.NameNotFoundException::class){
+ service.setStatus(UUID_ONE, setOf(DOMAIN_1), 1100)
+ }
+ }
+
+ @Test
+ fun setDomainVerificationLinkHandlingAllowed() {
+ val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
+
+ val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2)
+ val service = makeService(map::get).apply { addPackages(pkg1, pkg2) }
+
+ service.setDomainVerificationLinkHandlingAllowed(PKG_ONE, false, 0);
+
+ // Should edit same package, same user
+ assertThat(service.getDomainVerificationUserState(PKG_ONE, 0)
+ ?.isLinkHandlingAllowed).isEqualTo(false)
+
+ // Shouldn't edit different user
+ assertThat(service.getDomainVerificationUserState(PKG_ONE, 1)
+ ?.isLinkHandlingAllowed).isEqualTo(true)
+
+ // Shouldn't edit different package
+ assertThat(service.getDomainVerificationUserState(PKG_TWO, 0)
+ ?.isLinkHandlingAllowed).isEqualTo(true)
+
+ assertFailsWith(PackageManager.NameNotFoundException::class){
+ service.setDomainVerificationLinkHandlingAllowed("invalid.pkg.name", false, 0);
+ }
+ }
+
+ @Test
+ fun setUserSelection() {
+ val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
+ val pkg3 = mockPkgSetting(PKG_THREE, UUID_THREE, listOf(DOMAIN_1, DOMAIN_2))
+
+ val map = mutableMapOf(
+ pkg1.getName() to pkg1,
+ pkg2.getName() to pkg2,
+ pkg3.getName() to pkg3
+ )
+ val service = makeService(map::get).apply { addPackages(pkg1, pkg2, pkg3) }
+
+ assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, 0))
+ .isEqualTo(DomainVerificationManager.STATUS_OK)
+
+ 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)
+
+ assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null,
+ true, 0)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+
+ assertThat(service.setUserSelection(UUID_ONE, emptySet(), true, 0))
+ .isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
+
+ assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0))
+ .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
+
+ assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), true, 0))
+ .isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
+
+ service.setStatus(UUID_ONE, setOf(DOMAIN_2), DomainVerificationInfo.STATE_SUCCESS)
+
+ assertThat(service.setUserSelection(UUID_THREE, setOf(DOMAIN_2), true, 0))
+ .isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE)
+
+ map.clear()
+ assertFailsWith(PackageManager.NameNotFoundException::class){
+ service.setUserSelection(UUID_ONE, setOf(DOMAIN_1), true, 0)
+ }
+ }
+
+ @Test
+ fun getDomainVerificationUserState() {
+ val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
+
+ val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
+ addPackages(pkgWithDomains, pkgWithoutDomains)
+ }
+
+ val infoOne = service.getDomainVerificationUserState(pkgWithDomains.getName(), 0)
+ assertThat(infoOne).isNotNull()
+ assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId)
+ assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName())
+ assertThat(infoOne.isLinkHandlingAllowed).isTrue()
+ assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ ))
+
+ val infoTwo = service.getDomainVerificationUserState(pkgWithoutDomains.getName(), 0)
+ assertThat(infoTwo).isNotNull()
+ assertThat(infoTwo!!.identifier).isEqualTo(pkgWithoutDomains.domainSetId)
+ assertThat(infoTwo.packageName).isEqualTo(pkgWithoutDomains.getName())
+ assertThat(infoOne.isLinkHandlingAllowed).isTrue()
+ assertThat(infoTwo.hostToStateMap).isEmpty()
+
+ assertFailsWith(PackageManager.NameNotFoundException::class) {
+ service.getDomainVerificationUserState("invalid.pkg.name", 0)
+ }
+ }
+
+ @Test
+ fun getOwnersForDomain() {
+ val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
+
+ val service = makeService(pkg1, pkg2).apply {
+ addPackages(pkg1, pkg2)
+ }
+
+ assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty()
+
+ service.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS)
+
+ service.setStatus(pkg2.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS)
+
+ service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0)
+
+ service.getOwnersForDomain(DOMAIN_1, 0).let {
+ assertThat(it).hasSize(2)
+ assertThat(it[0].packageName).isEqualTo(pkg1.getName())
+ assertThat(it[0].isOverrideable).isEqualTo(false)
+ assertThat(it[1].packageName).isEqualTo(pkg2.getName())
+ assertThat(it[1].isOverrideable).isEqualTo(false)
+ }
+
+ service.getOwnersForDomain(DOMAIN_2, 0).let {
+ assertThat(it).hasSize(1)
+ assertThat(it.single().packageName).isEqualTo(pkg1.getName())
+ assertThat(it.single().isOverrideable).isEqualTo(true)
+ }
+ assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
+
+ service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
+ service.getOwnersForDomain(DOMAIN_2, 0).let {
+ assertThat(it).hasSize(1)
+ assertThat(it.single().packageName).isEqualTo(pkg2.getName())
+ assertThat(it.single().isOverrideable).isEqualTo(true)
+ }
+ assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
+ }
+
+ private fun makeService(vararg pkgSettings: PackageSetting) =
+ makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } }
+
+ private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) =
+ DomainVerificationService(mockThrowOnUnmocked {
+ // Assume the test has every permission necessary
+ whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
+ whenever(checkPermission(anyString(), anyInt(), anyInt())) {
+ PackageManager.PERMISSION_GRANTED
+ }
+ }, mockThrowOnUnmocked {
+ whenever(linkedApps) { ArraySet<String>() }
+ }, mockThrowOnUnmocked {
+ whenever(isChangeEnabled(anyLong(), any())) { true }
+ }).apply {
+ setConnection(mockThrowOnUnmocked {
+ whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
+ whenever(doesUserExist(0)) { true }
+ whenever(doesUserExist(1)) { true }
+ whenever(scheduleWriteSettings())
+
+ // Need to provide an internal UID so some permission checks are ignored
+ whenever(callingUid) { Process.ROOT_UID }
+ whenever(callingUserId) { 0 }
+
+ whenever(getPackageSettingLocked(anyString())) {
+ pkgSettingFunction(arguments[0] as String)
+ }
+ whenever(getPackageLocked(anyString())) {
+ pkgSettingFunction(arguments[0] as String)?.getPkg()
+ }
+ })
+ }
+
+ private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf(
+ DOMAIN_1, DOMAIN_2
+ )) = mockThrowOnUnmocked<PackageSetting> {
+ val pkg = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { pkgName }
+ whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+
+ val activityList = listOf(
+ ParsedActivity().apply {
+ domains.forEach {
+ addIntent(
+ ParsedIntentInfo().apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority(it, null)
+ }
+ )
+ }
+ },
+ )
+
+ whenever(activities) { activityList }
+ }
+
+ whenever(getPkg()) { pkg }
+ whenever(getName()) { pkgName }
+ whenever(this.domainSetId) { domainSetId }
+ whenever(getInstantApp(anyInt())) { false }
+ whenever(firstInstallTime) { 0L }
+ }
+
+ fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) =
+ pkgSettings.forEach(::addPackage)
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
index 8c31c65..a5db3c5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
@@ -16,8 +16,8 @@
package com.android.server.pm.test.verify.domain
-import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationUserState
import com.android.server.pm.verify.domain.DomainVerificationPersistence
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
new file mode 100644
index 0000000..fe3672d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -0,0 +1,425 @@
+/*
+ * 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.test.verify.domain
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
+import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS
+import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationState
+import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE
+import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED
+import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED
+import android.os.Build
+import android.os.PatternMatcher
+import android.os.Process
+import android.util.ArraySet
+import android.util.Xml
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.verify.domain.DomainVerificationService
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import java.util.UUID
+
+class DomainVerificationPackageTest {
+
+ companion object {
+ private const val PKG_ONE = "com.test.one"
+ private const val PKG_TWO = "com.test.two"
+ private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
+ private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
+
+ private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName
+ private val DOMAIN_1 = "one.$DOMAIN_BASE"
+ private val DOMAIN_2 = "two.$DOMAIN_BASE"
+ private val DOMAIN_3 = "three.$DOMAIN_BASE"
+ private val DOMAIN_4 = "four.$DOMAIN_BASE"
+
+ private const val USER_ID = 0
+ }
+
+ private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE)
+ private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO)
+
+ @Test
+ fun addPackageFirstTime() {
+ val service = makeService(pkg1, pkg2)
+ service.addPackage(pkg1)
+ val info = service.getInfo(pkg1.getName())
+ assertThat(info.packageName).isEqualTo(pkg1.getName())
+ assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
+ assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_NO_RESPONSE,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+
+ val userState = service.getUserState(pkg1.getName())
+ assertThat(userState.packageName).isEqualTo(pkg1.getName())
+ assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
+ assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
+ assertThat(userState.user.identifier).isEqualTo(USER_ID)
+ assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ ))
+
+ assertThat(service.queryValidVerificationPackageNames())
+ .containsExactly(pkg1.getName())
+ }
+
+ @Test
+ fun addPackageActive() {
+ // language=XML
+ val xml = """
+ <?xml?>
+ <domain-verifications>
+ <active>
+ <package-state
+ packageName="${pkg1.getName()}"
+ id="${pkg1.domainSetId}"
+ >
+ <state>
+ <domain name="$DOMAIN_1" state="$STATE_SUCCESS"/>
+ </state>
+ <user-states>
+ <user-state userId="$USER_ID" allowLinkHandling="false">
+ <enabled-hosts>
+ <host name="$DOMAIN_2"/>
+ </enabled-hosts>
+ </user-state>
+ </user-states>
+ </package-state>
+ </active>
+ </domain-verifications>
+ """.trimIndent()
+
+ val service = makeService(pkg1, pkg2)
+ xml.byteInputStream().use {
+ service.readSettings(Xml.resolvePullParser(it))
+ }
+
+ service.addPackage(pkg1)
+
+ val info = service.getInfo(pkg1.getName())
+ assertThat(info.packageName).isEqualTo(pkg1.getName())
+ assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
+ assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_SUCCESS,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+
+ val userState = service.getUserState(pkg1.getName())
+ assertThat(userState.packageName).isEqualTo(pkg1.getName())
+ assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
+ assertThat(userState.isLinkHandlingAllowed).isEqualTo(false)
+ assertThat(userState.user.identifier).isEqualTo(USER_ID)
+ assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ ))
+
+ assertThat(service.queryValidVerificationPackageNames())
+ .containsExactly(pkg1.getName())
+ }
+
+ @Test
+ fun migratePackageDropDomain() {
+ val pkgName = PKG_ONE
+ val pkgBefore = mockPkgSetting(pkgName, UUID_ONE,
+ listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4))
+ val pkgAfter = mockPkgSetting(pkgName, UUID_TWO,
+ listOf(DOMAIN_1, DOMAIN_2))
+
+ // Test 4 domains:
+ // 1 will be approved and preserved, 2 will be selected and preserved,
+ // 3 will be denied and dropped, 4 will be selected and dropped
+
+ val map = mutableMapOf<String, PackageSetting>()
+ val service = makeService { map[it] }
+ service.addPackage(pkgBefore)
+
+ // Only insert the package after addPackage call to ensure the service doesn't access
+ // a live package inside the addPackage logic. It should only use the provided input.
+ map[pkgName] = pkgBefore
+
+ // To test the approve/denial states, use the internal methods for this variant
+ service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_APPROVED,
+ ArraySet(setOf(DOMAIN_1)))
+ service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_DENIED,
+ ArraySet(setOf(DOMAIN_3)))
+ service.setUserSelection(
+ UUID_ONE, setOf(DOMAIN_2, DOMAIN_4), true, USER_ID)
+
+ // Check the verifier cannot change the shell approve/deny states
+ service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_3), STATE_SUCCESS)
+
+ assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_UNMODIFIABLE,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ DOMAIN_3 to STATE_UNMODIFIABLE,
+ DOMAIN_4 to STATE_NO_RESPONSE,
+ ))
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ DOMAIN_3 to DOMAIN_STATE_NONE,
+ DOMAIN_4 to DOMAIN_STATE_SELECTED,
+ ))
+
+ // Now remove the package because migrateState shouldn't use it either
+ map.remove(pkgName)
+
+ map[pkgName] = pkgAfter
+
+ service.migrateState(pkgBefore, pkgAfter)
+
+ assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_UNMODIFIABLE,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ ))
+ assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+ }
+
+ @Test
+ fun migratePackageDropAll() {
+ val pkgName = PKG_ONE
+ val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
+ val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, emptyList())
+
+ val map = mutableMapOf<String, PackageSetting>()
+ val service = makeService { map[it] }
+ service.addPackage(pkgBefore)
+
+ // Only insert the package after addPackage call to ensure the service doesn't access
+ // a live package inside the addPackage logic. It should only use the provided input.
+ map[pkgName] = pkgBefore
+
+ assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_NO_RESPONSE,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ ))
+ assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+
+ // Now remove the package because migrateState shouldn't use it either
+ map.remove(pkgName)
+
+ service.migrateState(pkgBefore, pkgAfter)
+
+ map[pkgName] = pkgAfter
+
+ assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
+ .isNotEqualTo(DomainVerificationManager.STATUS_OK)
+
+ assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
+ .isNotEqualTo(DomainVerificationManager.STATUS_OK)
+
+ assertThat(service.getDomainVerificationInfo(pkgName)).isNull()
+ assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty()
+ assertThat(service.queryValidVerificationPackageNames()).isEmpty()
+ }
+
+ @Test
+ fun migratePackageAddDomain() {
+ val pkgName = PKG_ONE
+ val pkgBefore = mockPkgSetting(pkgName, UUID_ONE,
+ listOf(DOMAIN_1, DOMAIN_2))
+ val pkgAfter = mockPkgSetting(pkgName, UUID_TWO,
+ listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
+
+ // Test 3 domains:
+ // 1 will be verified and preserved, 2 will be selected and preserved,
+ // 3 will be new and default
+
+ val map = mutableMapOf<String, PackageSetting>()
+ val service = makeService { map[it] }
+ service.addPackage(pkgBefore)
+
+ // Only insert the package after addPackage call to ensure the service doesn't access
+ // a live package inside the addPackage logic. It should only use the provided input.
+ map[pkgName] = pkgBefore
+
+ service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)
+ service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)
+
+ assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_SUCCESS,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ ))
+
+ // Now remove the package because migrateState shouldn't use it either
+ map.remove(pkgName)
+
+ service.migrateState(pkgBefore, pkgAfter)
+
+ map[pkgName] = pkgAfter
+
+ assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_SUCCESS,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ DOMAIN_3 to STATE_NO_RESPONSE,
+ ))
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ DOMAIN_3 to DOMAIN_STATE_NONE,
+ ))
+ assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+ }
+
+ @Test
+ fun migratePackageAddAll() {
+ val pkgName = PKG_ONE
+ val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, emptyList())
+ val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
+
+ val map = mutableMapOf<String, PackageSetting>()
+ val service = makeService { map[it] }
+ service.addPackage(pkgBefore)
+
+ // Only insert the package after addPackage call to ensure the service doesn't access
+ // a live package inside the addPackage logic. It should only use the provided input.
+ map[pkgName] = pkgBefore
+
+ assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
+ .isNotEqualTo(DomainVerificationManager.STATUS_OK)
+
+ assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
+ .isNotEqualTo(DomainVerificationManager.STATUS_OK)
+
+ assertThat(service.getDomainVerificationInfo(pkgName)).isNull()
+ assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty()
+ assertThat(service.queryValidVerificationPackageNames()).isEmpty()
+
+ // Now remove the package because migrateState shouldn't use it either
+ map.remove(pkgName)
+
+ service.migrateState(pkgBefore, pkgAfter)
+
+ map[pkgName] = pkgAfter
+
+ assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_NO_RESPONSE,
+ DOMAIN_2 to STATE_NO_RESPONSE,
+ ))
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_NONE,
+ DOMAIN_2 to DOMAIN_STATE_NONE,
+ ))
+ assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+ }
+
+ private fun DomainVerificationService.getInfo(pkgName: String) =
+ getDomainVerificationInfo(pkgName)
+ .also { assertThat(it).isNotNull() }!!
+
+ private fun DomainVerificationService.getUserState(pkgName: String) =
+ getDomainVerificationUserState(pkgName, USER_ID)
+ .also { assertThat(it).isNotNull() }!!
+
+ private fun makeService(vararg pkgSettings: PackageSetting) =
+ makeService { pkgName -> pkgSettings.find { pkgName == it.getName()} }
+
+ private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) =
+ DomainVerificationService(mockThrowOnUnmocked {
+ // Assume the test has every permission necessary
+ whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
+ whenever(checkPermission(anyString(), anyInt(), anyInt())) {
+ PackageManager.PERMISSION_GRANTED
+ }
+ }, mockThrowOnUnmocked {
+ whenever(linkedApps) { ArraySet<String>() }
+ }, mockThrowOnUnmocked {
+ whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true }
+ }).apply {
+ setConnection(mockThrowOnUnmocked {
+ whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
+ whenever(doesUserExist(0)) { true }
+ whenever(doesUserExist(1)) { true }
+ whenever(scheduleWriteSettings())
+
+ // Need to provide an internal UID so some permission checks are ignored
+ whenever(callingUid) { Process.ROOT_UID }
+ whenever(callingUserId) { 0 }
+
+ whenever(getPackageSettingLocked(anyString())) {
+ pkgSettingFunction(arguments[0] as String)!!
+ }
+ whenever(getPackageLocked(anyString())) {
+ pkgSettingFunction(arguments[0] as String)!!.getPkg()
+ }
+ })
+ }
+
+ private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf(
+ DOMAIN_1, DOMAIN_2
+ )) = mockThrowOnUnmocked<PackageSetting> {
+ val pkg = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { pkgName }
+ whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+
+ val activityList = listOf(
+ ParsedActivity().apply {
+ domains.forEach {
+ addIntent(
+ ParsedIntentInfo().apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority(it, null)
+ }
+ )
+ }
+ },
+ )
+
+ whenever(activities) { activityList }
+ }
+
+ whenever(getPkg()) { pkg }
+ whenever(getName()) { pkgName }
+ whenever(this.domainSetId) { domainSetId }
+ whenever(getInstantApp(anyInt())) { false }
+ whenever(firstInstallTime) { 0L }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index 6597577..f8fda12 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -16,7 +16,7 @@
package com.android.server.pm.test.verify.domain
-import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationState
import android.util.ArrayMap
import android.util.TypedXmlPullParser
import android.util.TypedXmlSerializer
@@ -117,11 +117,11 @@
@Test
fun readMalformed() {
val stateZero = mockEmptyPkgState(0).apply {
- stateMap["example.com"] = DomainVerificationManager.STATE_SUCCESS
- stateMap["example.org"] = DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED
+ stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS
+ stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED
// A domain without a written state falls back to default
- stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE
+ stateMap["missing-state.com"] = DomainVerificationState.STATE_NO_RESPONSE
userStates[1] = DomainVerificationInternalUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org"))
@@ -159,9 +159,9 @@
>
<state>
<domain name="example.com" state="${
- DomainVerificationManager.STATE_SUCCESS}"/>
+ DomainVerificationState.STATE_SUCCESS}"/>
<domain name="example.org" state="${
- DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
+ DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED}"/>
<not-domain name="not-domain.com" state="1"/>
<domain name="missing-state.com"/>
</state>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
index 91e5bec..a9b77ea 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
@@ -21,20 +21,20 @@
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationRequest
-import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationState
import android.os.Bundle
import android.os.UserHandle
import android.util.ArraySet
import com.android.server.DeviceIdleInternal
+import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationCollector
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2
-import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
@@ -106,20 +106,22 @@
when (val pkgName = arguments[0] as String) {
TEST_PKG_NAME_TARGET_ONE -> DomainVerificationInfo(
TEST_UUID_ONE, pkgName, mapOf(
- "example1.com" to DomainVerificationManager.STATE_NO_RESPONSE,
- "example2.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ "example1.com" to DomainVerificationInfo.STATE_NO_RESPONSE,
+ "example2.com" to DomainVerificationInfo.STATE_NO_RESPONSE
)
)
TEST_PKG_NAME_TARGET_TWO -> DomainVerificationInfo(
TEST_UUID_TWO, pkgName, mapOf(
- "example3.com" to DomainVerificationManager.STATE_NO_RESPONSE,
- "example4.com" to DomainVerificationManager.STATE_NO_RESPONSE
+ "example3.com" to DomainVerificationInfo.STATE_NO_RESPONSE,
+ "example4.com" to DomainVerificationInfo.STATE_NO_RESPONSE
)
)
else -> throw IllegalArgumentException("Unexpected package name $pkgName")
}
}
- whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt()))
+ whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt())) {
+ DomainVerificationManager.STATUS_OK
+ }
}
collector = mockThrowOnUnmocked {
whenever(collectValidAutoVerifyDomains(any())) {
@@ -316,7 +318,7 @@
eq(TEST_CALLING_UID_ACCEPT),
idCaptor.capture(),
hostCaptor.capture(),
- eq(DomainVerificationManager.STATE_SUCCESS)
+ eq(DomainVerificationState.STATE_SUCCESS)
)
assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt
new file mode 100644
index 0000000..6859b11
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationServiceUtil.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.verify.domain
+
+import android.annotation.UserIdInt
+import com.android.server.pm.verify.domain.DomainVerificationService
+import java.util.UUID
+
+fun DomainVerificationService.setStatus(domainSetId: UUID, domains: Set<String>, state: Int) =
+ setDomainVerificationStatus(domainSetId, domains.toMutableSet(), state)
+
+fun DomainVerificationService.setUserSelection(
+ domainSetId: UUID,
+ domains: Set<String>,
+ enabled: Boolean,
+ @UserIdInt userId: Int
+) = setDomainVerificationUserSelection(domainSetId, domains.toMutableSet(), enabled, userId)
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 0d8f275..377bae1 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
@@ -20,28 +20,27 @@
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
-import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.verify.domain.DomainVerificationState
import android.os.Build
import android.os.Process
import android.util.ArraySet
import android.util.SparseArray
import com.android.server.pm.PackageSetting
+import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
-import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.spyThrowOnUnmocked
import com.android.server.testutils.whenever
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import org.mockito.Mockito
+import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
-import org.mockito.Mockito.any
import org.mockito.Mockito.anyString
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
@@ -129,13 +128,13 @@
setDomainVerificationStatus(
TEST_UUID,
setOf("example.com"),
- DomainVerificationManager.STATE_SUCCESS
+ DomainVerificationState.STATE_SUCCESS
)
},
service("setStatusInternalPackageName") {
setDomainVerificationStatusInternal(
TEST_PKG,
- DomainVerificationManager.STATE_SUCCESS,
+ DomainVerificationState.STATE_SUCCESS,
ArraySet(setOf("example.com"))
)
},
@@ -144,7 +143,7 @@
TEST_UID,
TEST_UUID,
setOf("example.com"),
- DomainVerificationManager.STATE_SUCCESS
+ DomainVerificationState.STATE_SUCCESS
)
},
service("setLinkHandlingAllowedUserId") {
@@ -266,5 +265,7 @@
// This doesn't check for visibility; that's done in the enforcer test
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
+ whenever(doesUserExist(0)) { true }
+ whenever(doesUserExist(10)) { true }
}
}
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 0576125..44c1b8f 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
@@ -21,6 +21,7 @@
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationState
import android.content.pm.verify.domain.DomainVerificationUserState
import android.os.Build
import android.os.PatternMatcher
@@ -74,6 +75,8 @@
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
+ whenever(doesUserExist(0)) { true }
+ whenever(doesUserExist(1)) { true }
whenever(scheduleWriteSettings())
// Need to provide an internal UID so some permission checks are ignored
@@ -154,19 +157,20 @@
.containsExactly(PKG_TWO)
}
- @Test(expected = IllegalArgumentException::class)
+ @Test
fun anotherPackageTakeoverFailure() {
val service = makeService()
// Verify 1 to give it a higher approval level
service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
- DomainVerificationManager.STATE_SUCCESS)
+ DomainVerificationState.STATE_SUCCESS)
assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName })
.containsExactly(PKG_ONE)
// Attempt override by package 2
- service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID)
+ assertThat(service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true,
+ USER_ID)).isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE)
}
private fun DomainVerificationService.stateFor(pkgName: String, host: String) =
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index f2e85a7..28940b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -27,6 +27,7 @@
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.WINDOW_EXACT;
import static android.app.AlarmManager.WINDOW_HEURISTIC;
+import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
@@ -47,17 +48,20 @@
import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
+import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_CRASH_NON_CLOCK_APPS;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -71,6 +75,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -81,6 +86,7 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import android.Manifest;
import android.app.ActivityManager;
@@ -408,7 +414,7 @@
ArgumentCaptor<IAppOpsCallback> appOpsCallbackCaptor = ArgumentCaptor.forClass(
IAppOpsCallback.class);
try {
- verify(mIAppOpsService).startWatchingMode(eq(AppOpsManager.OP_SCHEDULE_EXACT_ALARM),
+ verify(mIAppOpsService).startWatchingMode(eq(OP_SCHEDULE_EXACT_ALARM),
isNull(), appOpsCallbackCaptor.capture());
} catch (RemoteException e) {
// Not expected on a mock.
@@ -445,12 +451,12 @@
private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
int flags, int callingUid) {
- setTestAlarm(type, triggerTime, operation, interval, flags, callingUid, null);
+ setTestAlarm(type, triggerTime, 0, operation, interval, flags, callingUid, null);
}
- private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
- int flags, int callingUid, Bundle idleOptions) {
- mService.setImpl(type, triggerTime, WINDOW_EXACT, interval, operation, null, "test", flags,
+ private void setTestAlarm(int type, long triggerTime, long windowLength,
+ PendingIntent operation, long interval, int flags, int callingUid, Bundle idleOptions) {
+ mService.setImpl(type, triggerTime, windowLength, interval, operation, null, "test", flags,
null, null, callingUid, TEST_CALLING_PACKAGE, idleOptions);
}
@@ -572,6 +578,7 @@
setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW, 35);
setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 40);
setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 45);
+ setDeviceConfigLong(KEY_MIN_WINDOW, 50);
assertEquals(5, mService.mConstants.MIN_FUTURITY);
assertEquals(10, mService.mConstants.MIN_INTERVAL);
assertEquals(15, mService.mConstants.MAX_INTERVAL);
@@ -581,6 +588,7 @@
assertEquals(35, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW);
assertEquals(40, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
assertEquals(45, mService.mConstants.LISTENER_TIMEOUT);
+ assertEquals(50, mService.mConstants.MIN_WINDOW);
}
@Test
@@ -1644,6 +1652,10 @@
getNewMockPendingIntent(), null, null, null,
mock(AlarmManager.AlarmClockInfo.class));
+ // exact
+ mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+ 0, getNewMockPendingIntent(), null, null, null, null);
+
// exact, allow-while-idle
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null);
@@ -1658,6 +1670,22 @@
}
@Test
+ public void exactBinderCallWhenChangeDisabled() throws Exception {
+ doReturn(false).when(
+ () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+ anyString(), any(UserHandle.class)));
+
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+ 0, alarmPi, null, null, null, null);
+
+ verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+ eq(alarmPi), isNull(), isNull(),
+ eq(FLAG_STANDALONE), isNull(), isNull(),
+ eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull());
+ }
+
+ @Test
public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception {
doReturn(false).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
@@ -1746,6 +1774,86 @@
}
@Test
+ public void alarmClockBinderCallWithoutPermission() throws RemoteException {
+ setDeviceConfigBoolean(KEY_CRASH_NON_CLOCK_APPS, true);
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+ anyString(), any(UserHandle.class)));
+
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM));
+ when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
+
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
+ try {
+ mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
+ alarmPi, null, null, null, alarmClock);
+ fail("alarm clock binder call succeeded without permission");
+ } catch (SecurityException se) {
+ // Expected.
+ }
+
+ verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM));
+ verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+ }
+
+ @Test
+ public void exactBinderCallWithPermission() throws RemoteException {
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+ anyString(), any(UserHandle.class)));
+
+ // Permission check is granted by default by the mock.
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+ 0, alarmPi, null, null, null, null);
+
+ verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM));
+ verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+ eq(alarmPi), isNull(), isNull(),
+ eq(FLAG_STANDALONE), isNull(), isNull(),
+ eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+
+ final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+ final int type = idleOptions.getTemporaryAppAllowlistType();
+ assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+ }
+
+ @Test
+ public void exactBinderCallWithAllowlist() throws RemoteException {
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+ anyString(), any(UserHandle.class)));
+ // If permission is denied, only then allowlist will be checked.
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM));
+ when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
+
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+ 0, alarmPi, null, null, null, null);
+
+ verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM));
+ verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+ eq(alarmPi), isNull(), isNull(),
+ eq(FLAG_STANDALONE), isNull(), isNull(),
+ eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
+ System.out.println("what got captured: " + bundleCaptor.getValue());
+ }
+
+ @Test
public void exactAllowWhileIdleBinderCallWithPermission() throws RemoteException {
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
@@ -1798,48 +1906,57 @@
final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
final int type = idleOptions.getTemporaryAppAllowlistType();
- assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+ // App is on power allowlist, doesn't need explicit FGS grant in broadcast options.
+ assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type);
}
@Test
- public void inexactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
+ public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
+ setDeviceConfigBoolean(KEY_CRASH_NON_CLOCK_APPS, true);
doReturn(true).when(
() -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
anyString(), any(UserHandle.class)));
- when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
- final PendingIntent alarmPi = getNewMockPendingIntent();
- mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
- FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
-
- verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
- Manifest.permission.SCHEDULE_EXACT_ALARM), never());
- verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
-
- final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
- eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(),
- isNull(), eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture());
-
- final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
- final int type = idleOptions.getTemporaryAppAllowlistType();
- assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
- }
-
- @Test
- public void inexactAllowWhileIdleBinderCallWithoutAllowlist() throws RemoteException {
- doReturn(true).when(
- () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
- anyString(), any(UserHandle.class)));
-
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM));
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
+
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ try {
+ mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+ 0, alarmPi, null, null, null, null);
+ fail("exact binder call succeeded without permission");
+ } catch (SecurityException se) {
+ // Expected.
+ }
+ try {
+ mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+ FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+ fail("exact, allow-while-idle binder call succeeded without permission");
+ } catch (SecurityException se) {
+ // Expected.
+ }
+ verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM), times(2));
+ verify(mDeviceIdleInternal, times(2)).isAppOnWhitelist(anyInt());
+ }
+
+ @Test
+ public void inexactAllowWhileIdleBinderCall() throws RemoteException {
+ // Both permission and power exemption status don't matter for these alarms.
+ // We only want to test that the flags and idleOptions are correct.
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+ anyString(), any(UserHandle.class)));
+
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
verify(() -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
Manifest.permission.SCHEDULE_EXACT_ALARM), never());
- verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(Process.myUid()));
+ verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L),
@@ -1852,13 +1969,104 @@
}
@Test
+ public void binderCallWithUserAllowlist() throws RemoteException {
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+ anyString(), any(UserHandle.class)));
+
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ () -> PermissionChecker.checkCallingOrSelfPermissionForPreflight(mMockContext,
+ Manifest.permission.SCHEDULE_EXACT_ALARM));
+ when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
+ when(mAppStateTracker.isUidPowerSaveUserExempt(Process.myUid())).thenReturn(true);
+
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+ FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
+
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+ eq(alarmPi), isNull(), isNull(),
+ eq(FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_STANDALONE), isNull(), isNull(),
+ eq(Process.myUid()), eq(TEST_CALLING_PACKAGE), isNull());
+ }
+
+ @Test
+ public void minWindow() {
+ final long minWindow = 73;
+ setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
+
+ // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC.
+ for (int window = 1; window <= minWindow; window++) {
+ final PendingIntent pi = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME, 0, window, pi, 0, 0, TEST_CALLING_UID, null);
+
+ assertEquals(1, mService.mAlarmStore.size());
+ final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
+ assertEquals(minWindow, a.windowLength);
+ }
+ }
+
+ @Test
+ public void opScheduleExactAlarmRevoked() throws Exception {
+ when(mIAppOpsService.checkOperation(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE)).thenReturn(AppOpsManager.MODE_ERRORED);
+ mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
+ verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE);
+ }
+
+ @Test
+ public void removeExactAlarmsOnPermissionRevoked() {
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(eq(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION),
+ anyString(), any(UserHandle.class)));
+
+ // basic exact alarm
+ setTestAlarm(ELAPSED_REALTIME, 0, 0, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID,
+ null);
+ // exact and allow-while-idle alarm
+ setTestAlarm(ELAPSED_REALTIME, 0, 0, getNewMockPendingIntent(), 0, FLAG_ALLOW_WHILE_IDLE,
+ TEST_CALLING_UID, null);
+ // alarm clock
+ setWakeFromIdle(RTC_WAKEUP, 0, getNewMockPendingIntent());
+
+ final PendingIntent inexact = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME, 0, 10, inexact, 0, 0, TEST_CALLING_UID, null);
+
+ final PendingIntent inexactAwi = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME, 0, 10, inexactAwi, 0, FLAG_ALLOW_WHILE_IDLE,
+ TEST_CALLING_UID, null);
+
+ final PendingIntent exactButDifferentUid = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME, 0, 0, exactButDifferentUid, 0, 0, TEST_CALLING_UID + 5,
+ null);
+ assertEquals(6, mService.mAlarmStore.size());
+
+ mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+
+ final ArrayList<Alarm> remaining = mService.mAlarmStore.asList();
+ assertEquals(3, remaining.size());
+ assertTrue("Basic inexact alarm removed",
+ remaining.removeIf(a -> a.matches(inexact, null)));
+ assertTrue("Inexact allow-while-idle alarm removed",
+ remaining.removeIf(a -> a.matches(inexactAwi, null)));
+ assertTrue("Alarm from different uid removed",
+ remaining.removeIf(a -> a.matches(exactButDifferentUid, null)));
+
+ // Mock should return false by default.
+ verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID));
+ }
+
+ @Test
public void idleOptionsSentOnExpiration() throws Exception {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();
final Bundle idleOptions = new Bundle();
idleOptions.putChar("TEST_CHAR_KEY", 'x');
idleOptions.putInt("TEST_INT_KEY", 53);
- setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi, 0, 0, TEST_CALLING_UID,
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, 0, alarmPi, 0, 0, TEST_CALLING_UID,
idleOptions);
mNowElapsedTest = mTestTimer.getElapsed();
@@ -1885,6 +2093,7 @@
assertTrue(i + "th PendingIntent missing: ",
alarmsBefore.removeIf(a -> a.matches(pi, null)));
}
+ assertEquals(BatchingAlarmStore.TAG, mService.mAlarmStore.getName());
setDeviceConfigBoolean(KEY_LAZY_BATCHING, true);
@@ -1895,6 +2104,7 @@
assertTrue(i + "th PendingIntent missing: ",
alarmsAfter.removeIf(a -> a.matches(pi, null)));
}
+ assertEquals(LazyAlarmStore.TAG, mService.mAlarmStore.getName());
}
@After
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 29db740..281c1aa 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
@@ -75,6 +75,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.LargeTest;
import android.provider.DeviceConfig;
import android.util.SparseBooleanArray;
@@ -286,14 +287,20 @@
doReturn(procState).when(mActivityMangerInternal).getUidProcessState(uid);
SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
spyOn(foregroundUids);
+ final boolean contained = foregroundUids.get(uid);
mUidObserver.onUidStateChanged(uid, procState, 0,
ActivityManager.PROCESS_CAPABILITY_NONE);
if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
- .put(eq(uid), eq(true));
+ if (!contained) {
+ verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
+ .put(eq(uid), eq(true));
+ }
assertTrue(foregroundUids.get(uid));
} else {
- verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid));
+ if (contained) {
+ verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
+ .delete(eq(uid));
+ }
assertFalse(foregroundUids.get(uid));
}
waitForNonDelayedMessagesProcessed();
@@ -1880,6 +1887,190 @@
}
@Test
+ public void testIsWithinEJQuotaLocked_NeverApp() {
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1);
+ setStandbyBucket(NEVER_INDEX, js);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
+ @Test
+ public void testIsWithinEJQuotaLocked_Charging() {
+ setCharging();
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_Charging", 1);
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
+ @Test
+ public void testIsWithinEJQuotaLocked_UnderDuration() {
+ setDischarging();
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_UnderDuration", 1);
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
+ @Test
+ public void testIsWithinEJQuotaLocked_OverDuration() {
+ setDischarging();
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_OverDuration", 1);
+ setStandbyBucket(FREQUENT_INDEX, js);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
+ @Test
+ public void testIsWithinEJQuotaLocked_TimingSession() {
+ setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 8 * MINUTE_IN_MILLIS);
+
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TimingSession", 1);
+ for (int i = 0; i < 25; ++i) {
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS,
+ 2), true);
+
+ synchronized (mQuotaController.mLock) {
+ setStandbyBucket(ACTIVE_INDEX, js);
+ assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions",
+ i < 19, mQuotaController.isWithinEJQuotaLocked(js));
+
+ setStandbyBucket(WORKING_INDEX, js);
+ assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions",
+ i < 14, mQuotaController.isWithinEJQuotaLocked(js));
+
+ setStandbyBucket(FREQUENT_INDEX, js);
+ assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions",
+ i < 12, mQuotaController.isWithinEJQuotaLocked(js));
+
+ setStandbyBucket(RARE_INDEX, js);
+ assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions",
+ i < 9, mQuotaController.isWithinEJQuotaLocked(js));
+
+ setStandbyBucket(RESTRICTED_INDEX, js);
+ assertEquals("Restricted has incorrect quota status with " + (i + 1) + " sessions",
+ i < 7, mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+ }
+
+ /**
+ * Tests that Timers properly track sessions when an app is added and removed from the temp
+ * allowlist.
+ */
+ @Test
+ public void testIsWithinEJQuotaLocked_TempAllowlisting() {
+ setDischarging();
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting", 1);
+ setStandbyBucket(FREQUENT_INDEX, js);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ // Apps on the temp allowlist should be able to schedule & start EJs, even if they're out
+ // of quota (as long as they are in the temp allowlist grace period).
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ // Still in grace period
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(6 * SECOND_IN_MILLIS);
+ // Out of grace period.
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
+ /**
+ * Tests that Timers properly track sessions when an app becomes top and is closed.
+ */
+ @Test
+ public void testIsWithinEJQuotaLocked_TopApp() {
+ setDischarging();
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TopApp", 1);
+ setStandbyBucket(FREQUENT_INDEX, js);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ // Apps on top should be able to schedule & start EJs, even if they're out
+ // of quota (as long as they are in the top grace period).
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ // Still in grace period
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(6 * SECOND_IN_MILLIS);
+ // Out of grace period.
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
+ @Test
public void testMaybeScheduleCleanupAlarmLocked() {
// No sessions saved yet.
synchronized (mQuotaController.mLock) {
@@ -2519,8 +2710,9 @@
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 87 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 86 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 85 * SECOND_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS,
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS,
84 * SECOND_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS);
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
@@ -2559,7 +2751,8 @@
assertEquals(87 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
assertEquals(86 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
- assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJTempAllowlistGracePeriodMs());
+ assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
+ assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
}
@Test
@@ -2599,7 +2792,8 @@
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1);
- setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, -1);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(0, mQuotaController.getInQuotaBufferMs());
@@ -2635,7 +2829,8 @@
assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
assertEquals(5 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs());
- assertEquals(0, mQuotaController.getEJTempAllowlistGracePeriodMs());
+ assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs());
+ assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs());
// Invalid configurations.
// In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
@@ -2669,7 +2864,8 @@
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS);
- setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
@@ -2695,7 +2891,8 @@
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
- assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJTempAllowlistGracePeriodMs());
+ assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
+ assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
}
/** Tests that TimingSessions aren't saved when the device is charging. */
@@ -3295,7 +3492,7 @@
setDischarging();
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
- setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, gracePeriodMs);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
Handler handler = mQuotaController.getHandler();
spyOn(handler);
@@ -3881,6 +4078,55 @@
}
@Test
+ public void testGetRemainingEJExecutionTimeLocked_IncrementalTimingSessions() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS);
+
+ for (int i = 1; i <= 25; ++i) {
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS,
+ 2), true);
+
+ synchronized (mQuotaController.mLock) {
+ setStandbyBucket(ACTIVE_INDEX);
+ assertEquals("Active has incorrect remaining EJ time with " + i + " sessions",
+ (20 - i) * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingEJExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ setStandbyBucket(WORKING_INDEX);
+ assertEquals("Working has incorrect remaining EJ time with " + i + " sessions",
+ (15 - i) * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingEJExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ setStandbyBucket(FREQUENT_INDEX);
+ assertEquals("Frequent has incorrect remaining EJ time with " + i + " sessions",
+ (13 - i) * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingEJExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ setStandbyBucket(RARE_INDEX);
+ assertEquals("Rare has incorrect remaining EJ time with " + i + " sessions",
+ (10 - i) * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingEJExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ setStandbyBucket(RESTRICTED_INDEX);
+ assertEquals("Restricted has incorrect remaining EJ time with " + i + " sessions",
+ (5 - i) * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingEJExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+ }
+
+ @Test
public void testGetTimeUntilEJQuotaConsumedLocked_NoHistory() {
final long[] limits = mQuotaController.getEJLimitsMs();
for (int i = 0; i < limits.length; ++i) {
@@ -4723,6 +4969,7 @@
@Test
public void testEJTimerTracking_TopAndNonTop() {
setDischarging();
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
JobStatus jobBg1 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 1);
JobStatus jobBg2 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 2);
@@ -4845,7 +5092,7 @@
setDischarging();
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
- setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, gracePeriodMs);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
Handler handler = mQuotaController.getHandler();
spyOn(handler);
@@ -4947,11 +5194,158 @@
}
/**
+ * Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps.
+ */
+ @Test
+ @LargeTest
+ public void testEJTimerTracking_TopAndTempAllowlisting() throws Exception {
+ setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 5 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ JobStatus job1 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 1);
+ JobStatus job2 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 2);
+ JobStatus job3 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 3);
+ JobStatus job4 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 4);
+ JobStatus job5 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 5);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job1, null);
+ }
+ assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ List<TimingSession> expected = new ArrayList<>();
+
+ // Case 1: job starts in TA grace period then app becomes TOP
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ advanceElapsedClock(gracePeriodMs / 2);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job1);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(gracePeriodMs);
+ // Wait for the grace period to expire so the handler can process the message.
+ Thread.sleep(gracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
+ }
+ assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(gracePeriodMs);
+
+ // Case 2: job starts in TOP grace period then is TAed
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ advanceElapsedClock(gracePeriodMs / 2);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job2, null);
+ mQuotaController.prepareForExecutionLocked(job2);
+ }
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ advanceElapsedClock(gracePeriodMs);
+ // Wait for the grace period to expire so the handler can process the message.
+ Thread.sleep(gracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+ }
+ assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(gracePeriodMs);
+
+ // Case 3: job starts in TA grace period then app becomes TOP; job ends after TOP grace
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ advanceElapsedClock(gracePeriodMs / 2);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job3, null);
+ mQuotaController.prepareForExecutionLocked(job3);
+ }
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(gracePeriodMs);
+ // Wait for the grace period to expire so the handler can process the message.
+ Thread.sleep(gracePeriodMs);
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ advanceElapsedClock(gracePeriodMs);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Wait for the grace period to expire so the handler can process the message.
+ Thread.sleep(2 * gracePeriodMs);
+ advanceElapsedClock(gracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job3, job3, true);
+ }
+ expected.add(createTimingSession(start, gracePeriodMs, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(gracePeriodMs);
+
+ // Case 4: job starts in TOP grace period then app becomes TAed; job ends after TA grace
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ advanceElapsedClock(gracePeriodMs / 2);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job4, null);
+ mQuotaController.prepareForExecutionLocked(job4);
+ }
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ advanceElapsedClock(gracePeriodMs);
+ // Wait for the grace period to expire so the handler can process the message.
+ Thread.sleep(gracePeriodMs);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ advanceElapsedClock(gracePeriodMs);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Wait for the grace period to expire so the handler can process the message.
+ Thread.sleep(2 * gracePeriodMs);
+ advanceElapsedClock(gracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
+ }
+ expected.add(createTimingSession(start, gracePeriodMs, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(gracePeriodMs);
+
+ // Case 5: job starts during overlapping grace period
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ advanceElapsedClock(gracePeriodMs - SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job5, null);
+ mQuotaController.prepareForExecutionLocked(job5);
+ }
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Wait for the grace period to expire so the handler can process the message.
+ Thread.sleep(2 * gracePeriodMs);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ /**
* Tests that expedited jobs aren't stopped when an app runs out of quota.
*/
@Test
public void testEJTracking_OutOfQuota_ForegroundAndBackground() {
setDischarging();
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
JobStatus jobBg =
createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 1);
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 e853fd3..17c6b6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -163,7 +163,7 @@
/** Collection of mocks used for PackageManagerService tests. */
class Mocks {
- val lock = Any()
+ val lock = PackageManagerTracedLock()
val installLock = Any()
val injector: PackageManagerService.Injector = mock()
val systemWrapper: PackageManagerService.SystemWrapper = mock()
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java
similarity index 84%
rename from services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java
index ee0a16a..2e0cadf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -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.pm.dex;
@@ -28,28 +28,34 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.BatteryManager;
import android.os.Build;
+import android.os.PowerManager;
import android.os.UserHandle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.server.pm.Installer;
+import com.android.server.pm.PackageManagerService;
import dalvik.system.DelegateLastClassLoader;
import dalvik.system.PathClassLoader;
import dalvik.system.VMRuntime;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import java.io.File;
@@ -69,9 +75,15 @@
DelegateLastClassLoader.class.getName();
private static final String UNSUPPORTED_CLASS_LOADER_NAME = "unsupported.class_loader";
- @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+ private static final int TEST_BATTERY_LEVEL_CRITICAL = 10;
+ private static final int TEST_BATTERY_LEVEL_DEFAULT = 80;
+
+ public StaticMockitoSession mMockitoSession;
@Mock Installer mInstaller;
@Mock IPackageManager mPM;
+ @Mock BatteryManager mMockBatteryManager;
+ @Mock PowerManager mMockPowerManager;
+
private final Object mInstallLock = new Object();
private DexManager mDexManager;
@@ -117,7 +129,37 @@
mSystemServerJarUpdatedContext = new TestData("android", isa, mUser0,
DELEGATE_LAST_CLASS_LOADER_NAME);
- mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null,
+ // Initialize Static Mocking
+
+ mMockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // Mock....
+
+ mMockBatteryManager = ExtendedMockito.mock(BatteryManager.class);
+ mMockPowerManager = ExtendedMockito.mock(PowerManager.class);
+
+ setDefaultMockValues();
+
+ Resources mockResources = ExtendedMockito.mock(Resources.class);
+ ExtendedMockito.when(mockResources
+ .getInteger(com.android.internal.R.integer.config_criticalBatteryWarningLevel))
+ .thenReturn(15);
+
+ Context mockContext = ExtendedMockito.mock(Context.class);
+ ExtendedMockito.doReturn(mockResources)
+ .when(mockContext)
+ .getResources();
+ ExtendedMockito.doReturn(mMockBatteryManager)
+ .when(mockContext)
+ .getSystemService(BatteryManager.class);
+ ExtendedMockito.doReturn(mMockPowerManager)
+ .when(mockContext)
+ .getSystemService(PowerManager.class);
+
+ mDexManager = new DexManager(mockContext, mPM, /*PackageDexOptimizer*/ null,
mInstaller, mInstallLock);
// Foo and Bar are available to user0.
@@ -128,6 +170,25 @@
mDexManager.load(existingPackages);
}
+ @After
+ public void teardown() throws Exception {
+ mMockitoSession.finishMocking();
+ }
+
+ private void setDefaultMockValues() {
+ ExtendedMockito.doReturn(BatteryManager.BATTERY_STATUS_DISCHARGING)
+ .when(mMockBatteryManager)
+ .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS);
+
+ ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_DEFAULT)
+ .when(mMockBatteryManager)
+ .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+
+ ExtendedMockito.doReturn(PowerManager.THERMAL_STATUS_NONE)
+ .when(mMockPowerManager)
+ .getCurrentThermalStatus();
+ }
+
@Test
public void testNotifyPrimaryUse() {
// The main dex file and splits are re-loaded by the app.
@@ -633,6 +694,114 @@
assertNoDclInfo(mSystemServerJarInvalid);
}
+ @Test
+ public void testInstallScenarioToReasonDefault() {
+ assertEquals(
+ PackageManagerService.REASON_INSTALL,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_DEFAULT));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_FAST,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_FAST));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK_SECONDARY,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK_SECONDARY));
+ }
+
+ @Test
+ public void testInstallScenarioToReasonThermal() {
+ ExtendedMockito.doReturn(PowerManager.THERMAL_STATUS_SEVERE)
+ .when(mMockPowerManager)
+ .getCurrentThermalStatus();
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_DEFAULT));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_FAST,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_FAST));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK_SECONDARY));
+ }
+
+ @Test
+ public void testInstallScenarioToReasonBatteryDischarging() {
+ ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_CRITICAL)
+ .when(mMockBatteryManager)
+ .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_DEFAULT));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_FAST,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_FAST));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK_SECONDARY));
+ }
+
+ @Test
+ public void testInstallScenarioToReasonBatteryCharging() {
+ ExtendedMockito.doReturn(TEST_BATTERY_LEVEL_CRITICAL)
+ .when(mMockBatteryManager)
+ .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+
+ ExtendedMockito.doReturn(BatteryManager.BATTERY_STATUS_CHARGING)
+ .when(mMockBatteryManager)
+ .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS);
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_DEFAULT));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_FAST,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_FAST));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK));
+
+ assertEquals(
+ PackageManagerService.REASON_INSTALL_BULK_SECONDARY,
+ mDexManager.getCompilationReasonForInstallScenario(
+ PackageManager.INSTALL_SCENARIO_BULK_SECONDARY));
+ }
+
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
String[] expectedContexts) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS b/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS
new file mode 100644
index 0000000..5a4431e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/dex/OWNERS
@@ -0,0 +1,2 @@
+calin@google.com
+ngeoffray@google.com
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS
new file mode 100644
index 0000000..e95633a
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/admin/OWNERS
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS
new file mode 100644
index 0000000..e95633a
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest2/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/admin/OWNERS
diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS
new file mode 100644
index 0000000..e95633a
--- /dev/null
+++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest3/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/admin/OWNERS
diff --git a/services/tests/servicestests/assets/OwnersTest/OWNERS b/services/tests/servicestests/assets/OwnersTest/OWNERS
new file mode 100644
index 0000000..e95633a
--- /dev/null
+++ b/services/tests/servicestests/assets/OwnersTest/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/admin/OWNERS
diff --git a/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS
new file mode 100644
index 0000000..e95633a
--- /dev/null
+++ b/services/tests/servicestests/assets/PolicyVersionUpgraderTest/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/admin/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 6814c050a..2eb9e34 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -524,7 +524,7 @@
assertTrue(outLaunched.value);
verify(mUiEventLogger, times(1))
- .log(GestureLauncherService.GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -578,7 +578,7 @@
assertTrue(intercepted);
verify(mUiEventLogger, times(1))
- .log(GestureLauncherService.GestureLauncherEvent.GESTURE_PANIC_TAP_POWER);
+ .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index c7e7c78..45f43e8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
@@ -30,6 +32,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,10 +50,13 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.testing.DexmakerShareClassLoaderRule;
+import android.testing.TestableContext;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityWindowInfo;
+import com.android.internal.R;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -58,7 +64,9 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
/**
@@ -72,9 +80,9 @@
private static final int APP_UID = 10400;
private static final int APP_PID = 2000;
private static final int SYSTEM_PID = 558;
-
- private static final String PERMISSION = "test-permission";
- private static final String FUNCTION = "test-function-name";
+ private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+ private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
+ "com.android.server.accessibility", "AccessibilitySecurityPolicyTest");
private static final int[] ALWAYS_DISPATCH_EVENTS = {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
@@ -108,29 +116,51 @@
private AccessibilitySecurityPolicy mA11ySecurityPolicy;
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ getInstrumentation().getTargetContext(), null);
+
// To mock package-private class
- @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+ @Rule
+ public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
- @Mock private Context mMockContext;
- @Mock private PackageManager mMockPackageManager;
- @Mock private UserManager mMockUserManager;
- @Mock private AppOpsManager mMockAppOpsManager;
- @Mock private AccessibilityServiceConnection mMockA11yServiceConnection;
- @Mock private AccessibilityWindowManager mMockA11yWindowManager;
- @Mock private AppWidgetManagerInternal mMockAppWidgetManager;
- @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
+ @Mock
+ private PackageManager mMockPackageManager;
+ @Mock
+ private UserManager mMockUserManager;
+ @Mock
+ private AppOpsManager mMockAppOpsManager;
+ @Mock
+ private AccessibilityServiceConnection mMockA11yServiceConnection;
+ @Mock
+ private AccessibilityWindowManager mMockA11yWindowManager;
+ @Mock
+ private AppWidgetManagerInternal mMockAppWidgetManager;
+ @Mock
+ private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
+ @Mock
+ private AccessibilityServiceInfo mMockA11yServiceInfo;
+ @Mock
+ private PolicyWarningUIController mPolicyWarningUIController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
- when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
+ mContext.setMockPackageManager(mMockPackageManager);
+ mContext.addMockSystemService(Context.USER_SERVICE, mMockUserManager);
+ mContext.addMockSystemService(Context.APP_OPS_SERVICE, mMockAppOpsManager);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.dimen.accessibility_focus_highlight_stroke_width, 1);
- mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager);
+ when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
+ when(mMockA11yServiceConnection.getServiceInfo()).thenReturn(mMockA11yServiceInfo);
+
+ mA11ySecurityPolicy = new AccessibilitySecurityPolicy(
+ mPolicyWarningUIController, mContext, mMockA11yUserManager);
mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager);
mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager);
+ mA11ySecurityPolicy.onSwitchUserLocked(TEST_USER_ID, new HashSet<>());
when(mMockA11yWindowManager.resolveParentWindowIdLocked(anyInt())).then(returnsFirstArg());
}
@@ -141,7 +171,7 @@
final AccessibilityEvent event = AccessibilityEvent.obtain(ALWAYS_DISPATCH_EVENTS[i]);
assertTrue("Should dispatch [" + event + "]",
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
- UserHandle.USER_SYSTEM,
+ TEST_USER_ID,
event));
}
}
@@ -154,28 +184,28 @@
event.setWindowId(invalidWindowId);
assertFalse("Shouldn't dispatch [" + event + "]",
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
- UserHandle.USER_SYSTEM,
+ TEST_USER_ID,
event));
}
}
@Test
public void canDispatchAccessibilityEvent_otherEvents_windowIdIsActive_returnTrue() {
- when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
+ when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
.thenReturn(WINDOWID);
for (int i = 0; i < OTHER_EVENTS.length; i++) {
final AccessibilityEvent event = AccessibilityEvent.obtain(OTHER_EVENTS[i]);
event.setWindowId(WINDOWID);
assertTrue("Should dispatch [" + event + "]",
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
- UserHandle.USER_SYSTEM,
+ TEST_USER_ID,
event));
}
}
@Test
public void canDispatchAccessibilityEvent_otherEvents_windowIdExist_returnTrue() {
- when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
+ when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
.thenReturn(WINDOWID2);
when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID))
.thenReturn(AccessibilityWindowInfo.obtain());
@@ -184,7 +214,7 @@
event.setWindowId(WINDOWID);
assertTrue("Should dispatch [" + event + "]",
mA11ySecurityPolicy.canDispatchAccessibilityEventLocked(
- UserHandle.USER_SYSTEM,
+ TEST_USER_ID,
event));
}
}
@@ -192,24 +222,24 @@
@Test
public void resolveValidReportedPackage_nullPkgName_returnNull() {
assertNull(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
- null, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID));
+ null, Process.SYSTEM_UID, TEST_USER_ID, SYSTEM_PID));
}
@Test
public void resolveValidReportedPackage_uidIsSystem_returnPkgName() {
assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
- PACKAGE_NAME, Process.SYSTEM_UID, UserHandle.USER_SYSTEM, SYSTEM_PID),
+ PACKAGE_NAME, Process.SYSTEM_UID, TEST_USER_ID, SYSTEM_PID),
PACKAGE_NAME);
}
@Test
public void resolveValidReportedPackage_uidAndPkgNameMatched_returnPkgName()
throws PackageManager.NameNotFoundException {
- when(mMockPackageManager.getPackageUidAsUser(PACKAGE_NAME, UserHandle.USER_SYSTEM))
+ when(mMockPackageManager.getPackageUidAsUser(PACKAGE_NAME, TEST_USER_ID))
.thenReturn(APP_UID);
assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
- PACKAGE_NAME, APP_UID, UserHandle.USER_SYSTEM, APP_PID),
+ PACKAGE_NAME, APP_UID, TEST_USER_ID, APP_PID),
PACKAGE_NAME);
}
@@ -225,11 +255,11 @@
when(mMockAppWidgetManager.getHostedWidgetPackages(widgetHostUid))
.thenReturn(widgetPackages);
- when(mMockPackageManager.getPackageUidAsUser(hostPackageName, UserHandle.USER_SYSTEM))
+ when(mMockPackageManager.getPackageUidAsUser(hostPackageName, TEST_USER_ID))
.thenReturn(widgetHostUid);
assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
- widgetPackageName, widgetHostUid, UserHandle.USER_SYSTEM, widgetHostPid),
+ widgetPackageName, widgetHostUid, TEST_USER_ID, widgetHostPid),
widgetPackageName);
}
@@ -240,16 +270,16 @@
final String[] uidPackages = {PACKAGE_NAME, PACKAGE_NAME2};
when(mMockPackageManager.getPackagesForUid(APP_UID))
.thenReturn(uidPackages);
- when(mMockPackageManager.getPackageUidAsUser(invalidPackageName, UserHandle.USER_SYSTEM))
+ when(mMockPackageManager.getPackageUidAsUser(invalidPackageName, TEST_USER_ID))
.thenThrow(PackageManager.NameNotFoundException.class);
when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
.thenReturn(new ArraySet<>());
- when(mMockContext.checkPermission(
- eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID)))
- .thenReturn(PackageManager.PERMISSION_DENIED);
+ mContext.getTestablePermissions().setPermission(
+ Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY,
+ PackageManager.PERMISSION_DENIED);
assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked(
- invalidPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID));
+ invalidPackageName, APP_UID, TEST_USER_ID, APP_PID));
}
@Test
@@ -260,16 +290,16 @@
final String[] uidPackages = {PACKAGE_NAME};
when(mMockPackageManager.getPackagesForUid(APP_UID))
.thenReturn(uidPackages);
- when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM))
+ when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID))
.thenReturn(wantedUid);
when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
.thenReturn(new ArraySet<>());
- when(mMockContext.checkPermission(
- eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID)))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ mContext.getTestablePermissions().setPermission(
+ Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY,
+ PackageManager.PERMISSION_GRANTED);
assertEquals(wantedPackageName, mA11ySecurityPolicy.resolveValidReportedPackageLocked(
- wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID));
+ wantedPackageName, APP_UID, TEST_USER_ID, APP_PID));
}
@Test
@@ -280,16 +310,16 @@
final String[] uidPackages = {PACKAGE_NAME};
when(mMockPackageManager.getPackagesForUid(APP_UID))
.thenReturn(uidPackages);
- when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, UserHandle.USER_SYSTEM))
+ when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID))
.thenReturn(wantedUid);
when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
.thenReturn(new ArraySet<>());
- when(mMockContext.checkPermission(
- eq(Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY), anyInt(), eq(APP_UID)))
- .thenReturn(PackageManager.PERMISSION_DENIED);
+ mContext.getTestablePermissions().setPermission(
+ Manifest.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY,
+ PackageManager.PERMISSION_DENIED);
assertEquals(PACKAGE_NAME, mA11ySecurityPolicy.resolveValidReportedPackageLocked(
- wantedPackageName, APP_UID, UserHandle.USER_SYSTEM, APP_PID));
+ wantedPackageName, APP_UID, TEST_USER_ID, APP_PID));
}
@Test
@@ -301,7 +331,7 @@
@Test
public void computeValidReportedPackages_uidIsAppWidgetHost_returnTargetAndWidgetName() {
final int widgetHostUid = APP_UID;
- final String targetPackageName = PACKAGE_NAME;
+ final String targetPackageName = PACKAGE_NAME;
final String widgetPackageName = PACKAGE_NAME2;
final ArraySet<String> widgetPackages = new ArraySet<>();
widgetPackages.add(widgetPackageName);
@@ -320,7 +350,7 @@
when(mMockA11yServiceConnection.getCapabilities())
.thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
- assertFalse(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM,
+ assertFalse(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID,
mMockA11yServiceConnection, invalidWindowId));
}
@@ -328,10 +358,10 @@
public void canGetAccessibilityNodeInfo_hasCapAndWindowIsActive_returnTrue() {
when(mMockA11yServiceConnection.getCapabilities())
.thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
- when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
+ when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
.thenReturn(WINDOWID);
- assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM,
+ assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID,
mMockA11yServiceConnection, WINDOWID));
}
@@ -339,12 +369,12 @@
public void canGetAccessibilityNodeInfo_hasCapAndWindowExist_returnTrue() {
when(mMockA11yServiceConnection.getCapabilities())
.thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
- when(mMockA11yWindowManager.getActiveWindowId(UserHandle.USER_SYSTEM))
+ when(mMockA11yWindowManager.getActiveWindowId(TEST_USER_ID))
.thenReturn(WINDOWID2);
when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID))
.thenReturn(AccessibilityWindowInfo.obtain());
- assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(UserHandle.USER_SYSTEM,
+ assertTrue(mA11ySecurityPolicy.canGetAccessibilityNodeInfoLocked(TEST_USER_ID,
mMockA11yServiceConnection, WINDOWID));
}
@@ -464,8 +494,10 @@
.thenReturn(currentUserId);
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
callingUserId);
- when(mMockContext.checkCallingPermission(any()))
- .thenReturn(PackageManager.PERMISSION_DENIED);
+ mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS,
+ PackageManager.PERMISSION_DENIED);
+ mContext.getTestablePermissions().setPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_DENIED);
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
UserHandle.USER_CURRENT_OR_SELF);
@@ -482,8 +514,8 @@
.thenReturn(currentUserId);
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
callingUserId);
- when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS,
+ PackageManager.PERMISSION_GRANTED);
assertEquals(wantedUserId,
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId));
@@ -500,8 +532,8 @@
.thenReturn(currentUserId);
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
callingUserId);
- when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL))
- .thenReturn(PackageManager.PERMISSION_GRANTED);
+ mContext.getTestablePermissions().setPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_GRANTED);
assertEquals(wantedUserId,
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId));
@@ -518,10 +550,10 @@
.thenReturn(currentUserId);
doReturn(callingParentId).when(spySecurityPolicy).resolveProfileParentLocked(
callingUserId);
- when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS))
- .thenReturn(PackageManager.PERMISSION_DENIED);
- when(mMockContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL))
- .thenReturn(PackageManager.PERMISSION_DENIED);
+ mContext.getTestablePermissions().setPermission(Manifest.permission.INTERACT_ACROSS_USERS,
+ PackageManager.PERMISSION_DENIED);
+ mContext.getTestablePermissions().setPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, PackageManager.PERMISSION_DENIED);
spySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(wantedUserId);
}
@@ -562,4 +594,57 @@
APP_UID, PACKAGE_NAME);
}
+ @Test
+ public void onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction() {
+ final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
+ boundServices.add(mMockA11yServiceConnection);
+ when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(true);
+
+ mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);
+
+ verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceBound(anyInt(), any());
+ }
+
+ @Test
+ public void onBoundServicesChanged_unbindA11yCategoryService_noUIControllerAction() {
+ onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction();
+
+ mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>());
+
+ verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceUnbound(anyInt(),
+ any());
+ }
+
+ @Test
+ public void onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction() {
+ final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
+ boundServices.add(mMockA11yServiceConnection);
+ when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(false);
+
+ mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);
+
+ verify(mPolicyWarningUIController).onNonA11yCategoryServiceBound(eq(TEST_USER_ID),
+ eq(TEST_COMPONENT_NAME));
+ }
+
+ @Test
+ public void onBoundServicesChanged_unbindNonA11yCategoryService_activateUIControllerAction() {
+ onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction();
+
+ mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>());
+
+ verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID),
+ eq(TEST_COMPONENT_NAME));
+ }
+
+ @Test
+ public void onSwitchUser_differentUser_activateUIControllerAction() {
+ onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction();
+
+ mA11ySecurityPolicy.onSwitchUserLocked(2, new HashSet<>());
+
+ verify(mPolicyWarningUIController).onSwitchUserLocked(eq(2), eq(new HashSet<>()));
+ verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID),
+ eq(TEST_COMPONENT_NAME));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java
new file mode 100644
index 0000000..01a641f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/PolicyWarningUIControllerTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.accessibility;
+
+import static android.app.AlarmManager.RTC_WAKEUP;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_VIEW_AND_CONTROL_ACCESS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for the {@link PolicyWarningUIController}.
+ */
+public class PolicyWarningUIControllerTest {
+ private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+ private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
+ "com.android.server.accessibility", "PolicyWarningUIControllerTest");
+
+ private final List<AccessibilityServiceInfo> mEnabledServiceList = new ArrayList<>();
+
+ @Rule
+ public final A11yTestableContext mContext = new A11yTestableContext(
+ getInstrumentation().getTargetContext());
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private StatusBarManager mStatusBarManager;
+ @Mock
+ private AccessibilityServiceInfo mMockA11yServiceInfo;
+ @Mock
+ private ResolveInfo mMockResolveInfo;
+ @Mock
+ private ServiceInfo mMockServiceInfo;
+ @Mock
+ private Context mSpyContext;
+ @Mock
+ private AccessibilitySecurityPolicy mAccessibilitySecurityPolicy;
+
+ private PolicyWarningUIController mPolicyWarningUIController;
+ private FakeNotificationController mFakeNotificationController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
+ mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+ mContext.addMockSystemService(StatusBarManager.class, mStatusBarManager);
+ mFakeNotificationController = new FakeNotificationController(mContext);
+ mPolicyWarningUIController = new PolicyWarningUIController(
+ getInstrumentation().getTargetContext().getMainThreadHandler(), mContext,
+ mFakeNotificationController);
+ mPolicyWarningUIController.setAccessibilityPolicyManager(mAccessibilitySecurityPolicy);
+ mPolicyWarningUIController.onSwitchUserLocked(TEST_USER_ID, new HashSet<>());
+ mEnabledServiceList.clear();
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
+ "", TEST_USER_ID);
+ }
+
+ @Test
+ public void receiveActionSendNotification_isNonA11yCategoryService_sendNotification() {
+ mEnabledServiceList.add(mMockA11yServiceInfo);
+ mMockResolveInfo.serviceInfo = mMockServiceInfo;
+ when(mMockA11yServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
+ when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
+ when(mAccessibilitySecurityPolicy.isA11yCategoryService(
+ mMockA11yServiceInfo)).thenReturn(false);
+
+ mFakeNotificationController.onReceive(mContext, createIntent(TEST_USER_ID,
+ PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
+ TEST_COMPONENT_NAME.flattenToShortString()));
+
+ verify(mNotificationManager).notify(eq(TEST_COMPONENT_NAME.flattenToShortString()),
+ eq(NOTE_A11Y_VIEW_AND_CONTROL_ACCESS), any(
+ Notification.class));
+ }
+
+ @Test
+ public void receiveActionA11ySettings_launchA11ySettingsAndDismissNotification() {
+ mFakeNotificationController.onReceive(mContext,
+ createIntent(TEST_USER_ID, PolicyWarningUIController.ACTION_A11Y_SETTINGS,
+ TEST_COMPONENT_NAME.flattenToShortString()));
+
+ verifyLaunchA11ySettings();
+ verify(mNotificationManager).cancel(TEST_COMPONENT_NAME.flattenToShortString(),
+ NOTE_A11Y_VIEW_AND_CONTROL_ACCESS);
+ assertNotifiedSettingsEqual(TEST_USER_ID,
+ TEST_COMPONENT_NAME.flattenToShortString());
+ }
+
+ @Test
+ public void receiveActionDismissNotification_addToNotifiedSettings() {
+ mFakeNotificationController.onReceive(mContext, createIntent(TEST_USER_ID,
+ PolicyWarningUIController.ACTION_DISMISS_NOTIFICATION,
+ TEST_COMPONENT_NAME.flattenToShortString()));
+
+ assertNotifiedSettingsEqual(TEST_USER_ID,
+ TEST_COMPONENT_NAME.flattenToShortString());
+ }
+
+ @Test
+ public void onEnabledServicesChangedLocked_serviceDisabled_removedFromNotifiedSettings() {
+ final Set<ComponentName> enabledServices = new HashSet<>();
+ enabledServices.add(TEST_COMPONENT_NAME);
+ mPolicyWarningUIController.onEnabledServicesChangedLocked(TEST_USER_ID, enabledServices);
+ getInstrumentation().waitForIdleSync();
+ receiveActionDismissNotification_addToNotifiedSettings();
+
+ mPolicyWarningUIController.onEnabledServicesChangedLocked(TEST_USER_ID, new HashSet<>());
+ getInstrumentation().waitForIdleSync();
+
+ assertNotifiedSettingsEqual(TEST_USER_ID, "");
+ }
+
+ @Test
+ public void onNonA11yCategoryServiceBound_setAlarm() {
+ mPolicyWarningUIController.onNonA11yCategoryServiceBound(TEST_USER_ID, TEST_COMPONENT_NAME);
+ getInstrumentation().waitForIdleSync();
+
+ verify(mAlarmManager).set(eq(RTC_WAKEUP), anyLong(),
+ eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID,
+ PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
+ TEST_COMPONENT_NAME.flattenToShortString())));
+ }
+
+ @Test
+ public void onNonA11yCategoryServiceUnbound_cancelAlarm() {
+ mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(TEST_USER_ID,
+ TEST_COMPONENT_NAME);
+ getInstrumentation().waitForIdleSync();
+
+ verify(mAlarmManager).cancel(
+ eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID,
+ PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
+ TEST_COMPONENT_NAME.flattenToShortString())));
+ }
+
+ private void assertNotifiedSettingsEqual(int userId, String settingString) {
+ final String notifiedServicesSetting = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
+ userId);
+ assertEquals(settingString, notifiedServicesSetting);
+ }
+
+ private Intent createIntent(int userId, String action, String serviceComponentName) {
+ final Intent intent = new Intent(action);
+ intent.setPackage(mContext.getPackageName())
+ .setIdentifier(serviceComponentName)
+ .putExtra(Intent.EXTRA_USER_ID, userId);
+ return intent;
+ }
+
+ private void verifyLaunchA11ySettings() {
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ final ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass(
+ UserHandle.class);
+ verify(mSpyContext).startActivityAsUser(intentCaptor.capture(),
+ any(), userHandleCaptor.capture());
+ assertThat(intentCaptor.getValue().getAction()).isEqualTo(
+ Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
+ assertThat(userHandleCaptor.getValue().getIdentifier()).isEqualTo(TEST_USER_ID);
+ verify(mStatusBarManager).collapsePanels();
+ }
+
+ private class A11yTestableContext extends TestableContext {
+ A11yTestableContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ mSpyContext.startActivityAsUser(intent, options, user);
+ }
+ }
+
+ private class FakeNotificationController extends
+ PolicyWarningUIController.NotificationController {
+ FakeNotificationController(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected List<AccessibilityServiceInfo> getEnabledServiceInfos() {
+ return mEnabledServiceList;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 44b9f44..29691fb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -65,7 +65,6 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Locale;
@@ -1114,18 +1113,56 @@
argThat(closeTo(newEndSpec)));
}
+ @Test
+ public void testSetForceShowMagnifiableBounds() {
+ register(DISPLAY_0);
+
+ mFullScreenMagnificationController.setForceShowMagnifiableBounds(DISPLAY_0, true);
+
+ verify(mMockWindowManager).setForceShowMagnifiableBounds(DISPLAY_0, true);
+ }
+
+ @Test
+ public void testIsForceShowMagnifiableBounds() {
+ register(DISPLAY_0);
+ mFullScreenMagnificationController.setForceShowMagnifiableBounds(DISPLAY_0, true);
+
+ assertTrue(mFullScreenMagnificationController.isForceShowMagnifiableBounds(DISPLAY_0));
+ }
+
+ @Test
+ public void testSetScale_toMagnifying_shouldNotifyActivatedState() {
+ setScaleToMagnifying();
+
+ verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(true));
+ }
+
+ @Test
+ public void testReset_afterMagnifying_shouldNotifyDeactivatedState() {
+ setScaleToMagnifying();
+
+ mFullScreenMagnificationController.reset(DISPLAY_0, mAnimationCallback);
+ verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(false));
+ }
+
+ private void setScaleToMagnifying() {
+ register(DISPLAY_0);
+ float scale = 2.0f;
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+
+ mFullScreenMagnificationController.setScale(DISPLAY_0, scale, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ }
+
private void initMockWindowManager() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true);
}
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Region regionArg = (Region) args[1];
- regionArg.set(INITIAL_MAGNIFICATION_REGION);
- return null;
- }
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ Object[] args = invocationOnMock.getArguments();
+ Region regionArg = (Region) args[1];
+ regionArg.set(INITIAL_MAGNIFICATION_REGION);
+ return null;
}).when(mMockWindowManager).getMagnificationRegion(anyInt(), (Region) anyObject());
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 0c3640c..cf23197 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -16,14 +16,19 @@
package com.android.server.accessibility.magnification;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
@@ -170,6 +175,23 @@
}
@Test
+ public void transitionToWindowMode_disablingWindowMode_showMagnificationButton()
+ throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ MODE_FULLSCREEN,
+ mTransitionCallBack);
+
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ MODE_WINDOW,
+ mTransitionCallBack);
+
+ mMockConnection.invokeCallbacks();
+ verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY),
+ eq(MODE_WINDOW));
+ }
+
+ @Test
public void transitionToFullScreenMode_windowMagnifying_disableWindowAndEnableFullScreen()
throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
@@ -224,7 +246,6 @@
verify(mTransitionCallBack).onResult(true);
}
-
@Test
public void interruptDuringTransitionToFullScreenMode_windowMagnifying_notifyTransitionFailed()
throws RemoteException {
@@ -283,6 +304,27 @@
}
@Test
+ public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() {
+ mMagnificationController.onWindowMagnificationActivationState(true);
+
+ mMagnificationController.onWindowMagnificationActivationState(false);
+
+ verify(mMagnificationController).logMagnificationUsageState(
+ eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong());
+ }
+
+ @Test
+ public void
+ onFullScreenMagnificationActivationState_fullScreenActivated_logFullScreenDuration() {
+ mMagnificationController.onFullScreenMagnificationActivationState(true);
+
+ mMagnificationController.onFullScreenMagnificationActivationState(false);
+
+ verify(mMagnificationController).logMagnificationUsageState(
+ eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong());
+ }
+
+ @Test
public void onTouchInteractionStart_fullScreenAndCapabilitiesAll_showMagnificationButton()
throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
@@ -340,6 +382,19 @@
eq(MODE_FULLSCREEN));
}
+
+ @Test
+ public void onTouchInteractionChanged_fullscreenNotActivated_notShowMagnificationButton()
+ throws RemoteException {
+ setMagnificationModeSettings(MODE_FULLSCREEN);
+
+ mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN);
+ mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN);
+
+ verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY),
+ eq(MODE_FULLSCREEN));
+ }
+
@Test
public void onShortcutTriggered_windowModeEnabledAndCapabilitiesAll_showMagnificationButton()
throws RemoteException {
@@ -402,6 +457,35 @@
verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY));
}
+ @Test
+ public void transitionToFullScreenMode_fullscreenModeActivated_showMagnificationButton()
+ throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ MODE_FULLSCREEN, mTransitionCallBack);
+ mMockConnection.invokeCallbacks();
+
+ verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY),
+ eq(MODE_FULLSCREEN));
+ }
+
+ @Test
+ public void transitionToWindow_windowModeActivated_showMagnificationButton()
+ throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ MODE_WINDOW, mTransitionCallBack);
+
+ verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY),
+ mCallbackArgumentCaptor.capture());
+ mCallbackArgumentCaptor.getValue().onResult(true);
+ mMockConnection.invokeCallbacks();
+ verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY),
+ eq(MODE_WINDOW));
+ }
+
private void setMagnificationEnabled(int mode) throws RemoteException {
setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
@@ -425,6 +509,8 @@
}
if (mode == MODE_FULLSCREEN) {
when(mScreenMagnificationController.isMagnifying(TEST_DISPLAY)).thenReturn(true);
+ when(mScreenMagnificationController.isForceShowMagnifiableBounds(
+ TEST_DISPLAY)).thenReturn(true);
when(mScreenMagnificationController.getPersistedScale()).thenReturn(DEFAULT_SCALE);
when(mScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(DEFAULT_SCALE);
when(mScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(
@@ -432,6 +518,11 @@
when(mScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(
centerY);
} else {
+ doAnswer(invocation -> {
+ when(mScreenMagnificationController.isMagnifying(TEST_DISPLAY)).thenReturn(true);
+ return null;
+ }).when(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+ eq(DEFAULT_SCALE), anyFloat(), anyFloat(), anyBoolean(), anyInt());
mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE,
centerX, centerY, null);
mMockConnection.invokeCallbacks();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index f26c86c..955217c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -392,6 +392,23 @@
assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
}
+ @Test
+ public void onWindowMagnificationActivationState_magnifierEnabled_notifyActivatedState() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+
+ verify(mMockCallback).onWindowMagnificationActivationState(eq(true));
+ }
+
+ @Test
+ public void onWindowMagnificationActivationState_magnifierDisabled_notifyDeactivatedState() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+ mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, true);
+
+ verify(mMockCallback).onWindowMagnificationActivationState(eq(false));
+ }
+
private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
final int len = pointersLocation.length;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index 129d263..e13597d 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -19,23 +19,37 @@
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;
import static org.junit.Assert.fail;
import android.app.ActivityManager;
+import android.app.ActivityManager.OnUidImportanceListener;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.IActivityManager;
+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.PackageManager;
+import android.os.Bundle;
+import android.os.DropBoxManager;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.support.test.uiautomator.UiDevice;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.text.TextUtils;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
@@ -65,6 +79,12 @@
private static final long AWAIT_TIMEOUT = 2000;
private static final long CHECK_INTERVAL = 100;
+ private static final String TEST_FGS_CLASS =
+ "com.android.servicestests.apps.simpleservicetestapp.SimpleFgService";
+ private static final String ACTION_FGS_STATS_TEST =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST";
+ private static final String EXTRA_MESSENGER = "extra_messenger";
+
private IActivityManager mService;
private IRemoteCallback mCallback;
private Context mContext;
@@ -204,4 +224,184 @@
public void onServiceDisconnected(ComponentName name) {
}
}
+
+ /**
+ * Note: This test actually only works in eng build. It'll always pass
+ * in user and userdebug build, because the expected exception won't be
+ * thrown in those builds.
+ */
+ @LargeTest
+ @Test
+ public void testFgsProcStatsTracker() throws Exception {
+ final PackageManager pm = mContext.getPackageManager();
+ final long timeout = 5000;
+ int uid = pm.getPackageUid(TEST_APP, 0);
+ final MyUidImportanceListener uidListener1 = new MyUidImportanceListener(uid);
+ final MyUidImportanceListener uidListener2 = new MyUidImportanceListener(uid);
+ final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+ final CountDownLatch[] latchHolder = new CountDownLatch[1];
+ final H handler = new H(Looper.getMainLooper(), latchHolder);
+ final Messenger messenger = new Messenger(handler);
+ final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+ final CountDownLatch dboxLatch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String tag_wtf = "system_server_wtf";
+ if (tag_wtf.equals(intent.getStringExtra(DropBoxManager.EXTRA_TAG))) {
+ final DropBoxManager.Entry e = dbox.getNextEntry(tag_wtf, intent.getLongExtra(
+ DropBoxManager.EXTRA_TIME, 0) - 1);
+ final String text = e.getText(8192);
+ if (TextUtils.isEmpty(text)) {
+ return;
+ }
+ if (text.indexOf("can't store negative values") == -1) {
+ return;
+ }
+ dboxLatch.countDown();
+ }
+ }
+ };
+ try {
+ mContext.registerReceiver(receiver,
+ new IntentFilter(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED));
+ am.addOnUidImportanceListener(uidListener1,
+ RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+ am.addOnUidImportanceListener(uidListener2, RunningAppProcessInfo.IMPORTANCE_GONE);
+ runShellCommand("cmd deviceidle whitelist +" + TEST_APP);
+ toggleScreenOn(true);
+
+ final Intent intent = new Intent(ACTION_FGS_STATS_TEST);
+ final ComponentName cn = ComponentName.unflattenFromString(
+ TEST_APP + "/" + TEST_FGS_CLASS);
+ final Bundle bundle = new Bundle();
+ intent.setComponent(cn);
+ bundle.putBinder(EXTRA_MESSENGER, messenger.getBinder());
+ intent.putExtras(bundle);
+
+ latchHolder[0] = new CountDownLatch(1);
+ mContext.startForegroundService(intent);
+ assertTrue("Timed out to start fg service", uidListener1.waitFor(
+ RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE, timeout));
+ assertTrue("Timed out to get the remote messenger", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(timeout);
+ latchHolder[0] = new CountDownLatch(1);
+ handler.sendRemoteMessage(H.MSG_STOP_FOREGROUND, 0, 0, null);
+ assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(timeout);
+ latchHolder[0] = new CountDownLatch(1);
+ handler.sendRemoteMessage(H.MSG_START_FOREGROUND, 0, 0, null);
+ assertTrue("Timed out to wait for start fg", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+
+ toggleScreenOn(false);
+ latchHolder[0] = new CountDownLatch(1);
+ handler.sendRemoteMessage(H.MSG_STOP_FOREGROUND, 0, 0, null);
+ assertTrue("Timed out to wait for stop fg", latchHolder[0].await(
+ timeout, TimeUnit.MILLISECONDS));
+ assertFalse("There shouldn't be negative values", dboxLatch.await(
+ timeout * 2, TimeUnit.MILLISECONDS));
+ } finally {
+ toggleScreenOn(true);
+ runShellCommand("cmd deviceidle whitelist -" + TEST_APP);
+ am.removeOnUidImportanceListener(uidListener1);
+ am.removeOnUidImportanceListener(uidListener2);
+ am.forceStopPackage(TEST_APP);
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ /**
+ * Make sure the screen state.
+ */
+ private void toggleScreenOn(final boolean screenon) throws Exception {
+ if (screenon) {
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+ runShellCommand("wm dismiss-keyguard");
+ } else {
+ runShellCommand("input keyevent KEYCODE_SLEEP");
+ }
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(2_000);
+ }
+
+ private class H extends Handler {
+ static final int MSG_INIT = 0;
+ static final int MSG_DONE = 1;
+ static final int MSG_START_FOREGROUND = 2;
+ static final int MSG_STOP_FOREGROUND = 3;
+
+ private Messenger mRemoteMessenger;
+ private CountDownLatch[] mLatchHolder;
+
+ H(Looper looper, CountDownLatch[] latchHolder) {
+ super(looper);
+ mLatchHolder = latchHolder;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INIT:
+ mRemoteMessenger = (Messenger) msg.obj;
+ mLatchHolder[0].countDown();
+ break;
+ case MSG_DONE:
+ mLatchHolder[0].countDown();
+ break;
+ }
+ }
+
+ void sendRemoteMessage(int what, int arg1, int arg2, Object obj) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = arg1;
+ msg.arg2 = arg2;
+ msg.obj = obj;
+ try {
+ mRemoteMessenger.send(msg);
+ } catch (RemoteException e) {
+ }
+ msg.recycle();
+ }
+ }
+
+ private static class MyUidImportanceListener implements OnUidImportanceListener {
+ final CountDownLatch[] mLatchHolder = new CountDownLatch[1];
+ private final int mExpectedUid;
+ private int mExpectedImportance;
+ private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE;
+
+ MyUidImportanceListener(int uid) {
+ mExpectedUid = uid;
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (uid == mExpectedUid) {
+ synchronized (this) {
+ if (importance == mExpectedImportance && mLatchHolder[0] != null) {
+ mLatchHolder[0].countDown();
+ }
+ mCurrentImportance = importance;
+ }
+ Log.i(TAG, "uid " + uid + " importance: " + importance);
+ }
+ }
+
+ boolean waitFor(int expectedImportance, long timeout) throws Exception {
+ synchronized (this) {
+ mExpectedImportance = expectedImportance;
+ if (mCurrentImportance == expectedImportance) {
+ return true;
+ }
+ mLatchHolder[0] = new CountDownLatch(1);
+ }
+ return mLatchHolder[0].await(timeout, TimeUnit.MILLISECONDS);
+ }
+ }
}
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 8d54ead..73a2feb 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -17,8 +17,10 @@
package com.android.server.am;
import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL;
+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_WIFI;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -39,6 +41,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.PowerProfile;
import org.junit.Before;
import org.junit.Test;
@@ -61,7 +64,7 @@
public void setUp() {
final Context context = InstrumentationRegistry.getContext();
- mBatteryStatsImpl = new TestBatteryStatsImpl();
+ mBatteryStatsImpl = new TestBatteryStatsImpl(context);
mPowerStatsInternal = new TestPowerStatsInternal();
mBatteryExternalStatsWorker = new BatteryExternalStatsWorker(new TestInjector(context),
mBatteryStatsImpl);
@@ -72,13 +75,24 @@
final int numCpuClusters = 4;
final int numOther = 3;
- final IntArray tempAllIds = new IntArray();
// Add some energy consumers used by BatteryExternalStatsWorker.
+ final IntArray tempAllIds = new IntArray();
+
final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
"display");
tempAllIds.add(displayId);
mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+ final int wifiId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.WIFI, 0,
+ "wifi");
+ tempAllIds.add(wifiId);
+ mPowerStatsInternal.incrementEnergyConsumption(wifiId, 23456);
+
+ final int btId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.BLUETOOTH, 0,
+ "bt");
+ tempAllIds.add(btId);
+ mPowerStatsInternal.incrementEnergyConsumption(btId, 34567);
+
final int[] cpuClusterIds = new int[numCpuClusters];
for (int i = 0; i < numCpuClusters; i++) {
cpuClusterIds[i] = mPowerStatsInternal.addEnergyConsumer(
@@ -109,6 +123,18 @@
assertEquals(1, displayResults.length);
assertEquals(displayId, displayResults[0].id);
+ final EnergyConsumerResult[] wifiResults =
+ mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_WIFI).getNow(null);
+ // Results should only have the wifi energy consumer
+ assertEquals(1, wifiResults.length);
+ assertEquals(wifiId, wifiResults[0].id);
+
+ final EnergyConsumerResult[] bluetoothResults =
+ mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_BT).getNow(null);
+ // Results should only have the bluetooth energy consumer
+ assertEquals(1, bluetoothResults.length);
+ assertEquals(btId, bluetoothResults[0].id);
+
final EnergyConsumerResult[] cpuResults =
mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null);
// Results should only have the cpu cluster energy consumers
@@ -148,6 +174,9 @@
}
public class TestBatteryStatsImpl extends BatteryStatsImpl {
+ public TestBatteryStatsImpl(Context context) {
+ mPowerProfile = new PowerProfile(context, true /* forTest */);
+ }
}
public class TestPowerStatsInternal extends PowerStatsInternal {
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 b5f4912..ad22cba 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -103,12 +103,12 @@
// Insert package1 document
GenericDocument document1 =
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package1", "database1", document1);
+ mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
// Insert package2 document
GenericDocument document2 =
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package2", "database2", document2);
+ mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null);
// No query filters specified, global query can retrieve all documents.
SearchSpec searchSpec =
@@ -155,12 +155,12 @@
// Insert package1 document
GenericDocument document1 =
new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package1", "database1", document1);
+ mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
// Insert package2 document
GenericDocument document2 =
new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package2", "database2", document2);
+ mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null);
// "package1" filter specified
SearchSpec searchSpec =
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 6890ed1..e0cdedd 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
@@ -415,10 +415,8 @@
+ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL;
i++) {
GenericDocument document =
- new GenericDocument.Builder<>("uri" + i, "type")
- .setNamespace("namespace")
- .build();
- mAppSearchImpl.putDocument("package", "database", document);
+ new GenericDocument.Builder<>("namespace", "uri" + i, "type").build();
+ mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
}
// Check optimize() will release 0 docs since there is no deletion.
@@ -477,8 +475,8 @@
// Insert document
GenericDocument document =
- new GenericDocument.Builder<>("uri", "type").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package", "database", document);
+ new GenericDocument.Builder<>("namespace", "uri", "type").build();
+ mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
// Rewrite SearchSpec
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
@@ -517,12 +515,12 @@
// Insert documents
GenericDocument document1 =
- new GenericDocument.Builder<>("uri", "typeA").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package", "database1", document1);
+ new GenericDocument.Builder<>("namespace", "uri", "typeA").build();
+ mAppSearchImpl.putDocument("package", "database1", document1, /*logger=*/ null);
GenericDocument document2 =
- new GenericDocument.Builder<>("uri", "typeB").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package", "database2", document2);
+ new GenericDocument.Builder<>("namespace", "uri", "typeB").build();
+ mAppSearchImpl.putDocument("package", "database2", document2, /*logger=*/ null);
// Rewrite SearchSpec
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
@@ -561,8 +559,8 @@
// Insert document
GenericDocument document =
- new GenericDocument.Builder<>("uri", "type").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package", "database", document);
+ new GenericDocument.Builder<>("namespace", "uri", "type").build();
+ mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
// If 'allowedPrefixedSchemas' is empty, this returns false since there's nothing to
// search over. Despite the searchSpecProto having schema type filters.
@@ -614,8 +612,8 @@
// Insert package1 document
GenericDocument document =
- new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package1", "database1", document);
+ new GenericDocument.Builder<>("namespace", "uri", "schema1").build();
+ mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
// No query filters specified, package2 shouldn't be able to query for package1's documents.
SearchSpec searchSpec =
@@ -625,14 +623,13 @@
assertThat(searchResultPage.getResults()).isEmpty();
// Insert package2 document
- document =
- new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package2", "database2", document);
+ document = new GenericDocument.Builder<>("namespace", "uri", "schema2").build();
+ mAppSearchImpl.putDocument("package2", "database2", document, /*logger=*/ null);
// No query filters specified. package2 should only get its own documents back.
searchResultPage = mAppSearchImpl.query("package2", "database2", "", searchSpec);
assertThat(searchResultPage.getResults()).hasSize(1);
- assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document);
+ assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document);
}
/**
@@ -665,8 +662,8 @@
// Insert package1 document
GenericDocument document =
- new GenericDocument.Builder<>("uri", "schema1").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package1", "database1", document);
+ new GenericDocument.Builder<>("namespace", "uri", "schema1").build();
+ mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
// "package1" filter specified, but package2 shouldn't be able to query for package1's
// documents.
@@ -680,9 +677,8 @@
assertThat(searchResultPage.getResults()).isEmpty();
// Insert package2 document
- document =
- new GenericDocument.Builder<>("uri", "schema2").setNamespace("namespace").build();
- mAppSearchImpl.putDocument("package2", "database2", document);
+ document = new GenericDocument.Builder<>("namespace", "uri", "schema2").build();
+ mAppSearchImpl.putDocument("package2", "database2", document, /*logger=*/ null);
// "package2" filter specified, package2 should only get its own documents back.
searchSpec =
@@ -692,7 +688,7 @@
.build();
searchResultPage = mAppSearchImpl.query("package2", "database2", "", searchSpec);
assertThat(searchResultPage.getResults()).hasSize(1);
- assertThat(searchResultPage.getResults().get(0).getDocument()).isEqualTo(document);
+ assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document);
}
@Test
@@ -1073,7 +1069,7 @@
for (SearchResult result : searchResultPage.getResults()) {
assertThat(result.getPackageName()).isEqualTo("com.package.foo");
assertThat(result.getDatabaseName()).isEqualTo("databaseName");
- assertThat(result.getDocument())
+ assertThat(result.getGenericDocument())
.isEqualTo(
GenericDocumentToProtoConverter.toGenericDocument(
strippedDocumentProto.build()));
@@ -1128,9 +1124,8 @@
appSearchImpl.putDocument(
"package",
"database",
- new GenericDocument.Builder<>("uri", "type")
- .setNamespace("namespace")
- .build());
+ new GenericDocument.Builder<>("namespace", "uri", "type").build(),
+ /*logger=*/ null);
});
expectThrows(
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
new file mode 100644
index 0000000..467ede4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.external.localstorage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.GenericDocument;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
+import com.android.server.appsearch.proto.PutDocumentStatsProto;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.Collections;
+import java.util.List;
+
+public class AppSearchLoggerTest {
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private AppSearchImpl mAppSearchImpl;
+ private TestLogger mLogger;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+
+ // Give ourselves global query permissions
+ mAppSearchImpl =
+ AppSearchImpl.create(
+ mTemporaryFolder.newFolder(),
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ context.getPackageName());
+ mLogger = new TestLogger();
+ }
+
+ // Test only not thread safe.
+ public class TestLogger implements AppSearchLogger {
+ @Nullable PutDocumentStats mPutDocumentStats;
+
+ @Override
+ public void logStats(@NonNull CallStats stats) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void logStats(@NonNull PutDocumentStats stats) {
+ mPutDocumentStats = stats;
+ }
+ }
+
+ @Test
+ public void testAppSearchLoggerHelper_testCopyNativeStats_putDocument() {
+ final int nativeLatencyMillis = 3;
+ final int nativeDocumentStoreLatencyMillis = 4;
+ final int nativeIndexLatencyMillis = 5;
+ final int nativeIndexMergeLatencyMillis = 6;
+ final int nativeDocumentSize = 7;
+ final int nativeNumTokensIndexed = 8;
+ final boolean nativeExceededMaxNumTokens = true;
+ PutDocumentStatsProto nativePutDocumentStats =
+ PutDocumentStatsProto.newBuilder()
+ .setLatencyMs(nativeLatencyMillis)
+ .setDocumentStoreLatencyMs(nativeDocumentStoreLatencyMillis)
+ .setIndexLatencyMs(nativeIndexLatencyMillis)
+ .setIndexMergeLatencyMs(nativeIndexMergeLatencyMillis)
+ .setDocumentSize(nativeDocumentSize)
+ .setTokenizationStats(
+ PutDocumentStatsProto.TokenizationStats.newBuilder()
+ .setNumTokensIndexed(nativeNumTokensIndexed)
+ .setExceededMaxTokenNum(nativeExceededMaxNumTokens)
+ .build())
+ .build();
+ PutDocumentStats.Builder pBuilder = new PutDocumentStats.Builder("packageName", "database");
+
+ AppSearchLoggerHelper.copyNativeStats(nativePutDocumentStats, pBuilder);
+
+ PutDocumentStats pStats = pBuilder.build();
+ assertThat(pStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis);
+ assertThat(pStats.getNativeDocumentStoreLatencyMillis())
+ .isEqualTo(nativeDocumentStoreLatencyMillis);
+ assertThat(pStats.getNativeIndexLatencyMillis()).isEqualTo(nativeIndexLatencyMillis);
+ assertThat(pStats.getNativeIndexMergeLatencyMillis())
+ .isEqualTo(nativeIndexMergeLatencyMillis);
+ assertThat(pStats.getNativeDocumentSizeBytes()).isEqualTo(nativeDocumentSize);
+ assertThat(pStats.getNativeNumTokensIndexed()).isEqualTo(nativeNumTokensIndexed);
+ assertThat(pStats.getNativeExceededMaxNumTokens()).isEqualTo(nativeExceededMaxNumTokens);
+ }
+
+ //
+ // Testing actual logging
+ //
+ @Test
+ public void testLoggingStats_putDocument() throws Exception {
+ // Insert schema
+ final String testPackageName = "testPackage";
+ final String testDatabase = "testDatabase";
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ mAppSearchImpl.setSchema(
+ testPackageName,
+ testDatabase,
+ schemas,
+ /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+ /*schemasPackageAccessible=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false);
+ GenericDocument document =
+ new GenericDocument.Builder<>("namespace", "uri", "type").build();
+
+ mAppSearchImpl.putDocument(testPackageName, testDatabase, document, mLogger);
+
+ PutDocumentStats pStats = mLogger.mPutDocumentStats;
+ assertThat(pStats).isNotNull();
+ assertThat(pStats.getGeneralStats().getPackageName()).isEqualTo(testPackageName);
+ assertThat(pStats.getGeneralStats().getDatabase()).isEqualTo(testDatabase);
+ assertThat(pStats.getGeneralStats().getStatusCode()).isEqualTo(AppSearchResult.RESULT_OK);
+ // The rest of native stats have been tested in testCopyNativeStats
+ assertThat(pStats.getNativeDocumentSizeBytes()).isGreaterThan(0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
index 194be37..70e1e05 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -36,23 +36,23 @@
private static final byte[] BYTE_ARRAY_2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
private static final GenericDocument DOCUMENT_PROPERTIES_1 =
new GenericDocument.Builder<GenericDocument.Builder<?>>(
- "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+ "namespace", "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
.setCreationTimestampMillis(12345L)
.build();
private static final GenericDocument DOCUMENT_PROPERTIES_2 =
new GenericDocument.Builder<GenericDocument.Builder<?>>(
- "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+ "namespace", "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
.setCreationTimestampMillis(6789L)
.build();
@Test
public void testDocumentProtoConvert() {
GenericDocument document =
- new GenericDocument.Builder<GenericDocument.Builder<?>>("uri1", "schemaType1")
+ new GenericDocument.Builder<GenericDocument.Builder<?>>(
+ "namespace", "uri1", "schemaType1")
.setCreationTimestampMillis(5L)
.setScore(1)
.setTtlMillis(1L)
- .setNamespace("namespace")
.setPropertyLong("longKey1", 1L)
.setPropertyDouble("doubleKey1", 1.0)
.setPropertyBoolean("booleanKey1", true)
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
index 0b1c120..d07211f 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
@@ -93,10 +93,10 @@
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
assertThat(match.getFullText()).isEqualTo(propertyValueString);
assertThat(match.getExactMatch()).isEqualTo(exactMatch);
- assertThat(match.getExactMatchPosition())
+ assertThat(match.getExactMatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 32));
assertThat(match.getFullText()).isEqualTo(propertyValueString);
- assertThat(match.getSnippetPosition())
+ assertThat(match.getSnippetRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 32));
assertThat(match.getSnippet()).isEqualTo(window);
}
@@ -210,20 +210,20 @@
SearchResult.MatchInfo match1 = result.getMatches().get(0);
assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
assertThat(match1.getFullText()).isEqualTo("Test Name Jr.");
- assertThat(match1.getExactMatchPosition())
+ assertThat(match1.getExactMatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 4));
assertThat(match1.getExactMatch()).isEqualTo("Test");
- assertThat(match1.getSnippetPosition())
+ assertThat(match1.getSnippetRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 9));
assertThat(match1.getSnippet()).isEqualTo("Test Name");
SearchResult.MatchInfo match2 = result.getMatches().get(1);
assertThat(match2.getPropertyPath()).isEqualTo("sender.email");
assertThat(match2.getFullText()).isEqualTo("TestNameJr@gmail.com");
- assertThat(match2.getExactMatchPosition())
+ assertThat(match2.getExactMatchRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 20));
assertThat(match2.getExactMatch()).isEqualTo("TestNameJr@gmail.com");
- assertThat(match2.getSnippetPosition())
+ assertThat(match2.getSnippetRange())
.isEqualTo(new SearchResult.MatchRange(/*lower=*/ 0, /*upper=*/ 20));
assertThat(match2.getSnippet()).isEqualTo("TestNameJr@gmail.com");
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
index 4308885..8dbf249 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
@@ -18,12 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.appsearch.AppSearchResult;
+
import org.junit.Test;
public class AppSearchStatsTest {
static final String TEST_PACKAGE_NAME = "com.google.test";
static final String TEST_DATA_BASE = "testDataBase";
- static final int TEST_STATUS_CODE = 2;
+ static final int TEST_STATUS_CODE = AppSearchResult.RESULT_INTERNAL_ERROR;
static final int TEST_TOTAL_LATENCY_MILLIS = 20;
@Test
@@ -40,25 +42,38 @@
assertThat(gStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
}
+ /** Make sure status code is UNKNOWN if not set in {@link GeneralStats} */
+ @Test
+ public void testAppSearchStats_GeneralStats_defaultStatsCode_Unknown() {
+ final GeneralStats gStats =
+ new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
+ .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
+ .build();
+
+ assertThat(gStats.getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+ assertThat(gStats.getDatabase()).isEqualTo(TEST_DATA_BASE);
+ assertThat(gStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_UNKNOWN_ERROR);
+ assertThat(gStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
+ }
+
@Test
public void testAppSearchStats_CallStats() {
final int estimatedBinderLatencyMillis = 1;
final int numOperationsSucceeded = 2;
final int numOperationsFailed = 3;
-
- final GeneralStats gStats =
- new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
- .setStatusCode(TEST_STATUS_CODE)
- .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
- .build();
final @CallStats.CallType int callType = CallStats.CALL_TYPE_PUT_DOCUMENTS;
- final CallStats cStats =
- new CallStats.Builder(gStats)
+
+ final CallStats.Builder cStatsBuilder =
+ new CallStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
.setCallType(callType)
.setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
.setNumOperationsSucceeded(numOperationsSucceeded)
- .setNumOperationsFailed(numOperationsFailed)
- .build();
+ .setNumOperationsFailed(numOperationsFailed);
+ cStatsBuilder
+ .getGeneralStatsBuilder()
+ .setStatusCode(TEST_STATUS_CODE)
+ .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS);
+ final CallStats cStats = cStatsBuilder.build();
assertThat(cStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
assertThat(cStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE);
@@ -82,15 +97,9 @@
final int nativeIndexMergeLatencyMillis = 6;
final int nativeDocumentSize = 7;
final int nativeNumTokensIndexed = 8;
- final int nativeNumTokensClipped = 9;
-
- final GeneralStats gStats =
- new GeneralStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
- .setStatusCode(TEST_STATUS_CODE)
- .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
- .build();
- final PutDocumentStats pStats =
- new PutDocumentStats.Builder(gStats)
+ final boolean nativeExceededMaxNumTokens = true;
+ final PutDocumentStats.Builder pStatsBuilder =
+ new PutDocumentStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
.setGenerateDocumentProtoLatencyMillis(generateDocumentProtoLatencyMillis)
.setRewriteDocumentTypesLatencyMillis(rewriteDocumentTypesLatencyMillis)
.setNativeLatencyMillis(nativeLatencyMillis)
@@ -99,8 +108,12 @@
.setNativeIndexMergeLatencyMillis(nativeIndexMergeLatencyMillis)
.setNativeDocumentSizeBytes(nativeDocumentSize)
.setNativeNumTokensIndexed(nativeNumTokensIndexed)
- .setNativeNumTokensClipped(nativeNumTokensClipped)
- .build();
+ .setNativeExceededMaxNumTokens(nativeExceededMaxNumTokens);
+ pStatsBuilder
+ .getGeneralStatsBuilder()
+ .setStatusCode(TEST_STATUS_CODE)
+ .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS);
+ final PutDocumentStats pStats = pStatsBuilder.build();
assertThat(pStats.getGeneralStats().getPackageName()).isEqualTo(TEST_PACKAGE_NAME);
assertThat(pStats.getGeneralStats().getDatabase()).isEqualTo(TEST_DATA_BASE);
@@ -119,6 +132,6 @@
.isEqualTo(nativeIndexMergeLatencyMillis);
assertThat(pStats.getNativeDocumentSizeBytes()).isEqualTo(nativeDocumentSize);
assertThat(pStats.getNativeNumTokensIndexed()).isEqualTo(nativeNumTokensIndexed);
- assertThat(pStats.getNativeNumTokensClipped()).isEqualTo(nativeNumTokensClipped);
+ assertThat(pStats.getNativeExceededMaxNumTokens()).isEqualTo(nativeExceededMaxNumTokens);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
new file mode 100644
index 0000000..7afcbf7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.appsearch.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.SparseIntArray;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.appsearch.external.localstorage.MockPackageManager;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class PlatformLoggerTest {
+ private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
+ private static final int TEST_DEFAULT_SAMPLING_RATIO = 10;
+ private static final String TEST_PACKAGE_NAME = "packageName";
+ private MockPackageManager mMockPackageManager = new MockPackageManager();
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ mContext =
+ new ContextWrapper(context) {
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager.getMockPackageManager();
+ }
+ };
+ }
+
+ @Test
+ public void testcreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() {
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ TEST_DEFAULT_SAMPLING_RATIO,
+ /*samplingRatioMap=*/ null));
+
+ // Make sure default sampling ratio is used if samplingMap is not provided.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_UNKNOWN).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_QUERY).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ }
+
+
+ @Test
+ public void testcreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() {
+ int putDocumentSamplingRatio = 1;
+ int querySamplingRatio = 2;
+ final SparseIntArray samplingRatios = new SparseIntArray();
+ samplingRatios.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingRatio);
+ samplingRatios.put(CallStats.CALL_TYPE_QUERY, querySamplingRatio);
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ TEST_DEFAULT_SAMPLING_RATIO,
+ samplingRatios));
+
+ // The default sampling ratio should be used if no sampling ratio is
+ // provided for certain call type.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+
+ // The configured sampling ratio is used if sampling ratio is available
+ // for certain call type.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingRatio).isEqualTo(
+ putDocumentSamplingRatio);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_QUERY).mSamplingRatio).isEqualTo(
+ querySamplingRatio);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
+ final int samplingRatio = 1;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ samplingRatio,
+ /* samplingMap=*/ null));
+
+ // Sample should always be logged for the first time if sampling is disabled(value is one).
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_falseWhenSampleRatioIsNegative() {
+ final int samplingRatio = -1;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ samplingRatio,
+ /* samplingMap=*/ null));
+
+ // Makes sure sample will be excluded due to sampling if sample ratio is negative.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+ // Skipped count should be 0 since it doesn't pass the sampling.
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_falseWhenWithinCoolOffInterval() {
+ // Next sample won't be excluded due to sampling.
+ final int samplingRatio = 1;
+ // Next sample would guaranteed to be too close.
+ final int minTimeIntervalBetweenSamplesMillis = Integer.MAX_VALUE;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ minTimeIntervalBetweenSamplesMillis,
+ samplingRatio,
+ /* samplingMap=*/ null));
+ logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+ // Makes sure sample will be excluded due to rate limiting if samples are too close.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_trueWhenOutsideOfCoolOffInterval() {
+ // Next sample won't be excluded due to sampling.
+ final int samplingRatio = 1;
+ // Next sample would guaranteed to be included.
+ final int minTimeIntervalBetweenSamplesMillis = 0;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ minTimeIntervalBetweenSamplesMillis,
+ samplingRatio,
+ /* samplingMap=*/ null));
+ logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+ // Makes sure sample will be logged if it is not too close to previous sample.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ /** Makes sure the caching works while getting the UID for calling package. */
+ @Test
+ public void testGetPackageUidAsUser() throws Exception {
+ final String testPackageName = "packageName";
+ final int testUid = 1234;
+ PlatformLogger logger = new PlatformLogger(
+ mContext,
+ mContext.getUserId(),
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ TEST_DEFAULT_SAMPLING_RATIO,
+ /* samplingMap=*/ null));
+ mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
+
+ //
+ // First time, no cache
+ //
+ PlatformLogger.ExtraStats extraStats = logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+ verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
+ eq(testPackageName), /*userId=*/ anyInt());
+ assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+
+ //
+ // Second time, we have cache
+ //
+ extraStats = logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+ // Count is still one since we will use the cache
+ verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
+ eq(testPackageName), /*userId=*/ anyInt());
+ assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+
+ //
+ // Remove the cache and try again
+ //
+ assertThat(logger.removeCachedUidForPackage(testPackageName)).isEqualTo(testUid);
+ extraStats = logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+ // count increased by 1 since cache is cleared
+ verify(mMockPackageManager.getMockPackageManager(), times(2)).getPackageUidAsUser(
+ eq(testPackageName), /*userId=*/ anyInt());
+ assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 7a4b901..a6d146e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -39,6 +39,7 @@
import android.app.trust.ITrustManager;
import android.content.Context;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -292,9 +293,18 @@
}
});
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
mFingerprintSensorProps.add(new FingerprintSensorPropertiesInternal(id,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
+ componentInfo,
type,
false /* resetLockoutRequiresHardwareAuthToken */));
}
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 77a39d8..576f9c2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4016,6 +4016,27 @@
}
@Test
+ public void testUpdateNetworkPreferenceOnStartOnStopUser() throws Exception {
+ dpms.handleStartUser(CALLER_USER_HANDLE);
+ // TODO(b/178655595)
+ // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
+ // any(UserHandle.class),
+ // anyInt(),
+ // any(Executor.class),
+ // any(Runnable.class)
+ //);
+
+ dpms.handleStopUser(CALLER_USER_HANDLE);
+ // TODO(b/178655595)
+ // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
+ // any(UserHandle.class),
+ // eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT),
+ // any(Executor.class),
+ // any(Runnable.class)
+ //);
+ }
+
+ @Test
public void testGetSetNetworkSlicing() throws Exception {
assertExpectException(SecurityException.class, null,
() -> dpm.setNetworkSlicingEnabled(false));
@@ -4023,20 +4044,26 @@
assertExpectException(SecurityException.class, null,
() -> dpm.isNetworkSlicingEnabled());
- assertExpectException(SecurityException.class, null,
- () -> dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE)));
-
- mContext.callerPermissions.add(permission.READ_NETWORK_DEVICE_CONFIG);
- mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
- try {
- dpm.isNetworkSlicingEnabledForUser(UserHandle.of(CALLER_USER_HANDLE));
- } catch (SecurityException se) {
- fail("Threw SecurityException with right permission");
- }
-
setupProfileOwner();
dpm.setNetworkSlicingEnabled(false);
assertThat(dpm.isNetworkSlicingEnabled()).isFalse();
+ // TODO(b/178655595)
+ // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
+ // any(UserHandle.class),
+ // eq(ConnectivityManager.USER_PREFERENCE_SYSTEM_DEFAULT),
+ // any(Executor.class),
+ // any(Runnable.class)
+ //);
+
+ dpm.setNetworkSlicingEnabled(true);
+ assertThat(dpm.isNetworkSlicingEnabled()).isTrue();
+ // TODO(b/178655595)
+ // verify(getServices().connectivityManager, times(1)).setNetworkPreferenceForUser(
+ // any(UserHandle.class),
+ // eq(ConnectivityManager.USER_PREFERENCE_ENTERPRISE),
+ // any(Executor.class),
+ // any(Runnable.class)
+ //);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 6068fdf..2fcc021 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -224,6 +224,8 @@
return mMockSystemServices.accountManager;
case Context.TELEPHONY_SERVICE:
return mMockSystemServices.telephonyManager;
+ case Context.CONNECTIVITY_SERVICE:
+ return mMockSystemServices.connectivityManager;
case Context.APP_OPS_SERVICE:
return mMockSystemServices.appOpsManager;
case Context.CROSS_PROFILE_APPS_SERVICE:
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index f6dee38..9cc0572 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -47,6 +47,7 @@
import android.database.Cursor;
import android.hardware.usb.UsbManager;
import android.media.IAudioService;
+import android.net.ConnectivityManager;
import android.net.IIpConnectivityMetrics;
import android.net.Uri;
import android.net.wifi.WifiManager;
@@ -114,6 +115,7 @@
public final SettingsForMock settings;
public final MockContentResolver contentResolver;
public final TelephonyManager telephonyManager;
+ public final ConnectivityManager connectivityManager;
public final AccountManager accountManager;
public final AlarmManager alarmManager;
public final KeyChain.KeyChainConnection keyChainConnection;
@@ -159,6 +161,7 @@
wifiManager = mock(WifiManager.class);
settings = mock(SettingsForMock.class);
telephonyManager = mock(TelephonyManager.class);
+ connectivityManager = mock(ConnectivityManager.class);
accountManager = mock(AccountManager.class);
alarmManager = mock(AlarmManager.class);
keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54825ee..bf621b1 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -30,11 +30,13 @@
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,6 +45,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class AutomaticBrightnessControllerTest {
private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
@@ -55,9 +58,11 @@
private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
private static final int DISPLAY_ID = 0;
private static final int LAYER_STACK = 0;
+ private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
private Context mContext;
private LogicalDisplay mLogicalDisplay;
+ private AutomaticBrightnessController mController;
@Mock SensorManager mSensorManager;
@Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
@@ -65,8 +70,8 @@
@Mock HysteresisLevels mScreenBrightnessThresholds;
@Mock Handler mNoOpHandler;
@Mock DisplayDevice mDisplayDevice;
+ @Mock HighBrightnessModeController mHbmController;
- private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
@Before
public void setUp() {
// Share classloader to allow package private access.
@@ -77,6 +82,15 @@
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
}
+ @After
+ public void tearDown() {
+ if (mController != null) {
+ // Stop the update Brightness loop.
+ mController.stop();
+ mController = null;
+ }
+ }
+
private AutomaticBrightnessController setupController(Sensor lightSensor) {
AutomaticBrightnessController controller = new AutomaticBrightnessController(
new AutomaticBrightnessController.Injector() {
@@ -90,9 +104,12 @@
BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
- mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mLogicalDisplay, mContext
+ mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mLogicalDisplay,
+ mContext, mHbmController
);
- controller.setLoggingEnabled(true);
+
+ when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+ when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT);
// Configure the brightness controller and grab an instance of the sensor listener,
// through which we can deliver fake (for test) sensor values.
@@ -106,7 +123,7 @@
@Test
public void testNoHysteresisAtMinBrightness() throws Exception {
Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
- AutomaticBrightnessController controller = setupController(lightSensor);
+ mController = setupController(lightSensor);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -133,7 +150,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
- assertEquals(normalizedBrightness1, controller.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
// Set up system to return 0.0f (minimum possible brightness) as a brightness value
float lux2 = 10.0f;
@@ -147,13 +164,13 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
- assertEquals(normalizedBrightness2, controller.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
}
@Test
public void testNoHysteresisAtMaxBrightness() throws Exception {
Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
- AutomaticBrightnessController controller = setupController(lightSensor);
+ mController = setupController(lightSensor);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -179,7 +196,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
- assertEquals(normalizedBrightness1, controller.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
// Set up system to return 1.0f as a brightness value (brightness_max)
@@ -194,13 +211,13 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
- assertEquals(normalizedBrightness2, controller.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
}
@Test
public void testUserAddUserDataPoint() throws Exception {
Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
- AutomaticBrightnessController controller = setupController(lightSensor);
+ mController = setupController(lightSensor);
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
@@ -212,7 +229,7 @@
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000));
// User sets brightness to 100
- controller.configure(true /* enable */, null /* configuration */,
+ mController.configure(true /* enable */, null /* configuration */,
0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 893ce9e..bdf94f3 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -81,6 +81,7 @@
public class BrightnessTrackerTest {
private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f;
private static final boolean DEFAULT_COLOR_SAMPLING_ENABLED = true;
+ private static final String DEFAULT_DISPLAY_ID = "123";
private static final float FLOAT_DELTA = 0.01f;
private BrightnessTracker mTracker;
@@ -285,18 +286,20 @@
@Test
public void testBrightnessEvent() {
- final int brightness = 20;
+ final float brightness = 0.5f;
+ final String displayId = "1234";
startTracker(mTracker);
mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
- notifyBrightnessChanged(mTracker, brightness);
+ notifyBrightnessChanged(mTracker, brightness, displayId);
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
mTracker.stop();
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
+ assertEquals(displayId, event.uniqueDisplayId);
assertEquals(1, event.luxValues.length);
assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
assertEquals(mInjector.currentTimeMillis() - TimeUnit.SECONDS.toMillis(2),
@@ -314,6 +317,7 @@
public void testBrightnessFullPopulatedEvent() {
final int initialBrightness = 230;
final int brightness = 130;
+ final String displayId = "1234";
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333);
@@ -326,7 +330,7 @@
batteryChangeEvent(30, 60));
mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
final long sensorTime = mInjector.currentTimeMillis();
- notifyBrightnessChanged(mTracker, brightness);
+ notifyBrightnessChanged(mTracker, brightness, displayId);
List<BrightnessChangeEvent> eventsNoPackage
= mTracker.getEvents(0, false).getList();
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
@@ -335,6 +339,7 @@
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
assertEquals(event.timeStamp, mInjector.currentTimeMillis());
+ assertEquals(displayId, event.uniqueDisplayId);
assertArrayEquals(new float[] {1000.0f}, event.luxValues, 0.01f);
assertArrayEquals(new long[] {sensorTime}, event.luxTimestamps);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
@@ -364,7 +369,7 @@
final int systemUpdatedBrightness = 20;
notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/,
0.5f /*powerBrightnessFactor(*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/);
+ false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
// No events because we filtered out our change.
assertEquals(0, events.size());
@@ -455,6 +460,7 @@
+ "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
+ "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
+ "reduceBrightColorsOffset=\"0\"\n"
+ + "uniqueDisplayId=\"123\""
+ "lux=\"32.2,31.1\" luxTimestamps=\""
+ Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
+ "defaultConfig=\"true\" powerSaveFactor=\"0.5\" userPoint=\"true\" />"
@@ -465,6 +471,7 @@
+ "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\" "
+ "reduceBrightColors=\"true\" reduceBrightColorsStrength=\"40\" "
+ "reduceBrightColorsOffset=\"0\"\n"
+ + "uniqueDisplayId=\"456\""
+ "lux=\"132.2,131.1\" luxTimestamps=\""
+ Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
+ "colorSampleDuration=\"3456\" colorValueBuckets=\"123,598,23,19\"/>"
@@ -476,6 +483,7 @@
+ "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
+ "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
+ "reduceBrightColorsOffset=\"0\"\n"
+ + "uniqueDisplayId=\"789\""
+ "lux=\"32.2,31.1\" luxTimestamps=\""
+ Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>"
+ "</events>";
@@ -485,6 +493,7 @@
BrightnessChangeEvent event = events.get(0);
assertEquals(someTimeAgo, event.timeStamp);
assertEquals(194.2, event.brightness, FLOAT_DELTA);
+ assertEquals("123", event.uniqueDisplayId);
assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA);
assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
assertEquals(32.333, event.lastBrightness, FLOAT_DELTA);
@@ -503,6 +512,7 @@
event = events.get(0);
assertEquals(someTimeAgo, event.timeStamp);
assertEquals(71, event.brightness, FLOAT_DELTA);
+ assertEquals("456", event.uniqueDisplayId);
assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA);
assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
assertEquals(32, event.lastBrightness, FLOAT_DELTA);
@@ -575,6 +585,7 @@
@Test
public void testWriteThenRead() throws Exception {
final int brightness = 20;
+ final String displayId = "1234";
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
@@ -593,7 +604,7 @@
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
notifyBrightnessChanged(mTracker, brightness, true /*userInitiated*/,
0.5f /*powerBrightnessFactor*/, true /*hasUserBrightnessPoints*/,
- false /*isDefaultBrightnessConfig*/);
+ false /*isDefaultBrightnessConfig*/, displayId);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mTracker.writeEventsLocked(baos);
mTracker.stop();
@@ -607,6 +618,7 @@
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
+ assertEquals(displayId, event.uniqueDisplayId);
assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
assertArrayEquals(new long[] {firstSensorTime, secondSensorTime}, event.luxTimestamps);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
@@ -678,6 +690,7 @@
builder.setTimeStamp(345L);
builder.setPackageName("com.example");
builder.setUserId(12);
+ builder.setUniqueDisplayId("9876");
float[] luxValues = new float[2];
luxValues[0] = 3000.0f;
luxValues[1] = 4000.0f;
@@ -710,6 +723,7 @@
assertEquals(event.timeStamp, event2.timeStamp);
assertEquals(event.packageName, event2.packageName);
assertEquals(event.userId, event2.userId);
+ assertEquals(event.uniqueDisplayId, event2.uniqueDisplayId);
assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA);
assertArrayEquals(event.luxTimestamps, event2.luxTimestamps);
assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
@@ -773,7 +787,7 @@
long eventTime = mInjector.currentTimeMillis();
mTracker.notifyBrightnessChanged(brightness, true /*userInitiated*/,
1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/);
+ false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
// Time passes before handler can run.
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
@@ -791,6 +805,35 @@
assertEquals(eventTime, event.timeStamp);
}
+ @Test
+ public void testDisplayIdChange() {
+ float firstBrightness = 0.5f;
+ float secondBrightness = 0.75f;
+ String firstDisplayId = "123";
+ String secondDisplayId = "456";
+
+ startTracker(mTracker);
+ mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
+
+ notifyBrightnessChanged(mTracker, firstBrightness, firstDisplayId);
+ mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+ List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+ assertEquals(1, events.size());
+ BrightnessChangeEvent firstEvent = events.get(0);
+ assertEquals(firstDisplayId, firstEvent.uniqueDisplayId);
+ assertEquals(firstBrightness, firstEvent.brightness, 0.001f);
+
+ notifyBrightnessChanged(mTracker, secondBrightness, secondDisplayId);
+ mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+ events = mTracker.getEvents(0, true).getList();
+ assertEquals(2, events.size());
+ BrightnessChangeEvent secondEvent = events.get(1);
+ assertEquals(secondDisplayId, secondEvent.uniqueDisplayId);
+ assertEquals(secondBrightness, secondEvent.brightness, 0.001f);
+
+ mTracker.stop();
+ }
+
private InputStream getInputStream(String data) {
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
}
@@ -831,16 +874,21 @@
}
private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) {
+ notifyBrightnessChanged(tracker, brightness, DEFAULT_DISPLAY_ID);
+ }
+
+ private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+ String displayId) {
notifyBrightnessChanged(tracker, brightness, true /*userInitiated*/,
1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/);
+ false /*isDefaultBrightnessConfig*/, displayId);
}
private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
- boolean isDefaultBrightnessConfig) {
+ boolean isDefaultBrightnessConfig, String displayId) {
tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
- isUserSetBrightness, isDefaultBrightnessConfig);
+ isUserSetBrightness, isDefaultBrightnessConfig, displayId);
mInjector.waitForHandler();
}
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 47f3bf9..b5336e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -45,7 +45,6 @@
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
-import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -667,7 +666,6 @@
@Test
public void initCecVersion_limitToMinimumSupportedVersion() {
mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- Log.e("MARVIN", "set setting CEC");
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
@@ -679,9 +677,7 @@
@Test
public void initCecVersion_limitToAtLeast1_4() {
- Log.e("MARVIN", "set HAL CEC to 0");
mNativeWrapper.setCecVersion(0x0);
- Log.e("MARVIN", "set setting CEC to 2");
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
@@ -694,7 +690,6 @@
@Test
public void initCecVersion_useHighestMatchingVersion() {
mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0);
- Log.e("MARVIN", "set setting CEC");
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 605f781..53b4b49 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -19,7 +19,6 @@
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
-import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -51,6 +50,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.concurrent.TimeUnit;
/** Tests for {@link ActiveSourceAction} */
@@ -84,7 +84,8 @@
when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
- mHdmiControlService = new HdmiControlService(mContextSpy) {
+ mHdmiControlService = new HdmiControlService(mContextSpy,
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -140,6 +141,7 @@
mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
}
@Test
@@ -152,7 +154,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
- ADDR_TV,
+ mTvDevice.mAddress,
ADDR_PLAYBACK_1);
assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
@@ -191,7 +193,7 @@
mTestLooper.dispatchAll();
HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
- ADDR_TV,
+ mTvDevice.mAddress,
ADDR_PLAYBACK_1);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
@@ -220,12 +222,12 @@
mTestLooper.dispatchAll();
HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
- ADDR_TV,
+ mTvDevice.mAddress,
ADDR_PLAYBACK_1);
assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
- ADDR_TV,
+ mTvDevice.mAddress,
ADDR_PLAYBACK_2);
assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus2);
}
@@ -245,13 +247,13 @@
mTestLooper.dispatchAll();
HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
- ADDR_TV,
+ mTvDevice.mAddress,
ADDR_PLAYBACK_1);
assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
- ADDR_TV,
+ mTvDevice.mAddress,
ADDR_PLAYBACK_2);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus2);
@@ -265,7 +267,7 @@
}
private void reportPowerStatus(int logicalAddress, boolean broadcast, int powerStatus) {
- int destination = broadcast ? ADDR_BROADCAST : ADDR_TV;
+ int destination = broadcast ? ADDR_BROADCAST : mTvDevice.mAddress;
HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
logicalAddress, destination,
powerStatus);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
index 0784b7a..415e635 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -25,6 +25,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+
import android.content.integrity.AppInstallMetadata;
import org.junit.Test;
@@ -163,6 +165,22 @@
new RuleIndexRange(900, 945));
}
+ @Test
+ public void verifyIndexingFileIsCorrupt() throws IOException {
+ byte[] stringBytes =
+ getBytes(
+ getKeyValueString(START_INDEXING_KEY, 100)
+ + getKeyValueString("ccc", 200)
+ + getKeyValueString(END_INDEXING_KEY, 300)
+ + getKeyValueString(END_INDEXING_KEY, 900));
+ ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+ rule.put(stringBytes);
+ InputStream inputStream = new ByteArrayInputStream(rule.array());
+
+ assertThrows(IllegalStateException.class,
+ () -> new RuleIndexingController(inputStream));
+ }
+
private static InputStream obtainDefaultIndexingMapForTest() {
byte[] stringBytes =
getBytes(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index a38745f..d9af51f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -42,7 +42,6 @@
import android.content.Context;
import android.os.FileUtils;
-import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.recovery.KeyChainSnapshot;
@@ -109,7 +108,7 @@
private RecoverySnapshotStorage mRecoverySnapshotStorage;
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
private File mDatabaseFile;
- private AndroidKeyStoreSecretKey mWrappingKey;
+ private SecretKey mWrappingKey;
private PlatformEncryptionKey mEncryptKey;
private KeySyncTask mKeySyncTask;
@@ -848,7 +847,7 @@
return keyGenerator.generateKey();
}
- private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+ private SecretKey generateAndroidKeyStoreKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KEY_ALGORITHM,
ANDROID_KEY_STORE_PROVIDER);
@@ -857,7 +856,7 @@
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
- return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+ return keyGenerator.generateKey();
}
private static byte[] utf8Bytes(String s) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index c295177..6413026 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertNull;
import android.content.Context;
-import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
@@ -45,6 +44,7 @@
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
@SmallTest
@@ -77,7 +77,7 @@
mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
- AndroidKeyStoreSecretKey platformKey = generatePlatformKey();
+ SecretKey platformKey = generatePlatformKey();
mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey);
mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey);
mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb);
@@ -168,7 +168,7 @@
assertArrayEquals(rawMaterial, unwrappedMaterial);
}
- private AndroidKeyStoreSecretKey generatePlatformKey() throws Exception {
+ private SecretKey generatePlatformKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KEY_ALGORITHM,
ANDROID_KEY_STORE_PROVIDER);
@@ -177,7 +177,7 @@
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
- return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+ return keyGenerator.generateKey();
}
private static byte[] randomBytes(int n) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index b65e487..a227cd3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -45,7 +45,6 @@
import android.os.Binder;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
-import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.recovery.KeyChainProtectionParams;
@@ -1311,7 +1310,7 @@
mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
}
- private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+ private SecretKey generateAndroidKeyStoreKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KEY_ALGORITHM,
ANDROID_KEY_STORE_PROVIDER);
@@ -1320,6 +1319,6 @@
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
- return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+ return keyGenerator.generateKey();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
index 9813ab7..60052f7 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
@@ -21,7 +21,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Pair;
@@ -117,7 +116,7 @@
@Test
public void decryptWrappedKeys_decryptsWrappedKeys_nullMetadata() throws Exception {
String alias = "karlin";
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ SecretKey platformKey = generateAndroidKeyStoreKey();
SecretKey appKey = generateKey();
WrappedKey wrappedKey = WrappedKey.fromSecretKey(
new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NULL_METADATA);
@@ -136,7 +135,7 @@
@Test
public void decryptWrappedKeys_decryptsWrappedKeys_nonNullMetadata() throws Exception {
String alias = "karlin";
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ SecretKey platformKey = generateAndroidKeyStoreKey();
SecretKey appKey = generateKey();
WrappedKey wrappedKey = WrappedKey.fromSecretKey(
new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NON_NULL_METADATA);
@@ -155,7 +154,7 @@
@Test
public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception {
String alias = "karlin";
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ SecretKey platformKey = generateAndroidKeyStoreKey();
SecretKey appKey = generateKey();
WrappedKey wrappedKey = WrappedKey.fromSecretKey(
new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey, NULL_METADATA);
@@ -171,7 +170,7 @@
@Test
public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception {
- AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+ SecretKey platformKey = generateAndroidKeyStoreKey();
WrappedKey wrappedKey = WrappedKey.fromSecretKey(
new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey(),
/*metadata=*/ null);
@@ -197,7 +196,7 @@
return keyGenerator.generateKey();
}
- private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
+ private SecretKey generateAndroidKeyStoreKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KEY_ALGORITHM,
ANDROID_KEY_STORE_PROVIDER);
@@ -207,6 +206,6 @@
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
- return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
+ return keyGenerator.generateKey();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 9a52643..9f428c7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -31,6 +31,7 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.pm.parsing.ParsingPackage;
@@ -141,6 +142,10 @@
return pkg(packageName).addReceiver(receiver);
}
+ private static ParsingPackage pkgWithSharedLibrary(String packageName, String libName) {
+ return pkg(packageName).addLibraryName(libName);
+ }
+
private static ParsedActivity createActivity(String packageName, IntentFilter[] filters) {
ParsedActivity activity = new ParsedActivity();
activity.setPackageName(packageName);
@@ -413,6 +418,118 @@
}
@Test
+ public void testNoUsesLibrary_Filters() throws Exception {
+ final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
+ mMockExecutor);
+
+ simulateAddBasicAndroid(appsFilter);
+ appsFilter.onSystemReady();
+
+ final Signature mockSignature = Mockito.mock(Signature.class);
+ final SigningDetails mockSigningDetails = new SigningDetails(
+ new Signature[]{mockSignature},
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+ final PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+ DUMMY_TARGET_APPID,
+ setting -> setting.setSigningDetails(mockSigningDetails)
+ .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+ final PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package"), DUMMY_CALLING_APPID);
+
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
+ }
+
+ @Test
+ public void testUsesLibrary_DoesntFilter() throws Exception {
+ final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
+ mMockExecutor);
+
+ simulateAddBasicAndroid(appsFilter);
+ appsFilter.onSystemReady();
+
+ final Signature mockSignature = Mockito.mock(Signature.class);
+ final SigningDetails mockSigningDetails = new SigningDetails(
+ new Signature[]{mockSignature},
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+ final PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+ DUMMY_TARGET_APPID,
+ setting -> setting.setSigningDetails(mockSigningDetails)
+ .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+ final PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package").addUsesLibrary("com.some.shared_library"),
+ DUMMY_CALLING_APPID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
+ }
+
+ @Test
+ public void testUsesOptionalLibrary_DoesntFilter() throws Exception {
+ final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
+ mMockExecutor);
+
+ simulateAddBasicAndroid(appsFilter);
+ appsFilter.onSystemReady();
+
+ final Signature mockSignature = Mockito.mock(Signature.class);
+ final SigningDetails mockSigningDetails = new SigningDetails(
+ new Signature[]{mockSignature},
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+ final PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+ DUMMY_TARGET_APPID,
+ setting -> setting.setSigningDetails(mockSigningDetails)
+ .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+ final PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package").addUsesOptionalLibrary("com.some.shared_library"),
+ DUMMY_CALLING_APPID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
+ }
+
+ @Test
+ public void testUsesLibrary_ShareUid_DoesntFilter() throws Exception {
+ final AppsFilter appsFilter = new AppsFilter(mStateProvider, mFeatureConfigMock,
+ new String[]{}, /* systemAppsQueryable */ false, /* overlayProvider */ null,
+ mMockExecutor);
+
+ simulateAddBasicAndroid(appsFilter);
+ appsFilter.onSystemReady();
+
+ final Signature mockSignature = Mockito.mock(Signature.class);
+ final SigningDetails mockSigningDetails = new SigningDetails(
+ new Signature[]{mockSignature},
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2);
+
+ final PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithSharedLibrary("com.some.package", "com.some.shared_library"),
+ DUMMY_TARGET_APPID,
+ setting -> setting.setSigningDetails(mockSigningDetails)
+ .setPkgFlags(ApplicationInfo.FLAG_SYSTEM));
+ final PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package_a").setSharedUserId("com.some.uid"),
+ DUMMY_CALLING_APPID);
+ simulateAddPackage(appsFilter, pkg("com.some.other.package_b")
+ .setSharedUserId("com.some.uid").addUsesLibrary("com.some.shared_library"),
+ DUMMY_CALLING_APPID);
+
+ // Although package_a doesn't use library, it should be granted visibility. It's because
+ // package_a shares userId with package_b, and package_b uses that shared library.
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target,
+ SYSTEM_USER));
+ }
+
+ @Test
public void testForceQueryable_SystemDoesntFilter() throws Exception {
final AppsFilter appsFilter =
new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null,
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 59458e8..d63a467 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -59,10 +59,10 @@
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.LocalServices;
-import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.utils.WatchableTester;
import com.google.common.truth.Truth;
@@ -1197,7 +1197,7 @@
private Settings makeSettings() {
return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
mRuntimePermissionsPersistence, mPermissionDataProvider,
- mDomainVerificationManager, new Object());
+ mDomainVerificationManager, new PackageManagerTracedLock());
}
private void verifyKeySetMetaData(Settings settings)
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index ba60111..128cbaa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -48,6 +48,7 @@
import android.content.pm.parsing.component.ParsedPermissionGroup;
import android.content.pm.parsing.component.ParsedProvider;
import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.component.ParsedUsesPermission;
import android.os.Bundle;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -854,7 +855,7 @@
.addProvider(new ParsedProvider())
.addService(new ParsedService())
.addInstrumentation(new ParsedInstrumentation())
- .addRequestedPermission("foo7")
+ .addUsesPermission(new ParsedUsesPermission("foo7", 0))
.addImplicitPermission("foo25")
.addProtectedBroadcast("foo8")
.setStaticSharedLibName("foo23")
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index b5add84..8e1fc16 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -43,6 +43,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.component.ParsedUsesPermission;
import android.content.res.TypedArray;
import android.os.Environment;
import android.os.UserHandle;
@@ -429,7 +430,7 @@
@Test
public void factoryTestFlagSet() throws Exception {
final ParsingPackage basicPackage = createBasicPackage(DUMMY_PACKAGE_NAME)
- .addRequestedPermission(Manifest.permission.FACTORY_TEST);
+ .addUsesPermission(new ParsedUsesPermission(Manifest.permission.FACTORY_TEST, 0));
final PackageManagerService.ScanResult scanResult = PackageManagerService.scanPackageOnlyLI(
createBasicScanRequestBuilder(basicPackage).build(),
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java
new file mode 100644
index 0000000..e605d75
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java
@@ -0,0 +1,300 @@
+/*
+ * 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.dex;
+
+import static org.mockito.Mockito.inOrder;
+
+import com.android.internal.art.ArtStatsLog;
+import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Unit tests for {@link com.android.server.pm.dex.ArtStatsLogUtils}.
+ *
+ * Run with "atest ArtStatsLogUtilsTest".
+ */
+@RunWith(JUnit4.class)
+public final class ArtStatsLogUtilsTest {
+ private static final String TAG = ArtStatsLogUtilsTest.class.getSimpleName();
+ private static final String COMPILER_FILTER = "space-profile";
+ private static final String PROFILE_DEX_METADATA = "primary.prof";
+ private static final String VDEX_DEX_METADATA = "primary.vdex";
+ private static final byte[] DEX_CONTENT = "dexData".getBytes();
+ private static final int COMPILATION_REASON = 1;
+ private static final int RESULT_CODE = 222;
+ private static final int UID = 111;
+ private static final long COMPILE_TIME = 333L;
+ private static final long SESSION_ID = 444L;
+
+ @Mock
+ ArtStatsLogger mockLogger;
+
+ private static Path TEST_DIR;
+ private static Path DEX;
+ private static Path NON_DEX;
+
+ @BeforeClass
+ public static void setUpAll() throws IOException {
+ TEST_DIR = Files.createTempDirectory(null);
+ DEX = Files.createFile(TEST_DIR.resolve("classes.dex"));
+ NON_DEX = Files.createFile(TEST_DIR.resolve("test.dex"));
+ Files.write(DEX, DEX_CONTENT);
+ Files.write(NON_DEX, "empty".getBytes());
+ }
+
+ @AfterClass
+ public static void tearnDownAll() {
+ deleteSliently(DEX);
+ deleteSliently(NON_DEX);
+ deleteSliently(TEST_DIR);
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testProfileAndVdexDexMetadata() throws IOException {
+ // Setup
+ Path dexMetadataPath = null;
+ Path apk = null;
+ try {
+ dexMetadataPath = createDexMetadata(PROFILE_DEX_METADATA, VDEX_DEX_METADATA);
+ apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+ // Act
+ ArtStatsLogUtils.writeStatsLog(
+ mockLogger,
+ SESSION_ID,
+ apk.toString(),
+ COMPILER_FILTER,
+ UID,
+ COMPILE_TIME,
+ dexMetadataPath.toString(),
+ COMPILATION_REASON,
+ RESULT_CODE);
+
+ // Assert
+ verifyWrites(ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX);
+ } finally {
+ deleteSliently(dexMetadataPath);
+ deleteSliently(apk);
+ }
+ }
+
+ @Test
+ public void testProfileOnlyDexMetadata() throws IOException {
+ // Setup
+ Path dexMetadataPath = null;
+ Path apk = null;
+ try {
+ dexMetadataPath = createDexMetadata(PROFILE_DEX_METADATA);
+ apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+ // Act
+ ArtStatsLogUtils.writeStatsLog(
+ mockLogger,
+ SESSION_ID,
+ apk.toString(),
+ COMPILER_FILTER,
+ UID,
+ COMPILE_TIME,
+ dexMetadataPath.toString(),
+ COMPILATION_REASON,
+ RESULT_CODE);
+
+ // Assert
+ verifyWrites(ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE);
+ } finally {
+ deleteSliently(dexMetadataPath);
+ deleteSliently(apk);
+ }
+ }
+
+ @Test
+ public void testVdexOnlyDexMetadata() throws IOException {
+ // Setup
+ Path dexMetadataPath = null;
+ Path apk = null;
+ try {
+ dexMetadataPath = createDexMetadata(VDEX_DEX_METADATA);
+ apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+ // Act
+ ArtStatsLogUtils.writeStatsLog(
+ mockLogger,
+ SESSION_ID,
+ apk.toString(),
+ COMPILER_FILTER,
+ UID,
+ COMPILE_TIME,
+ dexMetadataPath.toString(),
+ COMPILATION_REASON,
+ RESULT_CODE);
+
+ // Assert
+ verifyWrites(ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX);
+ } finally {
+ deleteSliently(dexMetadataPath);
+ deleteSliently(apk);
+ }
+ }
+
+ @Test
+ public void testNoneDexMetadata() throws IOException {
+ // Setup
+ Path apk = null;
+ try {
+ apk = zipFiles(".apk", DEX, NON_DEX);
+
+ // Act
+ ArtStatsLogUtils.writeStatsLog(
+ mockLogger,
+ SESSION_ID,
+ apk.toString(),
+ COMPILER_FILTER,
+ UID,
+ COMPILE_TIME,
+ /*dexMetadataPath=*/ null,
+ COMPILATION_REASON,
+ RESULT_CODE);
+
+ // Assert
+ verifyWrites(ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE);
+ } finally {
+ deleteSliently(apk);
+ }
+ }
+
+ @Test
+ public void testUnKnownDexMetadata() throws IOException {
+ // Setup
+ Path dexMetadataPath = null;
+ Path apk = null;
+ try {
+ dexMetadataPath = createDexMetadata("unknown");
+ apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
+
+ // Act
+ ArtStatsLogUtils.writeStatsLog(
+ mockLogger,
+ SESSION_ID,
+ apk.toString(),
+ COMPILER_FILTER,
+ UID,
+ COMPILE_TIME,
+ dexMetadataPath.toString(),
+ COMPILATION_REASON,
+ RESULT_CODE);
+
+ // Assert
+ verifyWrites(ArtStatsLog.
+ ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN);
+ } finally {
+ deleteSliently(dexMetadataPath);
+ deleteSliently(apk);
+ }
+ }
+
+ private void verifyWrites(int dexMetadataType) {
+ InOrder inorder = inOrder(mockLogger);
+ inorder.verify(mockLogger).write(
+ SESSION_ID, UID,
+ COMPILATION_REASON,
+ COMPILER_FILTER,
+ ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_RESULT_CODE,
+ RESULT_CODE,
+ dexMetadataType);
+ inorder.verify(mockLogger).write(
+ SESSION_ID,
+ UID,
+ COMPILATION_REASON,
+ COMPILER_FILTER,
+ ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_DEX_CODE_BYTES,
+ DEX_CONTENT.length,
+ dexMetadataType);
+ inorder.verify(mockLogger).write(
+ SESSION_ID,
+ UID,
+ COMPILATION_REASON,
+ COMPILER_FILTER,
+ ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_TOTAL_TIME,
+ COMPILE_TIME,
+ dexMetadataType);
+ }
+
+ private Path zipFiles(String suffix, Path... files) throws IOException {
+ Path zipFile = Files.createTempFile(null, suffix);
+ try (final OutputStream os = Files.newOutputStream(zipFile)) {
+ try (final ZipOutputStream zos = new ZipOutputStream(os)) {
+ for (Path file : files) {
+ ZipEntry zipEntry = new ZipEntry(file.getFileName().toString());
+ zos.putNextEntry(zipEntry);
+ zos.write(Files.readAllBytes(file));
+ zos.closeEntry();
+ }
+ }
+ }
+ return zipFile;
+ }
+
+ private Path createDexMetadata(String... entryNames) throws IOException {
+ Path zipFile = Files.createTempFile(null, ".dm");
+ try (final OutputStream os = Files.newOutputStream(zipFile)) {
+ try (final ZipOutputStream zos = new ZipOutputStream(os)) {
+ for (String entryName : entryNames) {
+ ZipEntry zipEntry = new ZipEntry(entryName);
+ zos.putNextEntry(zipEntry);
+ zos.write(entryName.getBytes());
+ zos.closeEntry();
+ }
+ }
+ }
+ return zipFile;
+ }
+
+ private static void deleteSliently(Path file) {
+ if (file != null) {
+ try {
+ Files.deleteIfExists(file);
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+}
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 9b8a2a8..324e592 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
@@ -18,6 +18,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
+import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -70,6 +71,7 @@
private FileWriter mUncryptUpdateFileWriter;
private LockSettingsInternal mLockSettingsInternal;
private IBootControl mIBootControl;
+ private RecoverySystemServiceTestable.IMetricsReporter mMetricsReporter;
private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package";
private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package";
@@ -94,9 +96,11 @@
when(mIBootControl.getCurrentSlot()).thenReturn(0);
when(mIBootControl.getActiveBootSlot()).thenReturn(1);
+ mMetricsReporter = mock(RecoverySystemServiceTestable.IMetricsReporter.class);
+
mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal,
- mIBootControl);
+ mIBootControl, mMetricsReporter);
}
@Test
@@ -227,12 +231,24 @@
}
@Test
+ public void requestLskf_reportMetrics() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
+ verify(mMetricsReporter).reportRebootEscrowPreparationMetrics(
+ eq(1000), eq(0) /* need preparation */, eq(1) /* client count */);
+ }
+
+
+ @Test
public void requestLskf_success() throws Exception {
IntentSender intentSender = mock(IntentSender.class);
assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
is(true));
mRecoverySystemService.onPreparedForReboot(true);
verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+ verify(mMetricsReporter).reportRebootEscrowLskfCapturedMetrics(
+ eq(1000), eq(1) /* client count */, anyInt() /* duration */);
}
@Test
@@ -255,6 +271,8 @@
assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
is(true));
verify(intentSender, never()).sendIntent(any(), anyInt(), any(), any(), any());
+ verify(mMetricsReporter, never()).reportRebootEscrowLskfCapturedMetrics(
+ anyInt(), anyInt(), anyInt());
}
@Test
@@ -337,6 +355,9 @@
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 */);
}
@@ -373,6 +394,20 @@
verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
}
+ @Test
+ public void rebootWithLskf_multiClient_success_reportMetrics() throws Exception {
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+
+ // Client B's clear won't affect client A's preparation.
+ 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 */);
+ }
@Test
public void rebootWithLskf_multiClient_ClientBSuccess() throws Exception {
@@ -384,12 +419,18 @@
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 */);
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 */);
}
@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 0727e5a..a894178 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
@@ -32,11 +32,12 @@
private final UncryptSocket mUncryptSocket;
private final LockSettingsInternal mLockSettingsInternal;
private final IBootControl mIBootControl;
+ private final IMetricsReporter mIMetricsReporter;
MockInjector(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
- IBootControl bootControl) {
+ IBootControl bootControl, IMetricsReporter metricsReporter) {
super(context);
mSystemProperties = systemProperties;
mPowerManager = powerManager;
@@ -44,6 +45,7 @@
mUncryptSocket = uncryptSocket;
mLockSettingsInternal = lockSettingsInternal;
mIBootControl = bootControl;
+ mIMetricsReporter = metricsReporter;
}
@Override
@@ -94,14 +96,45 @@
public IBootControl getBootControl() {
return mIBootControl;
}
+ @Override
+ public int getUidFromPackageName(String packageName) {
+ if ("fake.ota.package".equals(packageName)) {
+ return 1000;
+ }
+ if ("fake.other.package".equals(packageName)) {
+ return 2000;
+ }
+ return 3000;
+ }
+
+ @Override
+ public void reportRebootEscrowPreparationMetrics(int uid, int requestResult,
+ int requestedClientCount) {
+ mIMetricsReporter.reportRebootEscrowPreparationMetrics(uid, requestResult,
+ requestedClientCount);
+ }
+
+ public void reportRebootEscrowLskfCapturedMetrics(int uid, int requestedClientCount,
+ int requestedToLskfCapturedDurationInSeconds) {
+ mIMetricsReporter.reportRebootEscrowLskfCapturedMetrics(uid, requestedClientCount,
+ requestedToLskfCapturedDurationInSeconds);
+ }
+
+ public void reportRebootEscrowRebootMetrics(int errorCode, int uid, int preparedClientCount,
+ int requestCount, boolean slotSwitch, boolean serverBased,
+ int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts) {
+ mIMetricsReporter.reportRebootEscrowRebootMetrics(errorCode, uid, preparedClientCount,
+ requestCount, slotSwitch, serverBased, lskfCapturedToRebootDurationInSeconds,
+ lskfCapturedCounts);
+ }
}
RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
- IBootControl bootControl) {
+ IBootControl bootControl, IMetricsReporter metricsReporter) {
super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter,
- uncryptSocket, lockSettingsInternal, bootControl));
+ uncryptSocket, lockSettingsInternal, bootControl, metricsReporter));
}
public static class FakeSystemProperties {
@@ -131,4 +164,17 @@
return mCtlStart;
}
}
+
+ public interface IMetricsReporter {
+ void reportRebootEscrowPreparationMetrics(int uid, int requestResult,
+ int requestedClientCount);
+
+ void reportRebootEscrowLskfCapturedMetrics(int uid, int requestedClientCount,
+ int requestedToLskfCapturedDurationInSeconds);
+
+ void reportRebootEscrowRebootMetrics(int errorCode, int uid, int preparedClientCount,
+ int requestCount, boolean slotSwitch, boolean serverBased,
+ int lskfCapturedToRebootDurationInSeconds, int lskfCapturedCounts);
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
new file mode 100644
index 0000000..d4222e6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timedetector/ConfigurationInternalTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timedetector;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.time.Capabilities;
+import android.app.time.TimeCapabilities;
+import android.app.time.TimeCapabilitiesAndConfig;
+import android.app.time.TimeConfiguration;
+import android.os.UserHandle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ConfigurationInternalTest {
+
+ @Test
+ public void capabilitiesAndConfig() {
+ int userId = 112233;
+ ConfigurationInternal configurationInternal = new ConfigurationInternal.Builder(userId)
+ .setAutoDetectionEnabled(true)
+ .setUserConfigAllowed(true)
+ .build();
+
+ TimeCapabilities timeCapabilities = new TimeCapabilities.Builder(UserHandle.of(userId))
+ .setConfigureAutoTimeDetectionEnabledCapability(Capabilities.CAPABILITY_POSSESSED)
+ .setSuggestTimeManuallyCapability(Capabilities.CAPABILITY_POSSESSED)
+ .build();
+ TimeConfiguration timeConfiguration = new TimeConfiguration.Builder()
+ .setAutoDetectionEnabled(true)
+ .build();
+ TimeCapabilitiesAndConfig expected =
+ new TimeCapabilitiesAndConfig(timeCapabilities, timeConfiguration);
+
+ assertThat(configurationInternal.capabilitiesAndConfig()).isEqualTo(expected);
+ }
+
+}
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 bbf11fd..1068270 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -328,6 +328,11 @@
}
@Override
+ public ConfigurationInternal getConfigurationInternal(int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void handleAutoTimeConfigChanged() {
mHandleAutoTimeDetectionChangedCalled = true;
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index f7a498b..095703e 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -1158,6 +1158,11 @@
}
@Override
+ public ConfigurationInternal configurationInternal(int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void acquireWakeLock() {
if (mWakeLockAcquired) {
fail("Wake lock already acquired");
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 0036982..aa46e7e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -16,10 +16,10 @@
package com.android.server.timezonedetector;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.Capabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 60390dc..b2dacab 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -261,6 +261,7 @@
// mEndTimeStamp is based on the enclosing IntervalStats, don't bother checking
assertEquals(us1.mLastTimeUsed, us2.mLastTimeUsed);
assertEquals(us1.mLastTimeVisible, us2.mLastTimeVisible);
+ assertEquals(us1.mLastTimeComponentUsed, us2.mLastTimeComponentUsed);
assertEquals(us1.mTotalTimeInForeground, us2.mTotalTimeInForeground);
assertEquals(us1.mTotalTimeVisible, us2.mTotalTimeVisible);
assertEquals(us1.mLastTimeForegroundServiceUsed, us2.mLastTimeForegroundServiceUsed);
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 2a3c2c4..b54b696 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -49,6 +49,8 @@
private int mCapabilities;
private int[] mSupportedEffects;
private int[] mSupportedPrimitives;
+ private float mResonantFrequency;
+ private float mQFactor;
private final class FakeNativeWrapper extends VibratorController.NativeWrapper {
public int vibratorId;
@@ -89,6 +91,14 @@
return mSupportedPrimitives;
}
+ public float getResonantFrequency() {
+ return mResonantFrequency;
+ }
+
+ public float getQFactor() {
+ return mQFactor;
+ }
+
public long perform(long effect, long strength, long vibrationId) {
if (mSupportedEffects == null
|| Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
@@ -198,6 +208,16 @@
mSupportedPrimitives = primitives;
}
+ /** Set the resonant frequency of the fake vibrator hardware. */
+ public void setResonantFrequency(float resonantFrequency) {
+ mResonantFrequency = resonantFrequency;
+ }
+
+ /** Set the Q factor of the fake vibrator hardware. */
+ public void setQFactor(float qFactor) {
+ mQFactor = qFactor;
+ }
+
/**
* Return the amplitudes set by this controller, including zeroes for each time the vibrator was
* turned off.
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 1b7e1ca..7d5eec0 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -107,7 +107,8 @@
waitForCompletion(thread);
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED));
+ verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
+ eq(Vibration.Status.IGNORED_UNSUPPORTED));
}
@Test
@@ -121,7 +122,8 @@
waitForCompletion(thread);
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED));
+ verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
+ eq(Vibration.Status.IGNORED_UNSUPPORTED));
}
@Test
@@ -206,8 +208,8 @@
thread.cancel();
waitForCompletion(thread);
- verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
- verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+ verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
+ verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
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 a28d18f..ce6639c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -266,6 +266,8 @@
vibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
vibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
vibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+ vibrator.setResonantFrequency(123.f);
+ vibrator.setQFactor(Float.NaN);
VibratorInfo info = createSystemReadyService().getVibratorInfo(1);
assertNotNull(info);
@@ -279,6 +281,8 @@
info.isEffectSupported(VibrationEffect.EFFECT_TICK));
assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
+ assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+ assertTrue(Float.isNaN(info.getQFactor()));
}
@Test
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
index 8789992..799ec53 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
@@ -17,9 +17,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.servicestests.apps.simpleservicetestapp">
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
<application>
<service android:name=".SimpleService"
android:exported="true" />
+ <service android:name=".SimpleFgService"
+ android:exported="true" />
</application>
</manifest>
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java
new file mode 100644
index 0000000..ccfc0b7
--- /dev/null
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleFgService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.servicestests.apps.simpleservicetestapp;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.R;
+
+public class SimpleFgService extends Service {
+ private static final String TAG = SimpleFgService.class.getSimpleName();
+ private static final String NOTIFICATION_CHANNEL_ID = TAG;
+ private static final int NOTIFICATION_ID = 1;
+
+ private static final int MSG_INIT = 0;
+ private static final int MSG_DONE = 1;
+ private static final int MSG_START_FOREGROUND = 2;
+ private static final int MSG_STOP_FOREGROUND = 3;
+
+ private static final String ACTION_FGS_STATS_TEST =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_FGS_STATS_TEST";
+ private static final String EXTRA_MESSENGER = "extra_messenger";
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_FOREGROUND: {
+ Log.i(TAG, "startForeground");
+ startForeground(NOTIFICATION_ID, mNotification);
+ sendRemoteMessage(MSG_DONE, 0, 0, null);
+ } break;
+ case MSG_STOP_FOREGROUND: {
+ Log.i(TAG, "stopForeground");
+ stopForeground(true);
+ sendRemoteMessage(MSG_DONE, 0, 0, null);
+ } break;
+ }
+ }
+ };
+ private final Messenger mMessenger = new Messenger(mHandler);
+
+ private Notification mNotification;
+ private Messenger mRemoteMessenger;
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate");
+ final NotificationManager nm = getSystemService(NotificationManager.class);
+ nm.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW));
+ mNotification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(TAG)
+ .setSmallIcon(R.drawable.ic_info)
+ .build();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.i(TAG, "onStartCommand");
+ startForeground(NOTIFICATION_ID, mNotification);
+ if (ACTION_FGS_STATS_TEST.equals(intent.getAction())) {
+ mRemoteMessenger = new Messenger(intent.getExtras().getBinder(EXTRA_MESSENGER));
+ sendRemoteMessage(MSG_INIT, 0, 0, mMessenger);
+ }
+ return START_NOT_STICKY;
+ }
+
+ private void sendRemoteMessage(int what, int arg1, int arg2, Object obj) {
+ final Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = arg1;
+ msg.arg2 = arg2;
+ msg.obj = obj;
+ try {
+ mRemoteMessenger.send(msg);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(TAG, "onDestroy");
+ mNotification = null;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
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 e510b4f..5462f47 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -61,6 +61,7 @@
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.VibrationEffect;
@@ -81,10 +82,12 @@
import com.android.internal.util.IntPair;
import com.android.server.UiServiceTestCase;
import com.android.server.lights.LogicalLight;
+import com.android.server.pm.PackageManagerService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -412,12 +415,16 @@
}
private void verifyVibrate() {
+ ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class);
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
- anyString(), any(AudioAttributes.class));
+ anyString(), captor.capture());
+ assertEquals(0, (captor.getValue().getAllFlags()
+ & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
}
private void verifyVibrate(int times) {
- verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(),
+ verify(mVibrator, times(times)).vibrate(eq(Process.SYSTEM_UID),
+ eq(PackageManagerService.PLATFORM_PACKAGE_NAME), any(), anyString(),
any(AudioAttributes.class));
}
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 1905e2f..ec3a1af 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -4038,6 +4038,24 @@
}
@Test
+ public void testVisitUris_audioContentsString() throws Exception {
+ final Uri audioContents = Uri.parse("content://com.example/audio");
+
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString());
+
+ Notification n = new Notification.Builder(mContext, "a")
+ .setContentTitle("notification with uris")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addExtras(extras)
+ .build();
+
+ Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+ n.visitUris(visitor);
+ verify(visitor, times(1)).accept(eq(audioContents));
+ }
+
+ @Test
public void testSetNotificationPolicy_preP_setOldFields() {
ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = mZenModeHelper;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c19f348..96ebd24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1687,6 +1687,7 @@
public void testIsSnapshotCompatible() {
final ActivityRecord activity = createActivityWithTask();
final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
+ .setTopActivityComponent(activity.mActivityComponent)
.setRotation(activity.getWindowConfiguration().getRotation())
.build();
@@ -1697,6 +1698,26 @@
assertFalse(activity.isSnapshotCompatible(snapshot));
}
+ /**
+ * Test that the snapshot should be obsoleted if the top activity changed.
+ */
+ @Test
+ public void testIsSnapshotCompatibleTopActivityChanged() {
+ final ActivityRecord activity = createActivityWithTask();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm)
+ .setTask(activity.getTask())
+ .setOnTop(true)
+ .build();
+ final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
+ .setTopActivityComponent(secondActivity.mActivityComponent)
+ .build();
+
+ assertTrue(secondActivity.isSnapshotCompatible(snapshot));
+
+ // Emulate the top activity changed.
+ assertFalse(activity.isSnapshotCompatible(snapshot));
+ }
+
@Test
public void testFixedRotationSnapshotStartingWindow() {
final ActivityRecord activity = createActivityWithTask();
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 f5d831b..33bcc5b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -117,7 +117,9 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.View;
import android.view.WindowManager;
+import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -1610,6 +1612,54 @@
}
@Test
+ public void testShellTransitRotation() {
+ DisplayContent dc = createNewDisplay();
+
+ // Set-up mock shell transitions
+ final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
+ mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
+ mAtm.getTransitionController().registerTransitionPlayer(testPlayer);
+
+ final DisplayRotation dr = dc.getDisplayRotation();
+ doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
+ // Rotate 180 degree so the display doesn't have configuration change. This condition is
+ // used for the later verification of stop-freezing (without setting mWaitingForConfig).
+ doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
+ mWm.mDisplayRotationController =
+ new IDisplayWindowRotationController.Stub() {
+ @Override
+ public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
+ IDisplayWindowRotationCallback callback) {
+ try {
+ callback.continueRotateDisplay(toRotation, null);
+ } catch (RemoteException e) {
+ assertTrue(false);
+ }
+ }
+ };
+
+ // kill any existing rotation animation (vestigial from test setup).
+ dc.setRotationAnimation(null);
+
+ final int origRot = dc.getConfiguration().windowConfiguration.getRotation();
+
+ mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
+ // Should create a transition request without performing rotation
+ assertNotNull(testPlayer.mLastRequest);
+ assertEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
+
+ // Once transition starts, rotation is applied and transition shows DC rotating.
+ testPlayer.start();
+ assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
+ assertNotNull(testPlayer.mLastReady);
+ assertEquals(dc, DisplayRotation.getDisplayFromTransition(testPlayer.mLastTransit));
+ WindowContainerToken dcToken = dc.mRemoteToken.toWindowContainerToken();
+ assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
+ testPlayer.mLastReady.getChange(dcToken).getStartRotation());
+ testPlayer.finish();
+ }
+
+ @Test
public void testGetOrCreateRootHomeTask_defaultDisplay() {
TaskDisplayArea defaultTaskDisplayArea = mWm.mRoot.getDefaultTaskDisplayArea();
@@ -1699,6 +1749,20 @@
}
@Test
+ public void testFindScrollCaptureTargetWindow_cantReceiveKeys() {
+ DisplayContent display = createNewDisplay();
+ Task stack = createTaskStackOnDisplay(display);
+ Task task = createTaskInStack(stack, 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
+
+ WindowState result = display.findScrollCaptureTargetWindow(null,
+ ActivityTaskManager.INVALID_TASK_ID);
+ assertEquals(activityWindow, result);
+ }
+
+ @Test
public void testFindScrollCaptureTargetWindow_taskId() {
DisplayContent display = createNewDisplay();
Task stack = createTaskStackOnDisplay(display);
@@ -1711,6 +1775,19 @@
}
@Test
+ public void testFindScrollCaptureTargetWindow_taskIdCantReceiveKeys() {
+ DisplayContent display = createNewDisplay();
+ Task stack = createTaskStackOnDisplay(display);
+ Task task = createTaskInStack(stack, 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");
+
+ WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId);
+ assertEquals(window, result);
+ }
+
+ @Test
public void testEnsureActivitiesVisibleNotRecursive() {
final TaskDisplayArea mockTda = mock(TaskDisplayArea.class);
final boolean[] called = { false };
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 7f9e7da..4e2697a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -134,6 +134,7 @@
null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow());
window.mInputChannel = new InputChannel();
window.mHasSurface = true;
+ mWm.mInputToWindowMap.put(window.mInputChannelToken, window);
return window;
}
@@ -226,7 +227,7 @@
// Verify after consuming that the drag surface is relinquished
try {
mTarget.mDeferDragStateClosed = true;
-
+ mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0);
// Verify the drop event includes the drag surface
mTarget.handleMotionEvent(false, 0, 0);
final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
@@ -355,6 +356,7 @@
private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
startDrag(flags, data, () -> {
+ mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY);
mTarget.handleMotionEvent(false, dropX, dropY);
mToken = mWindow.mClient.asBinder();
});
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 c98e013..956c277 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -18,11 +18,13 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +36,11 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+import static junit.framework.Assert.fail;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -96,7 +101,7 @@
mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */);
mAdapter.setCallingPidUid(123, 456);
runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0);
- mController = new RemoteAnimationController(mWm, mAdapter, mHandler);
+ mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter, mHandler);
}
private WindowState createAppOverlayWindow() {
@@ -525,6 +530,110 @@
}
}
+ @Test
+ public void testNonAppTarget_sendNavBar() throws Exception {
+ final int transit = TRANSIT_OLD_TASK_OPEN;
+ final AnimationAdapter adapter = setupForNonAppTargetNavBar(transit, true);
+
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(eq(transit), any(), any(),
+ nonAppsCaptor.capture(), finishedCaptor.capture());
+ boolean containNavTarget = false;
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ containNavTarget = true;
+ break;
+ }
+ }
+ assertTrue(containNavTarget);
+ assertEquals(1, mController.mPendingNonAppAnimations.size());
+ final NonAppWindowAnimationAdapter nonAppAdapter =
+ mController.mPendingNonAppAnimations.get(0);
+ spyOn(nonAppAdapter.getLeashFinishedCallback());
+
+ finishedCaptor.getValue().onAnimationFinished();
+ verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+ eq(adapter));
+ verify(nonAppAdapter.getLeashFinishedCallback())
+ .onAnimationFinished(nonAppAdapter.getLastAnimationType(), nonAppAdapter);
+ }
+
+ @Test
+ public void testNonAppTarget_notSendNavBar_notAttachToApp() throws Exception {
+ final int transit = TRANSIT_OLD_TASK_OPEN;
+ setupForNonAppTargetNavBar(transit, false);
+
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ verify(mMockRunner).onAnimationStart(eq(transit),
+ any(), any(), nonAppsCaptor.capture(), any());
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ fail("Non-app animation target must not contain navbar");
+ }
+ }
+ }
+
+ @Test
+ public void testNonAppTarget_notSendNavBar_controlledByFixedRotation() throws Exception {
+ final FixedRotationAnimationController mockController =
+ mock(FixedRotationAnimationController.class);
+ doReturn(mockController).when(mDisplayContent).getFixedRotationAnimationController();
+ final int transit = TRANSIT_OLD_TASK_OPEN;
+ setupForNonAppTargetNavBar(transit, true);
+
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ verify(mMockRunner).onAnimationStart(eq(transit),
+ any(), any(), nonAppsCaptor.capture(), any());
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ fail("Non-app animation target must not contain navbar");
+ }
+ }
+ }
+
+ @Test
+ public void testNonAppTarget_notSendNavBar_controlledByRecents() throws Exception {
+ final RecentsAnimationController mockController =
+ mock(RecentsAnimationController.class);
+ doReturn(mockController).when(mWm).getRecentsAnimationController();
+ final int transit = TRANSIT_OLD_TASK_OPEN;
+ setupForNonAppTargetNavBar(transit, true);
+
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ verify(mMockRunner).onAnimationStart(eq(transit),
+ any(), any(), nonAppsCaptor.capture(), any());
+ for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
+ if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
+ fail("Non-app animation target must not contain navbar");
+ }
+ }
+ }
+
+ private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+ final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar");
+ mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+ final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ spyOn(policy);
+ doReturn(shouldAttachNavBar).when(policy).shouldAttachNavBarToAppDuringTransition();
+
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord, new Point(50, 100), null,
+ new Rect(50, 100, 150, 150), null).mAdapter;
+ adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+ mFinishedCallback);
+ mController.goodToGo(transit);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ return adapter;
+ }
+
private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
verify(binder, atLeast(0)).asBinder();
verifyNoMoreInteractions(binder);
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 171aa76..1b114c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -116,8 +116,7 @@
final Task rootTask = new TaskBuilder(mSupervisor).build();
final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
new ActivityBuilder(mAtm).setTask(task1).build().mVisibleRequested = true;
- // RootWindowContainer#invalidateTaskLayers should post to update.
- waitHandlerIdle(mWm.mH);
+ mWm.mRoot.rankTaskLayers();
assertEquals(1, task1.mLayerRank);
// Only tasks that directly contain activities have a ranking.
@@ -125,7 +124,7 @@
final Task task2 = new TaskBuilder(mSupervisor).build();
new ActivityBuilder(mAtm).setTask(task2).build().mVisibleRequested = true;
- waitHandlerIdle(mWm.mH);
+ mWm.mRoot.rankTaskLayers();
// Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the
// activities have the visible rank.
@@ -134,6 +133,7 @@
assertEquals(1, task2.mLayerRank);
task2.moveToBack("test", null /* task */);
+ // RootWindowContainer#invalidateTaskLayers should post to update.
waitHandlerIdle(mWm.mH);
assertEquals(1, task1.mLayerRank);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 36cf9c9..5c7e580 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -161,7 +161,8 @@
final Rect displayBounds = mActivity.mDisplayContent.getWindowConfiguration().getBounds();
final float aspectRatio = 1.2f;
- mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = aspectRatio;
+ mActivity.info.setMaxAspectRatio(aspectRatio);
+ mActivity.info.setMinAspectRatio(aspectRatio);
prepareUnresizable(mActivity, -1f, SCREEN_ORIENTATION_UNSPECIFIED);
final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
@@ -780,6 +781,139 @@
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+ public void testOverrideMinAspectRatioMedium() {
+ setUpDisplaySizeWithApp(1000, 1200);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override forces the activity into a 3:2 aspect ratio
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+ public void testOverrideMinAspectRatioLowerThanManifest() {
+ setUpDisplaySizeWithApp(1400, 1600);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setMinAspectRatio(2f)
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override should have no effect, because the manifest aspect ratio is
+ // larger (2:1)
+ assertEquals(1600, activity.getBounds().height());
+ assertEquals(800, activity.getBounds().width());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+ public void testOverrideMinAspectRatioLargerThanManifest() {
+ setUpDisplaySizeWithApp(1400, 1600);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setMinAspectRatio(1.1f)
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override should have no effect, because the manifest aspect ratio is
+ // larger (2:1)
+ assertEquals(1600, activity.getBounds().height());
+ assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+ public void testOverrideMinAspectRatioLarge() {
+ setUpDisplaySizeWithApp(1500, 1600);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override forces the activity into a 16:9 aspect ratio
+ assertEquals(1600, activity.getBounds().height());
+ assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+ public void testOverrideMinAspectRatio_Both() {
+ // If multiple override aspect ratios are set, we should use the largest one
+
+ setUpDisplaySizeWithApp(1400, 1600);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override forces the activity into a 16:9 aspect ratio
+ assertEquals(1600, activity.getBounds().height());
+ assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+ public void testOverrideMinAspectRatioWithoutGlobalOverride() {
+ // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without
+ // OVERRIDE_MIN_ASPECT_RATIO being also set.
+
+ setUpDisplaySizeWithApp(1000, 1200);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override should have no effect
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1000, activity.getBounds().width());
+ }
+
+ @Test
public void testLaunchWithFixedRotationTransform() {
final int dw = 1000;
final int dh = 2500;
@@ -872,7 +1006,7 @@
// Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed
// orientation letterbox.
mActivity.mWmService.setFixedOrientationLetterboxAspectRatio(1.1f);
- mActivity.info.minAspectRatio = 3;
+ mActivity.info.setMinAspectRatio(3);
prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT);
final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
@@ -889,7 +1023,8 @@
// Activity bounds should respect minimum aspect ratio for activity.
assertEquals(displayBounds.height(), activityBounds.height());
- assertEquals((int) Math.rint(displayBounds.height() / mActivity.info.minAspectRatio),
+ assertEquals((int) Math.rint(displayBounds.height()
+ / mActivity.info.getManifestMinAspectRatio()),
activityBounds.width());
}
@@ -918,7 +1053,8 @@
// Activity bounds should respect maximum aspect ratio for activity.
assertEquals(displayBounds.height(), activityBounds.height());
- assertEquals((int) Math.rint(displayBounds.height() / mActivity.info.maxAspectRatio),
+ assertEquals((int) Math.rint(displayBounds.height()
+ / mActivity.info.getMaxAspectRatio()),
activityBounds.width());
}
@@ -1098,7 +1234,8 @@
assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(newActivity.inSizeCompatMode());
assertEquals(displayBounds.height(), newActivityBounds.height());
- assertEquals((long) Math.rint(newActivityBounds.height() / newActivity.info.maxAspectRatio),
+ assertEquals((long) Math.rint(newActivityBounds.height()
+ / newActivity.info.getMaxAspectRatio()),
newActivityBounds.width());
}
@@ -1347,7 +1484,7 @@
: RESIZE_MODE_RESIZEABLE;
activity.mVisibleRequested = true;
if (maxAspect >= 0) {
- activity.info.maxAspectRatio = maxAspect;
+ activity.info.setMaxAspectRatio(maxAspect);
}
if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
activity.info.screenOrientation = screenOrientation;
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 e22cda6..eba5634 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -42,16 +42,20 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.app.ActivityOptions;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import com.android.server.wm.LaunchParamsController.LaunchParams;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -87,6 +91,48 @@
}
@Test
+ public void getOrCreateLaunchRootRespectsResolvedWindowingMode() {
+ final Task rootTask = createTaskStackOnDisplay(
+ WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ 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 ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
+ final LaunchParams launchParams = new LaunchParams();
+ launchParams.mWindowingMode = WINDOWING_MODE_FREEFORM;
+
+ final Task actualRootTask = taskDisplayArea.getOrCreateRootTask(
+ activity, null /* options */, candidateRootTask,
+ launchParams, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ assertSame(rootTask, actualRootTask.getRootTask());
+ }
+
+ @Test
+ public void getOrCreateLaunchRootUsesActivityOptionsWindowingMode() {
+ final Task rootTask = createTaskStackOnDisplay(
+ WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ 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 ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ final Task actualRootTask = taskDisplayArea.getOrCreateRootTask(
+ activity, options, candidateRootTask,
+ null /* launchParams */, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ assertSame(rootTask, actualRootTask.getRootTask());
+ }
+
+ @Test
public void testActivityWithZBoost_taskDisplayAreaDoesNotMoveUp() {
final Task stack = createTaskStackOnDisplay(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index edf7056..b5219fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -28,7 +28,6 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.window.TaskSnapshot;
import android.content.ComponentName;
import android.content.ContextWrapper;
import android.content.res.Resources;
@@ -42,6 +41,7 @@
import android.hardware.HardwareBuffer;
import android.os.UserManager;
import android.view.Surface;
+import android.window.TaskSnapshot;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -154,10 +154,16 @@
private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
private int mSystemUiVisibility = 0;
private int mRotation = Surface.ROTATION_0;
+ private ComponentName mTopActivityComponent = new ComponentName("", "");
TaskSnapshotBuilder() {
}
+ TaskSnapshotBuilder setTopActivityComponent(ComponentName topActivityComponent) {
+ mTopActivityComponent = topActivityComponent;
+ return this;
+ }
+
TaskSnapshotBuilder setScaleFraction(float scale) {
mScaleFraction = scale;
return this;
@@ -199,7 +205,7 @@
Canvas c = buffer.lockCanvas();
c.drawColor(Color.RED);
buffer.unlockCanvasAndPost(c);
- return new TaskSnapshot(MOCK_SNAPSHOT_ID, new ComponentName("", ""),
+ return new TaskSnapshot(MOCK_SNAPSHOT_ID, mTopActivityComponent,
HardwareBuffer.createFromGraphicBuffer(buffer),
ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
mRotation, taskSize, TEST_INSETS,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index cac6965..21536a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -136,7 +136,10 @@
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
final TestDisplayContent newDisplay = createInternal(display);
-
+ // Ensure letterbox aspect ratio is not overridden on any device target.
+ // {@link com.android.internal.R.dimen.config_taskLetterboxAspectRatio}, provided by
+ // the below method, is set on some device form factors.
+ mService.mWindowManager.setFixedOrientationLetterboxAspectRatio(0);
// disable the normal system decorations
final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy();
spyOn(displayPolicy);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 001afe3..b3a0745 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -22,10 +22,11 @@
import android.os.RemoteException;
import android.util.MergedConfiguration;
import android.view.DragEvent;
-import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureResponseListener;
import android.view.IWindow;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.ScrollCaptureResponse;
import android.window.ClientWindowFrames;
import com.android.internal.os.IResultReceiver;
@@ -116,7 +117,15 @@
}
@Override
- public void requestScrollCapture(IScrollCaptureCallbacks callbacks) throws RemoteException {
+ public void requestScrollCapture(IScrollCaptureResponseListener listener)
+ throws RemoteException {
+ try {
+ listener.onScrollCaptureResponse(
+ new ScrollCaptureResponse.Builder().setDescription("Not Implemented").build());
+
+ } catch (RemoteException ex) {
+ // ignore
+ }
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 7822a85..ae8e2de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -100,9 +100,9 @@
}
@Override
- public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
- CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
- int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+ public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+ int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+ int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
final com.android.server.wm.WindowState window;
final ActivityRecord activity;
final WindowManagerService wm = mWmSupplier.get();
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 4a7784c..b210dfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -60,6 +60,7 @@
import static org.mockito.Mockito.mock;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
@@ -90,7 +91,10 @@
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.window.ITaskOrganizer;
+import android.window.ITransitionPlayer;
import android.window.StartingWindowInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
@@ -714,6 +718,7 @@
private int mLaunchMode;
private int mResizeMode = RESIZE_MODE_RESIZEABLE;
private float mMaxAspectRatio;
+ private float mMinAspectRatio;
private boolean mSupportsSizeChanges;
private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
private boolean mLaunchTaskBehind = false;
@@ -793,6 +798,11 @@
return this;
}
+ ActivityBuilder setMinAspectRatio(float minAspectRatio) {
+ mMinAspectRatio = minAspectRatio;
+ return this;
+ }
+
ActivityBuilder setSupportsSizeChanges(boolean supportsSizeChanges) {
mSupportsSizeChanges = supportsSizeChanges;
return this;
@@ -884,7 +894,8 @@
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
aInfo.resizeMode = mResizeMode;
- aInfo.maxAspectRatio = mMaxAspectRatio;
+ aInfo.setMaxAspectRatio(mMaxAspectRatio);
+ aInfo.setMinAspectRatio(mMinAspectRatio);
aInfo.supportsSizeChanges = mSupportsSizeChanges;
aInfo.screenOrientation = mScreenOrientation;
aInfo.configChanges |= mConfigChanges;
@@ -1352,4 +1363,48 @@
mHasSurface = hadSurface;
}
}
+
+ class TestTransitionPlayer extends ITransitionPlayer.Stub {
+ final TransitionController mController;
+ final WindowOrganizerController mOrganizer;
+ Transition mLastTransit = null;
+ TransitionRequestInfo mLastRequest = null;
+ TransitionInfo mLastReady = null;
+
+ TestTransitionPlayer(@NonNull TransitionController controller,
+ @NonNull WindowOrganizerController organizer) {
+ mController = controller;
+ mOrganizer = organizer;
+ }
+
+ void clear() {
+ mLastTransit = null;
+ mLastReady = null;
+ mLastRequest = null;
+ }
+
+ @Override
+ public void onTransitionReady(IBinder transitToken, TransitionInfo transitionInfo,
+ SurfaceControl.Transaction transaction) throws RemoteException {
+ mLastTransit = Transition.fromBinder(transitToken);
+ mLastReady = transitionInfo;
+ }
+
+ @Override
+ public void requestStartTransition(IBinder transitToken,
+ TransitionRequestInfo request) throws RemoteException {
+ mLastTransit = Transition.fromBinder(transitToken);
+ mLastRequest = request;
+ }
+
+ public void start() {
+ mOrganizer.startTransition(mLastRequest.getType(), mLastTransit, null);
+ mLastTransit.onTransactionReady(mLastTransit.getSyncId(),
+ mock(SurfaceControl.Transaction.class));
+ }
+
+ public void finish() {
+ mController.finishTransition(mLastTransit);
+ }
+ }
}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
index 8874e0a..72e1e33 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerService.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -27,6 +27,7 @@
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -203,6 +204,28 @@
}
}
+ @Override
+ public void registerUiTranslationStateCallback(IRemoteCallback callback, int userId) {
+ TranslationManagerServiceImpl service;
+ synchronized (mLock) {
+ service = getServiceForUserLocked(userId);
+ }
+ if (service != null) {
+ service.registerUiTranslationStateCallback(callback, Binder.getCallingUid());
+ }
+ }
+
+ @Override
+ public void unregisterUiTranslationStateCallback(IRemoteCallback callback, int userId) {
+ TranslationManagerServiceImpl service;
+ synchronized (mLock) {
+ service = getServiceForUserLocked(userId);
+ }
+ if (service != null) {
+ service.unregisterUiTranslationStateCallback(callback);
+ }
+ }
+
/**
* 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 ab6ac12..1ca07cb 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -17,17 +17,24 @@
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;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.service.translation.TranslationServiceInfo;
import android.util.Slog;
import android.view.autofill.AutofillId;
+import android.view.inputmethod.InputMethodInfo;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
@@ -36,6 +43,7 @@
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;
@@ -174,5 +182,50 @@
} catch (RemoteException e) {
Slog.w(TAG, "Update UiTranslationState fail: " + e);
}
+ invokeCallbacks(state, sourceSpec, destSpec);
}
+
+ private void invokeCallbacks(
+ int state, TranslationSpec sourceSpec, TranslationSpec targetSpec) {
+ Bundle res = new Bundle();
+ res.putInt(EXTRA_STATE, state);
+ // TODO(177500482): Store the locale pair so it can be sent for RESUME events.
+ if (sourceSpec != null) {
+ res.putString(EXTRA_SOURCE_LOCALE, sourceSpec.getLanguage());
+ res.putString(EXTRA_TARGET_LOCALE, targetSpec.getLanguage());
+ }
+ // TODO(177500482): Only support the *current* Input Method.
+ List<InputMethodInfo> enabledInputMethods =
+ LocalServices.getService(InputMethodManagerInternal.class)
+ .getEnabledInputMethodListAsUser(mUserId);
+ mCallbacks.broadcast((callback, uid) -> {
+ // Code here is non-optimal since it's temporary..
+ boolean isIme = false;
+ for (InputMethodInfo inputMethod : enabledInputMethods) {
+ if ((int) uid == inputMethod.getServiceInfo().applicationInfo.uid) {
+ isIme = true;
+ }
+ }
+ // TODO(177500482): Invoke it for the application being translated too.
+ if (!isIme) {
+ return;
+ }
+ try {
+ callback.sendResult(res);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e);
+ }
+ });
+ }
+
+ public void registerUiTranslationStateCallback(IRemoteCallback callback, int sourceUid) {
+ mCallbacks.register(callback, sourceUid);
+ // TODO(177500482): trigger the callback here if we're already translating the UI.
+ }
+
+ public void unregisterUiTranslationStateCallback(IRemoteCallback callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 78b1477..ec4c5fc 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -147,6 +147,10 @@
stats.mTotalTimeVisible = proto.readLong(
IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS);
break;
+ case (int) IntervalStatsProto.UsageStats.LAST_TIME_COMPONENT_USED_MS:
+ stats.mLastTimeComponentUsed = statsOut.beginTime + proto.readLong(
+ IntervalStatsProto.UsageStats.LAST_TIME_COMPONENT_USED_MS);
+ break;
}
}
proto.end(token);
@@ -345,6 +349,9 @@
usageStats.mLastTimeVisible, stats.beginTime);
proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS,
usageStats.mTotalTimeVisible);
+ UsageStatsProtoV2.writeOffsetTimestamp(proto,
+ IntervalStatsProto.UsageStats.LAST_TIME_COMPONENT_USED_MS,
+ usageStats.mLastTimeComponentUsed, stats.beginTime);
proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount);
try {
writeChooserCounts(proto, usageStats);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
index e6d2841..5c5667c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
@@ -90,6 +90,10 @@
stats.mTotalTimeVisible = proto.readLong(
UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS);
break;
+ case (int) UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS:
+ stats.mLastTimeComponentUsed = beginTime + proto.readLong(
+ UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS);
+ break;
case ProtoInputStream.NO_MORE_FIELDS:
return stats;
}
@@ -312,6 +316,8 @@
writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS,
stats.mLastTimeVisible, beginTime);
proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS, stats.mTotalTimeVisible);
+ writeOffsetTimestamp(proto, UsageStatsObfuscatedProto.LAST_TIME_COMPONENT_USED_MS,
+ stats.mLastTimeComponentUsed, beginTime);
proto.write(UsageStatsObfuscatedProto.APP_LAUNCH_COUNT, stats.mAppLaunchCount);
try {
writeChooserCounts(proto, stats);
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index f35b9e2..22b4f4e 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -1003,6 +1003,8 @@
formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates));
pw.printPair("lastTimeVisible",
formatDateTime(usageStats.mLastTimeVisible, prettyDates));
+ pw.printPair("lastTimeComponentUsed",
+ formatDateTime(usageStats.mLastTimeComponentUsed, prettyDates));
pw.printPair("totalTimeFS",
formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates));
pw.printPair("lastTimeFS",
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index c53c95c..d6c0469 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -60,9 +60,9 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.service.ServiceProtoEnums;
import android.service.usb.UsbPortInfoProto;
import android.service.usb.UsbPortManagerProto;
-import android.service.usb.UsbServiceProto;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -1061,15 +1061,15 @@
private static int convertContaminantDetectionStatusToProto(int contaminantDetectionStatus) {
switch (contaminantDetectionStatus) {
case UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED:
- return UsbServiceProto.CONTAMINANT_STATUS_NOT_SUPPORTED;
+ return ServiceProtoEnums.CONTAMINANT_STATUS_NOT_SUPPORTED;
case UsbPortStatus.CONTAMINANT_DETECTION_DISABLED:
- return UsbServiceProto.CONTAMINANT_STATUS_DISABLED;
+ return ServiceProtoEnums.CONTAMINANT_STATUS_DISABLED;
case UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED:
- return UsbServiceProto.CONTAMINANT_STATUS_NOT_DETECTED;
+ return ServiceProtoEnums.CONTAMINANT_STATUS_NOT_DETECTED;
case UsbPortStatus.CONTAMINANT_DETECTION_DETECTED:
- return UsbServiceProto.CONTAMINANT_STATUS_DETECTED;
+ return ServiceProtoEnums.CONTAMINANT_STATUS_DETECTED;
default:
- return UsbServiceProto.CONTAMINANT_STATUS_UNKNOWN;
+ return ServiceProtoEnums.CONTAMINANT_STATUS_UNKNOWN;
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 2626bfd..80d4f8f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -985,6 +985,7 @@
@Override
public void setHotwordDetectionServiceConfig(@Nullable Bundle options,
@Nullable SharedMemory sharedMemory) {
+ enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION);
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java
index 201c5db..f5357b1 100644
--- a/telecomm/java/android/telecom/CallDiagnosticService.java
+++ b/telecomm/java/android/telecom/CallDiagnosticService.java
@@ -19,9 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -30,6 +33,7 @@
import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* The platform supports a single OEM provided {@link CallDiagnosticService}, as defined by the
@@ -51,6 +55,11 @@
* </service>
* }
* </pre>
+ * <p>
+ * <h2>Threading Model</h2>
+ * By default, all incoming IPC from Telecom in this service and in the {@link CallDiagnostics}
+ * instances will take place on the main thread. You can override {@link #getExecutor()} in your
+ * implementation to provide your own {@link Executor}.
* @hide
*/
@SystemApi
@@ -83,7 +92,7 @@
@Override
public void updateCallAudioState(CallAudioState callAudioState) throws RemoteException {
- onCallAudioStateChanged(callAudioState);
+ getExecutor().execute(() -> onCallAudioStateChanged(callAudioState));
}
@Override
@@ -96,29 +105,37 @@
throws RemoteException {
handleBluetoothCallQualityReport(qualityReport);
}
+
+ @Override
+ public void notifyCallDisconnected(@NonNull String callId,
+ @NonNull DisconnectCause disconnectCause) throws RemoteException {
+ handleCallDisconnected(callId, disconnectCause);
+ }
}
/**
- * Listens to events raised by a {@link DiagnosticCall}.
+ * Listens to events raised by a {@link CallDiagnostics}.
*/
- private android.telecom.DiagnosticCall.Listener mDiagnosticCallListener =
- new android.telecom.DiagnosticCall.Listener() {
+ private CallDiagnostics.Listener mDiagnosticCallListener =
+ new CallDiagnostics.Listener() {
@Override
- public void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall,
- @DiagnosticCall.MessageType int message, int value) {
- handleSendDeviceToDeviceMessage(diagnosticCall, message, value);
+ public void onSendDeviceToDeviceMessage(CallDiagnostics callDiagnostics,
+ @CallDiagnostics.MessageType int message, int value) {
+ handleSendDeviceToDeviceMessage(callDiagnostics, message, value);
}
@Override
- public void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
+ public void onDisplayDiagnosticMessage(CallDiagnostics callDiagnostics,
+ int messageId,
CharSequence message) {
- handleDisplayDiagnosticMessage(diagnosticCall, messageId, message);
+ handleDisplayDiagnosticMessage(callDiagnostics, messageId, message);
}
@Override
- public void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
- handleClearDiagnosticMessage(diagnosticCall, messageId);
+ public void onClearDiagnosticMessage(CallDiagnostics callDiagnostics,
+ int messageId) {
+ handleClearDiagnosticMessage(callDiagnostics, messageId);
}
};
@@ -132,9 +149,19 @@
* Map which tracks the Telecom calls received from the Telecom stack.
*/
private final Map<String, Call.Details> mCallByTelecomCallId = new ArrayMap<>();
- private final Map<String, DiagnosticCall> mDiagnosticCallByTelecomCallId = new ArrayMap<>();
+ private final Map<String, CallDiagnostics> mDiagnosticCallByTelecomCallId = new ArrayMap<>();
+ private final Object mLock = new Object();
private ICallDiagnosticServiceAdapter mAdapter;
+ /**
+ * Handles binding to the {@link CallDiagnosticService}.
+ *
+ * @param intent The Intent that was used to bind to this service,
+ * as given to {@link android.content.Context#bindService
+ * Context.bindService}. Note that any extras that were included with
+ * the Intent at that point will <em>not</em> be seen here.
+ * @return
+ */
@Nullable
@Override
public IBinder onBind(@NonNull Intent intent) {
@@ -143,32 +170,57 @@
}
/**
+ * Returns the {@link Executor} to use for incoming IPS from Telecom into your service
+ * implementation.
+ * <p>
+ * Override this method in your {@link CallDiagnosticService} implementation to provide the
+ * executor you want to use for incoming IPC.
+ *
+ * @return the {@link Executor} to use for incoming IPC from Telecom to
+ * {@link CallDiagnosticService} and {@link CallDiagnostics}.
+ */
+ @SuppressLint("OnNameExpected")
+ @NonNull public Executor getExecutor() {
+ return new HandlerExecutor(Handler.createAsync(getMainLooper()));
+ }
+
+ /**
* Telecom calls this method on the {@link CallDiagnosticService} with details about a new call
* which was added to Telecom.
* <p>
- * The {@link CallDiagnosticService} returns an implementation of {@link DiagnosticCall} to be
+ * The {@link CallDiagnosticService} returns an implementation of {@link CallDiagnostics} to be
* used for the lifespan of this call.
+ * <p>
+ * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
+ * {@link CallDiagnosticService#getExecutor()} for more information.
*
* @param call The details of the new call.
- * @return An instance of {@link DiagnosticCall} which the {@link CallDiagnosticService}
+ * @return An instance of {@link CallDiagnostics} which the {@link CallDiagnosticService}
* provides to be used for the lifespan of the call.
- * @throws IllegalArgumentException if a {@code null} {@link DiagnosticCall} is returned.
+ * @throws IllegalArgumentException if a {@code null} {@link CallDiagnostics} is returned.
*/
- public abstract @NonNull DiagnosticCall onInitializeDiagnosticCall(@NonNull
+ public abstract @NonNull CallDiagnostics onInitializeCallDiagnostics(@NonNull
android.telecom.Call.Details call);
/**
- * Telecom calls this method when a previous created {@link DiagnosticCall} is no longer needed.
- * This happens when Telecom is no longer tracking the call in question.
+ * Telecom calls this method when a previous created {@link CallDiagnostics} is no longer
+ * needed. This happens when Telecom is no longer tracking the call in question.
+ * <p>
+ * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
+ * {@link CallDiagnosticService#getExecutor()} for more information.
+ *
* @param call The diagnostic call which is no longer tracked by Telecom.
*/
- public abstract void onRemoveDiagnosticCall(@NonNull DiagnosticCall call);
+ public abstract void onRemoveCallDiagnostics(@NonNull CallDiagnostics call);
/**
* Telecom calls this method when the audio routing or available audio route information
* changes.
* <p>
* Audio state is common to all calls.
+ * <p>
+ * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
+ * {@link CallDiagnosticService#getExecutor()} for more information.
*
* @param audioState The new audio state.
*/
@@ -178,6 +230,10 @@
/**
* Telecom calls this method when a {@link BluetoothCallQualityReport} is received from the
* bluetooth stack.
+ * <p>
+ * Calls to this method will use the {@link CallDiagnosticService}'s {@link Executor}; see
+ * {@link CallDiagnosticService#getExecutor()} for more information.
+ *
* @param qualityReport the {@link BluetoothCallQualityReport}.
*/
public abstract void onBluetoothCallQualityReportReceived(
@@ -199,31 +255,40 @@
String telecomCallId = parcelableCall.getId();
Log.i(this, "handleCallAdded: callId=%s - added", telecomCallId);
Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
- mCallByTelecomCallId.put(telecomCallId, newCallDetails);
-
- DiagnosticCall diagnosticCall = onInitializeDiagnosticCall(newCallDetails);
- if (diagnosticCall == null) {
- throw new IllegalArgumentException("A valid DiagnosticCall instance was not provided.");
+ synchronized (mLock) {
+ mCallByTelecomCallId.put(telecomCallId, newCallDetails);
}
- diagnosticCall.setListener(mDiagnosticCallListener);
- diagnosticCall.setCallId(telecomCallId);
- mDiagnosticCallByTelecomCallId.put(telecomCallId, diagnosticCall);
+
+ getExecutor().execute(() -> {
+ CallDiagnostics callDiagnostics = onInitializeCallDiagnostics(newCallDetails);
+ if (callDiagnostics == null) {
+ throw new IllegalArgumentException(
+ "A valid DiagnosticCall instance was not provided.");
+ }
+ synchronized (mLock) {
+ callDiagnostics.setListener(mDiagnosticCallListener);
+ callDiagnostics.setCallId(telecomCallId);
+ mDiagnosticCallByTelecomCallId.put(telecomCallId, callDiagnostics);
+ }
+ });
}
/**
* Handles an update to {@link Call.Details} notified by Telecom.
- * Caches the call details and notifies the {@link DiagnosticCall} of the change via
- * {@link DiagnosticCall#onCallDetailsChanged(Call.Details)}.
+ * Caches the call details and notifies the {@link CallDiagnostics} of the change via
+ * {@link CallDiagnostics#onCallDetailsChanged(Call.Details)}.
* @param parcelableCall the new parceled call details from Telecom.
*/
private void handleCallUpdated(@NonNull ParcelableCall parcelableCall) {
String telecomCallId = parcelableCall.getId();
Log.i(this, "handleCallUpdated: callId=%s - updated", telecomCallId);
Call.Details newCallDetails = Call.Details.createFromParcelableCall(parcelableCall);
-
- DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(telecomCallId);
- mCallByTelecomCallId.put(telecomCallId, newCallDetails);
- diagnosticCall.handleCallUpdated(newCallDetails);
+ CallDiagnostics callDiagnostics;
+ synchronized (mLock) {
+ callDiagnostics = mDiagnosticCallByTelecomCallId.get(telecomCallId);
+ mCallByTelecomCallId.put(telecomCallId, newCallDetails);
+ }
+ getExecutor().execute(() -> callDiagnostics.handleCallUpdated(newCallDetails));
}
/**
@@ -236,24 +301,65 @@
if (mCallByTelecomCallId.containsKey(telecomCallId)) {
mCallByTelecomCallId.remove(telecomCallId);
}
- if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) {
- DiagnosticCall call = mDiagnosticCallByTelecomCallId.remove(telecomCallId);
- // Inform the service of the removed call.
- onRemoveDiagnosticCall(call);
+
+ CallDiagnostics callDiagnostics;
+ synchronized (mLock) {
+ if (mDiagnosticCallByTelecomCallId.containsKey(telecomCallId)) {
+ callDiagnostics = mDiagnosticCallByTelecomCallId.remove(telecomCallId);
+ } else {
+ callDiagnostics = null;
+ }
+ }
+
+ // Inform the service of the removed call.
+ if (callDiagnostics != null) {
+ getExecutor().execute(() -> onRemoveCallDiagnostics(callDiagnostics));
}
}
/**
* Handles an incoming device to device message received from Telecom. Notifies the
- * {@link DiagnosticCall} via {@link DiagnosticCall#onReceiveDeviceToDeviceMessage(int, int)}.
+ * {@link CallDiagnostics} via {@link CallDiagnostics#onReceiveDeviceToDeviceMessage(int, int)}.
* @param callId
* @param message
* @param value
*/
private void handleReceivedD2DMessage(@NonNull String callId, int message, int value) {
Log.i(this, "handleReceivedD2DMessage: callId=%s, msg=%d/%d", callId, message, value);
- DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
- diagnosticCall.onReceiveDeviceToDeviceMessage(message, value);
+ CallDiagnostics callDiagnostics;
+ synchronized (mLock) {
+ callDiagnostics = mDiagnosticCallByTelecomCallId.get(callId);
+ }
+ if (callDiagnostics != null) {
+ getExecutor().execute(
+ () -> callDiagnostics.onReceiveDeviceToDeviceMessage(message, value));
+ }
+ }
+
+ /**
+ * Handles a request from the Telecom framework to get a disconnect message from the
+ * {@link CallDiagnosticService}.
+ * @param callId The ID of the call.
+ * @param disconnectCause The telecom disconnect cause.
+ */
+ private void handleCallDisconnected(@NonNull String callId,
+ @NonNull DisconnectCause disconnectCause) {
+ Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause);
+ CallDiagnostics callDiagnostics = mDiagnosticCallByTelecomCallId.get(callId);
+ CharSequence message;
+ if (disconnectCause.getImsReasonInfo() != null) {
+ message = callDiagnostics.onCallDisconnected(disconnectCause.getImsReasonInfo());
+ } else {
+ message = callDiagnostics.onCallDisconnected(
+ disconnectCause.getTelephonyDisconnectCause(),
+ disconnectCause.getTelephonyPreciseDisconnectCause());
+ }
+ try {
+ mAdapter.overrideDisconnectMessage(callId, message);
+ } catch (RemoteException e) {
+ Log.w(this, "handleCallDisconnected: call=%s; cause=%s; %s",
+ callId, disconnectCause, e);
+ }
}
/**
@@ -265,19 +371,19 @@
private void handleBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport
qualityReport) {
Log.i(this, "handleBluetoothCallQualityReport; report=%s", qualityReport);
- onBluetoothCallQualityReportReceived(qualityReport);
+ getExecutor().execute(() -> onBluetoothCallQualityReportReceived(qualityReport));
}
/**
- * Handles a request from a {@link DiagnosticCall} to send a device to device message (received
- * via {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}.
- * @param diagnosticCall
+ * Handles a request from a {@link CallDiagnostics} to send a device to device message (received
+ * via {@link CallDiagnostics#sendDeviceToDeviceMessage(int, int)}.
+ * @param callDiagnostics
* @param message
* @param value
*/
- private void handleSendDeviceToDeviceMessage(@NonNull DiagnosticCall diagnosticCall,
+ private void handleSendDeviceToDeviceMessage(@NonNull CallDiagnostics callDiagnostics,
int message, int value) {
- String callId = diagnosticCall.getCallId();
+ String callId = callDiagnostics.getCallId();
try {
mAdapter.sendDeviceToDeviceMessage(callId, message, value);
Log.i(this, "handleSendDeviceToDeviceMessage: call=%s; msg=%d/%d", callId, message,
@@ -289,15 +395,15 @@
}
/**
- * Handles a request from a {@link DiagnosticCall} to display an in-call diagnostic message.
- * Originates from {@link DiagnosticCall#displayDiagnosticMessage(int, CharSequence)}.
- * @param diagnosticCall
+ * Handles a request from a {@link CallDiagnostics} to display an in-call diagnostic message.
+ * Originates from {@link CallDiagnostics#displayDiagnosticMessage(int, CharSequence)}.
+ * @param callDiagnostics
* @param messageId
* @param message
*/
- private void handleDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
+ private void handleDisplayDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId,
CharSequence message) {
- String callId = diagnosticCall.getCallId();
+ String callId = callDiagnostics.getCallId();
try {
mAdapter.displayDiagnosticMessage(callId, messageId, message);
Log.i(this, "handleDisplayDiagnosticMessage: call=%s; msg=%d/%s", callId, messageId,
@@ -309,14 +415,14 @@
}
/**
- * Handles a request from a {@link DiagnosticCall} to clear a previously shown diagnostic
+ * Handles a request from a {@link CallDiagnostics} to clear a previously shown diagnostic
* message.
- * Originates from {@link DiagnosticCall#clearDiagnosticMessage(int)}.
- * @param diagnosticCall
+ * Originates from {@link CallDiagnostics#clearDiagnosticMessage(int)}.
+ * @param callDiagnostics
* @param messageId
*/
- private void handleClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId) {
- String callId = diagnosticCall.getCallId();
+ private void handleClearDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId) {
+ String callId = callDiagnostics.getCallId();
try {
mAdapter.clearDiagnosticMessage(callId, messageId);
Log.i(this, "handleClearDiagnosticMessage: call=%s; msg=%d", callId, messageId);
diff --git a/telecomm/java/android/telecom/CallDiagnostics.java b/telecomm/java/android/telecom/CallDiagnostics.java
new file mode 100644
index 0000000..3356431
--- /dev/null
+++ b/telecomm/java/android/telecom/CallDiagnostics.java
@@ -0,0 +1,374 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.telephony.Annotation;
+import android.telephony.CallQuality;
+import android.telephony.ims.ImsReasonInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * {@link CallDiagnostics} provides a way for a {@link CallDiagnosticService} to receive diagnostic
+ * information about a mobile call on the device. A {@link CallDiagnostics} instance is similar to
+ * a {@link Call}, however it does not expose call control capabilities and exposes extra diagnostic
+ * and messaging capabilities not present on a {@link Call}. The {@link CallDiagnosticService}
+ * creates a {@link CallDiagnostics} for each {@link Call} on the device. This means that for each
+ * in progress call on the device, the {@link CallDiagnosticService} will create an instance of
+ * {@link CallDiagnostics}.
+ * <p>
+ * The {@link CallDiagnosticService} can generate mid-call diagnostic messages using the
+ * {@link #displayDiagnosticMessage(int, CharSequence)} API which provides the user with valuable
+ * information about conditions impacting their call and corrective actions. For example, if the
+ * {@link CallDiagnosticService} determines that conditions on the call are degrading, it can inform
+ * the user that the call may soon drop and that they can try using a different calling method
+ * (e.g. VOIP or WIFI).
+ * <h2>Threading Model</h2>
+ * All incoming IPC from Telecom in this class will use the same {@link Executor} as the
+ * {@link CallDiagnosticService}. See {@link CallDiagnosticService#setExecutor(Executor)} for more
+ * information.
+ * @hide
+ */
+@SystemApi
+public abstract class CallDiagnostics {
+
+ /**
+ * @hide
+ */
+ public interface Listener {
+ /**
+ * Used to inform the {@link CallDiagnosticService} of a request to send a D2d message
+ * @param callDiagnostics the call the message is from.
+ * @param message the message type
+ * @param value the message value
+ */
+ void onSendDeviceToDeviceMessage(CallDiagnostics callDiagnostics, int message, int value);
+
+ /**
+ * Used to inform the {@link CallDiagnosticService} of a request to display a diagnostic
+ * message.
+ * @param callDiagnostics the call the message pertains to.
+ * @param messageId an identifier for the message.
+ * @param message the message to display.
+ */
+ void onDisplayDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId,
+ CharSequence message);
+
+ /**
+ * Used to inform the {@link CallDiagnosticService} that a previously shown message is no
+ * longer pertinent.
+ * @param callDiagnostics the call the message pertains to.
+ * @param messageId the ID of the previously posted message.
+ */
+ void onClearDiagnosticMessage(CallDiagnostics callDiagnostics, int messageId);
+ }
+
+ /**
+ * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+ * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the radio access type
+ * used for the current call. The call network type communicated here is an intentional
+ * simplification of the {@link android.telephony.TelephonyManager#getNetworkType(int)} which
+ * removes some of the resolution inherent in those values; the
+ * {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE_CA} value, for example is
+ * collapsed into the {@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE} value for
+ * efficiency of transport. For a discussion on the necessity of this simplification, see
+ * {@link #sendDeviceToDeviceMessage(int, int)}.
+ * <p>
+ * Valid values are below:
+ * <UL>
+ * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_LTE}</LI>
+ * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_IWLAN}</LI>
+ * <LI>{@link android.telephony.TelephonyManager#NETWORK_TYPE_NR}</LI>
+ * </UL>
+ */
+ public static final int MESSAGE_CALL_NETWORK_TYPE = 1;
+
+ /**
+ * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+ * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the call audio codec
+ * used for the current call.
+ * <p>
+ * The audio codec communicated here is an intentional simplification of the
+ * {@link Connection#EXTRA_AUDIO_CODEC} for a call and focuses on communicating the most common
+ * variants of these audio codecs. Other variants of these codecs are reported as the next
+ * closest variant. For example, the {@link Connection#AUDIO_CODEC_EVS_FB} full band codec
+ * is reported via device to device communication as {@link Connection#AUDIO_CODEC_EVS_WB}.
+ * For a discussion on the necessity of this simplification, see
+ * {@link #sendDeviceToDeviceMessage(int, int)}.
+ * <p>
+ * Valid values:
+ * <UL>
+ * <LI>{@link Connection#AUDIO_CODEC_EVS_WB}</LI>
+ * <LI>{@link Connection#AUDIO_CODEC_AMR_WB}</LI>
+ * <LI>{@link Connection#AUDIO_CODEC_AMR}</LI>
+ * </UL>
+ */
+ public static final int MESSAGE_CALL_AUDIO_CODEC = 2;
+
+ /**
+ * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+ * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the battery state of
+ * the device. Will typically mirror battery state reported via intents such as
+ * {@link android.content.Intent#ACTION_BATTERY_LOW}.
+ * <p>
+ * Valid values:
+ * <UL>
+ * <LI>{@link #BATTERY_STATE_LOW}</LI>
+ * <LI>{@link #BATTERY_STATE_GOOD}</LI>
+ * <LI>{@link #BATTERY_STATE_CHARGING}</LI>
+ * </UL>
+ */
+ public static final int MESSAGE_DEVICE_BATTERY_STATE = 3;
+
+ /**
+ * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
+ * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the overall network
+ * coverage as it pertains to the current call. A {@link CallDiagnosticService} should signal
+ * poor coverage if the network coverage reaches a level where there is a high probability of
+ * the call dropping as a result.
+ * <p>
+ * Valid values:
+ * <UL>
+ * <LI>{@link #COVERAGE_POOR}</LI>
+ * <LI>{@link #COVERAGE_GOOD}</LI>
+ * </UL>
+ */
+ public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "MESSAGE_", value = {
+ MESSAGE_CALL_NETWORK_TYPE,
+ MESSAGE_CALL_AUDIO_CODEC,
+ MESSAGE_DEVICE_BATTERY_STATE,
+ MESSAGE_DEVICE_NETWORK_COVERAGE
+ })
+ public @interface MessageType {}
+
+ /**
+ * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is low.
+ */
+ public static final int BATTERY_STATE_LOW = 1;
+
+ /**
+ * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is not low.
+ */
+ public static final int BATTERY_STATE_GOOD = 2;
+
+ /**
+ * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is charging.
+ */
+ public static final int BATTERY_STATE_CHARGING = 3;
+
+ /**
+ * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is poor.
+ */
+ public static final int COVERAGE_POOR = 1;
+
+ /**
+ * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is good.
+ */
+ public static final int COVERAGE_GOOD = 2;
+
+ private Listener mListener;
+ private String mCallId;
+
+ /**
+ * @hide
+ */
+ public void setListener(@NonNull Listener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Sets the call ID for this {@link CallDiagnostics}.
+ * @param callId
+ * @hide
+ */
+ public void setCallId(@NonNull String callId) {
+ mCallId = callId;
+ }
+
+ /**
+ * @return the Telecom call ID for this {@link CallDiagnostics}.
+ * @hide
+ */
+ public @NonNull String getCallId() {
+ return mCallId;
+ }
+
+ /**
+ * Telecom calls this method when the details of a call changes.
+ * <p>
+ * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService};
+ * see {@link CallDiagnosticService#getExecutor()} for more information.
+ */
+ public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details details);
+
+ /**
+ * The {@link CallDiagnosticService} implements this method to handle messages received via
+ * device to device communication.
+ * <p>
+ * See {@link #sendDeviceToDeviceMessage(int, int)} for background on device to device
+ * communication.
+ * <p>
+ * The underlying device to device communication protocol assumes that where there the two
+ * devices communicating are using a different version of the protocol, messages the recipient
+ * are not aware of are silently discarded. This means an older client talking to a new client
+ * will not receive newer messages and values sent by the new client.
+ * <p>
+ * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService};
+ * see {@link CallDiagnosticService#getExecutor()} for more information.
+ */
+ public abstract void onReceiveDeviceToDeviceMessage(
+ @MessageType int message,
+ int value);
+
+ /**
+ * Sends a device to device message to the device on the other end of this call.
+ * <p>
+ * Device to device communication is an Android platform feature which supports low bandwidth
+ * communication between Android devices while they are in a call. The device to device
+ * communication leverages DTMF tones or RTP header extensions to pass messages. The
+ * messages are unacknowledged and sent in a best-effort manner. The protocols assume that the
+ * nature of the message are informational only and are used only to convey basic state
+ * information between devices.
+ * <p>
+ * Device to device messages are intentional simplifications of more rich indicators in the
+ * platform due to the extreme bandwidth constraints inherent with underlying device to device
+ * communication transports used by the telephony framework. Device to device communication is
+ * either accomplished by adding RFC8285 compliant RTP header extensions to the audio packets
+ * for a call, or using the DTMF digits A-D as a communication pathway. RTP header extension
+ * packets ride alongside a the audio for a call, and are thus limited to roughly a byte for
+ * a message. Signalling requirements for DTMF digits place even more significant limitations
+ * on the amount of information which can be communicated during a call, offering only a few
+ * bits of potential information per message. The messages and values are constrained in order
+ * to meet the limited bandwidth inherent with DTMF signalling.
+ * <p>
+ * Allowed message types are:
+ * <ul>
+ * <li>{@link #MESSAGE_CALL_NETWORK_TYPE}</LI>
+ * <li>{@link #MESSAGE_CALL_AUDIO_CODEC}</LI>
+ * <li>{@link #MESSAGE_DEVICE_BATTERY_STATE}</LI>
+ * <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE}</LI>
+ * </ul>
+ * @param message The message type to send.
+ * @param value The message value corresponding to the type.
+ */
+ public final void sendDeviceToDeviceMessage(int message, int value) {
+ if (mListener != null) {
+ mListener.onSendDeviceToDeviceMessage(this, message, value);
+ }
+ }
+
+ /**
+ * Telecom calls this method when a GSM or CDMA call disconnects.
+ * The CallDiagnosticService can return a human readable disconnect message which will be passed
+ * to the Dialer app as the {@link DisconnectCause#getDescription()}. A dialer app typically
+ * shows this message at the termination of the call. If {@code null} is returned, the
+ * disconnect message generated by the telephony stack will be shown instead.
+ * <p>
+ * @param disconnectCause the disconnect cause for the call.
+ * @param preciseDisconnectCause the precise disconnect cause for the call.
+ * @return the disconnect message to use in place of the default Telephony message, or
+ * {@code null} if the default message will not be overridden.
+ * <p>
+ * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService};
+ * see {@link CallDiagnosticService#getExecutor()} for more information.
+ */
+ // TODO: Wire in Telephony support for this.
+ public abstract @Nullable CharSequence onCallDisconnected(
+ @Annotation.DisconnectCauses int disconnectCause,
+ @Annotation.PreciseDisconnectCauses int preciseDisconnectCause);
+
+ /**
+ * Telecom calls this method when an IMS call disconnects and Telephony has already
+ * provided the disconnect reason info and disconnect message for the call. The
+ * {@link CallDiagnosticService} can intercept the raw IMS disconnect reason at this point and
+ * combine it with other call diagnostic information it is aware of to override the disconnect
+ * call message if desired.
+ *
+ * @param disconnectReason The {@link ImsReasonInfo} associated with the call disconnection.
+ * @return A user-readable call disconnect message to use in place of the platform-generated
+ * disconnect message, or {@code null} if the disconnect message should not be overridden.
+ * <p>
+ * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService};
+ * see {@link CallDiagnosticService#getExecutor()} for more information.
+ */
+ // TODO: Wire in Telephony support for this.
+ public abstract @Nullable CharSequence onCallDisconnected(
+ @NonNull ImsReasonInfo disconnectReason);
+
+ /**
+ * Telecom calls this method when a {@link CallQuality} report is received from the telephony
+ * stack for a call.
+ * @param callQuality The call quality report for this call.
+ * <p>
+ * Calls to this method will use the same {@link Executor} as the {@link CallDiagnosticService};
+ * see {@link CallDiagnosticService#getExecutor()} for more information.
+ */
+ public abstract void onCallQualityReceived(@NonNull CallQuality callQuality);
+
+ /**
+ * Signals the active default dialer app to display a call diagnostic message. This can be
+ * used to report problems encountered during the span of a call.
+ * <p>
+ * The {@link CallDiagnosticService} provides a unique client-specific identifier used to
+ * identify the specific diagnostic message type.
+ * <p>
+ * The {@link CallDiagnosticService} should call {@link #clearDiagnosticMessage(int)} when the
+ * diagnostic condition has cleared.
+ * @param messageId the unique message identifier.
+ * @param message a human-readable, localized message to be shown to the user indicating a
+ * call issue which has occurred, along with potential mitigating actions.
+ */
+ public final void displayDiagnosticMessage(int messageId, @NonNull
+ CharSequence message) {
+ if (mListener != null) {
+ mListener.onDisplayDiagnosticMessage(this, messageId, message);
+ }
+ }
+
+ /**
+ * Signals to the active default dialer that the diagnostic message previously signalled using
+ * {@link #displayDiagnosticMessage(int, CharSequence)} with the specified messageId is no
+ * longer applicable (e.g. service has improved, for example.
+ * @param messageId the message identifier for a message previously shown via
+ * {@link #displayDiagnosticMessage(int, CharSequence)}.
+ */
+ public final void clearDiagnosticMessage(int messageId) {
+ if (mListener != null) {
+ mListener.onClearDiagnosticMessage(this, messageId);
+ }
+ }
+
+ /**
+ * Called by the {@link CallDiagnosticService} to update the call details for this
+ * {@link CallDiagnostics} based on an update received from Telecom.
+ * @param newDetails the new call details.
+ * @hide
+ */
+ public void handleCallUpdated(@NonNull Call.Details newDetails) {
+ onCallDetailsChanged(newDetails);
+ }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 335857af8..6dab6df 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -947,7 +947,7 @@
* {@link CallDiagnosticService} implementation which is active.
* <p>
* Likewise, if a {@link CallDiagnosticService} sends a message using
- * {@link DiagnosticCall#sendDeviceToDeviceMessage(int, int)}, it will be routed to telephony
+ * {@link CallDiagnostics#sendDeviceToDeviceMessage(int, int)}, it will be routed to telephony
* via {@link Connection#onCallEvent(String, Bundle)}. The telephony stack will relay the
* message to the other device.
* @hide
@@ -960,7 +960,7 @@
* Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device
* message type.
*
- * See {@link DiagnosticCall} for more information.
+ * See {@link CallDiagnostics} for more information.
* @hide
*/
@SystemApi
@@ -971,7 +971,7 @@
* Sent along with {@link #EVENT_DEVICE_TO_DEVICE_MESSAGE} to indicate the device to device
* message value.
* <p>
- * See {@link DiagnosticCall} for more information.
+ * See {@link CallDiagnostics} for more information.
* @hide
*/
@SystemApi
diff --git a/telecomm/java/android/telecom/DiagnosticCall.java b/telecomm/java/android/telecom/DiagnosticCall.java
index a495289..a6b7258 100644
--- a/telecomm/java/android/telecom/DiagnosticCall.java
+++ b/telecomm/java/android/telecom/DiagnosticCall.java
@@ -16,366 +16,12 @@
package android.telecom;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.telephony.Annotation;
-import android.telephony.CallQuality;
-import android.telephony.ims.ImsReasonInfo;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
/**
- * A {@link DiagnosticCall} provides a way for a {@link CallDiagnosticService} to receive diagnostic
- * information about a mobile call on the device. The {@link CallDiagnosticService} can generate
- * mid-call diagnostic messages using the {@link #displayDiagnosticMessage(int, CharSequence)} API
- * which provides the user with valuable information about conditions impacting their call and
- * corrective actions. For example, if the {@link CallDiagnosticService} determines that conditions
- * on the call are degrading, it can inform the user that the call may soon drop and that they
- * can try using a different calling method (e.g. VOIP or WIFI).
+ * @deprecated use {@link CallDiagnostics} instead.
* @hide
*/
@SystemApi
-public abstract class DiagnosticCall {
-
- /**
- * @hide
- */
- public interface Listener {
- void onSendDeviceToDeviceMessage(DiagnosticCall diagnosticCall, int message, int value);
- void onDisplayDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId,
- CharSequence message);
- void onClearDiagnosticMessage(DiagnosticCall diagnosticCall, int messageId);
- }
-
- /**
- * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
- * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the radio access type
- * used for the current call. Based loosely on the
- * {@link android.telephony.TelephonyManager#getNetworkType(int)} for the call, provides a
- * high level summary of the call radio access type.
- * <p>
- * Valid values:
- * <UL>
- * <LI>{@link #NETWORK_TYPE_LTE}</LI>
- * <LI>{@link #NETWORK_TYPE_IWLAN}</LI>
- * <LI>{@link #NETWORK_TYPE_NR}</LI>
- * </UL>
- */
- public static final int MESSAGE_CALL_NETWORK_TYPE = 1;
-
- /**
- * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
- * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the call audio codec
- * used for the current call. Based loosely on the {@link Connection#EXTRA_AUDIO_CODEC} for a
- * call.
- * <p>
- * Valid values:
- * <UL>
- * <LI>{@link #AUDIO_CODEC_EVS}</LI>
- * <LI>{@link #AUDIO_CODEC_AMR_WB}</LI>
- * <LI>{@link #AUDIO_CODEC_AMR_NB}</LI>
- * </UL>
- */
- public static final int MESSAGE_CALL_AUDIO_CODEC = 2;
-
- /**
- * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
- * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the battery state of
- * the device. Will typically mirror battery state reported via intents such as
- * {@link android.content.Intent#ACTION_BATTERY_LOW}.
- * <p>
- * Valid values:
- * <UL>
- * <LI>{@link #BATTERY_STATE_LOW}</LI>
- * <LI>{@link #BATTERY_STATE_GOOD}</LI>
- * <LI>{@link #BATTERY_STATE_CHARGING}</LI>
- * </UL>
- */
- public static final int MESSAGE_DEVICE_BATTERY_STATE = 3;
-
- /**
- * Device to device message sent via {@link #sendDeviceToDeviceMessage(int, int)} (received via
- * {@link #onReceiveDeviceToDeviceMessage(int, int)}) which communicates the overall network
- * coverage as it pertains to the current call. A {@link CallDiagnosticService} should signal
- * poor coverage if the network coverage reaches a level where there is a high probability of
- * the call dropping as a result.
- * <p>
- * Valid values:
- * <UL>
- * <LI>{@link #COVERAGE_POOR}</LI>
- * <LI>{@link #COVERAGE_GOOD}</LI>
- * </UL>
- */
- public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4;
-
- /**@hide*/
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "MESSAGE_", value = {
- MESSAGE_CALL_NETWORK_TYPE,
- MESSAGE_CALL_AUDIO_CODEC,
- MESSAGE_DEVICE_BATTERY_STATE,
- MESSAGE_DEVICE_NETWORK_COVERAGE
- })
- public @interface MessageType {}
-
- /**
- * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate an LTE network is being used for the
- * call.
- */
- public static final int NETWORK_TYPE_LTE = 1;
-
- /**
- * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate WIFI calling is in use for the call.
- */
- public static final int NETWORK_TYPE_IWLAN = 2;
-
- /**
- * Used with {@link #MESSAGE_CALL_NETWORK_TYPE} to indicate a 5G NR (new radio) network is in
- * used for the call.
- */
- public static final int NETWORK_TYPE_NR = 3;
-
- /**
- * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the
- * Enhanced Voice Services (EVS) codec for the call.
- */
- public static final int AUDIO_CODEC_EVS = 1;
-
- /**
- * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR
- * (adaptive multi-rate) WB (wide band) audio codec.
- */
- public static final int AUDIO_CODEC_AMR_WB = 2;
-
- /**
- * Used with {@link #MESSAGE_CALL_AUDIO_CODEC} to indicate call audio is using the AMR
- * (adaptive multi-rate) NB (narrow band) audio codec.
- */
- public static final int AUDIO_CODEC_AMR_NB = 3;
-
- /**
- * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is low.
- */
- public static final int BATTERY_STATE_LOW = 1;
-
- /**
- * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is not low.
- */
- public static final int BATTERY_STATE_GOOD = 2;
-
- /**
- * Used with {@link #MESSAGE_DEVICE_BATTERY_STATE} to indicate that the battery is charging.
- */
- public static final int BATTERY_STATE_CHARGING = 3;
-
- /**
- * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is poor.
- */
- public static final int COVERAGE_POOR = 1;
-
- /**
- * Used with {@link #MESSAGE_DEVICE_NETWORK_COVERAGE} to indicate that the coverage is good.
- */
- public static final int COVERAGE_GOOD = 2;
-
- private Listener mListener;
- private String mCallId;
- private Call.Details mCallDetails;
-
- /**
- * @hide
- */
- public void setListener(@NonNull Listener listener) {
- mListener = listener;
- }
-
- /**
- * Sets the call ID for this {@link DiagnosticCall}.
- * @param callId
- * @hide
- */
- public void setCallId(@NonNull String callId) {
- mCallId = callId;
- }
-
- /**
- * @return the Telecom call ID for this {@link DiagnosticCall}.
- * @hide
- */
- public @NonNull String getCallId() {
- return mCallId;
- }
-
- /**
- * Returns the latest {@link Call.Details} associated with this {@link DiagnosticCall} as
- * reported by {@link #onCallDetailsChanged(Call.Details)}.
- * @return The latest {@link Call.Details}.
- */
- public @NonNull Call.Details getCallDetails() {
- return mCallDetails;
- }
-
- /**
- * Telecom calls this method when the details of a call changes.
- */
- public abstract void onCallDetailsChanged(@NonNull android.telecom.Call.Details details);
-
- /**
- * The {@link CallDiagnosticService} implements this method to handle messages received via
- * device to device communication.
- * <p>
- * See {@link #sendDeviceToDeviceMessage(int, int)} for background on device to device
- * communication.
- * <p>
- * The underlying device to device communication protocol assumes that where there the two
- * devices communicating are using a different version of the protocol, messages the recipient
- * are not aware of are silently discarded. This means an older client talking to a new client
- * will not receive newer messages and values sent by the new client.
- */
- public abstract void onReceiveDeviceToDeviceMessage(
- @MessageType int message,
- int value);
-
- /**
- * Sends a device to device message to the device on the other end of this call.
- * <p>
- * Device to device communication is an Android platform feature which supports low bandwidth
- * communication between Android devices while they are in a call. The device to device
- * communication leverages DTMF tones or RTP header extensions to pass messages. The
- * messages are unacknowledged and sent in a best-effort manner. The protocols assume that the
- * nature of the message are informational only and are used only to convey basic state
- * information between devices.
- * <p>
- * Device to device messages are intentional simplifications of more rich indicators in the
- * platform due to the extreme bandwidth constraints inherent with underlying device to device
- * communication transports used by the telephony framework. Device to device communication is
- * either accomplished by adding RFC8285 compliant RTP header extensions to the audio packets
- * for a call, or using the DTMF digits A-D as a communication pathway. Signalling requirements
- * for DTMF digits place a significant limitation on the amount of information which can be
- * communicated during a call.
- * <p>
- * Allowed message types and values are:
- * <ul>
- * <li>{@link #MESSAGE_CALL_NETWORK_TYPE}
- * <ul>
- * <li>{@link #NETWORK_TYPE_LTE}</li>
- * <li>{@link #NETWORK_TYPE_IWLAN}</li>
- * <li>{@link #NETWORK_TYPE_NR}</li>
- * </ul>
- * </li>
- * <li>{@link #MESSAGE_CALL_AUDIO_CODEC}
- * <ul>
- * <li>{@link #AUDIO_CODEC_EVS}</li>
- * <li>{@link #AUDIO_CODEC_AMR_WB}</li>
- * <li>{@link #AUDIO_CODEC_AMR_NB}</li>
- * </ul>
- * </li>
- * <li>{@link #MESSAGE_DEVICE_BATTERY_STATE}
- * <ul>
- * <li>{@link #BATTERY_STATE_LOW}</li>
- * <li>{@link #BATTERY_STATE_GOOD}</li>
- * <li>{@link #BATTERY_STATE_CHARGING}</li>
- * </ul>
- * </li>
- * <li>{@link #MESSAGE_DEVICE_NETWORK_COVERAGE}
- * <ul>
- * <li>{@link #COVERAGE_POOR}</li>
- * <li>{@link #COVERAGE_GOOD}</li>
- * </ul>
- * </li>
- * </ul>
- * @param message The message type to send.
- * @param value The message value corresponding to the type.
- */
- public final void sendDeviceToDeviceMessage(int message, int value) {
- if (mListener != null) {
- mListener.onSendDeviceToDeviceMessage(this, message, value);
- }
- }
-
- /**
- * Telecom calls this method when a GSM or CDMA call disconnects.
- * The CallDiagnosticService can return a human readable disconnect message which will be passed
- * to the Dialer app as the {@link DisconnectCause#getDescription()}. A dialer app typically
- * shows this message at the termination of the call. If {@code null} is returned, the
- * disconnect message generated by the telephony stack will be shown instead.
- * <p>
- * @param disconnectCause the disconnect cause for the call.
- * @param preciseDisconnectCause the precise disconnect cause for the call.
- * @return the disconnect message to use in place of the default Telephony message, or
- * {@code null} if the default message will not be overridden.
- */
- // TODO: Wire in Telephony support for this.
- public abstract @Nullable CharSequence onCallDisconnected(
- @Annotation.DisconnectCauses int disconnectCause,
- @Annotation.PreciseDisconnectCauses int preciseDisconnectCause);
-
- /**
- * Telecom calls this method when an IMS call disconnects and Telephony has already
- * provided the disconnect reason info and disconnect message for the call. The
- * {@link CallDiagnosticService} can intercept the raw IMS disconnect reason at this point and
- * combine it with other call diagnostic information it is aware of to override the disconnect
- * call message if desired.
- *
- * @param disconnectReason The {@link ImsReasonInfo} associated with the call disconnection.
- * @return A user-readable call disconnect message to use in place of the platform-generated
- * disconnect message, or {@code null} if the disconnect message should not be overridden.
- */
- // TODO: Wire in Telephony support for this.
- public abstract @Nullable CharSequence onCallDisconnected(
- @NonNull ImsReasonInfo disconnectReason);
-
- /**
- * Telecom calls this method when a {@link CallQuality} report is received from the telephony
- * stack for a call.
- * @param callQuality The call quality report for this call.
- */
- public abstract void onCallQualityReceived(@NonNull CallQuality callQuality);
-
- /**
- * Signals the active default dialer app to display a call diagnostic message. This can be
- * used to report problems encountered during the span of a call.
- * <p>
- * The {@link CallDiagnosticService} provides a unique client-specific identifier used to
- * identify the specific diagnostic message type.
- * <p>
- * The {@link CallDiagnosticService} should call {@link #clearDiagnosticMessage(int)} when the
- * diagnostic condition has cleared.
- * @param messageId the unique message identifier.
- * @param message a human-readable, localized message to be shown to the user indicating a
- * call issue which has occurred, along with potential mitigating actions.
- */
- public final void displayDiagnosticMessage(int messageId, @NonNull
- CharSequence message) {
- if (mListener != null) {
- mListener.onDisplayDiagnosticMessage(this, messageId, message);
- }
- }
-
- /**
- * Signals to the active default dialer that the diagnostic message previously signalled using
- * {@link #displayDiagnosticMessage(int, CharSequence)} with the specified messageId is no
- * longer applicable (e.g. service has improved, for example.
- * @param messageId the message identifier for a message previously shown via
- * {@link #displayDiagnosticMessage(int, CharSequence)}.
- */
- public final void clearDiagnosticMessage(int messageId) {
- if (mListener != null) {
- mListener.onClearDiagnosticMessage(this, messageId);
- }
- }
-
- /**
- * Called by the {@link CallDiagnosticService} to update the call details for this
- * {@link DiagnosticCall} based on an update received from Telecom.
- * @param newDetails the new call details.
- * @hide
- */
- public void handleCallUpdated(@NonNull Call.Details newDetails) {
- mCallDetails = newDetails;
- onCallDetailsChanged(newDetails);
- }
+public abstract class DiagnosticCall extends CallDiagnostics {
}
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 1472a4a..ed7b79f 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,9 +16,13 @@
package android.telecom;
+import android.annotation.Nullable;
import android.media.ToneGenerator;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.Annotation;
+import android.telephony.PreciseDisconnectCause;
+import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import java.util.Objects;
@@ -112,6 +116,9 @@
private CharSequence mDisconnectDescription;
private String mDisconnectReason;
private int mToneToPlay;
+ private int mTelephonyDisconnectCause;
+ private int mTelephonyPreciseDisconnectCause;
+ private ImsReasonInfo mImsReasonInfo;
/**
* Creates a new DisconnectCause.
@@ -155,11 +162,36 @@
*/
public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
int toneToPlay) {
+ this(code, label, description, reason, toneToPlay,
+ android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
+ PreciseDisconnectCause.ERROR_UNSPECIFIED,
+ null /* imsReasonInfo */);
+ }
+
+ /**
+ * Creates a new DisconnectCause instance.
+ * @param code The code for the disconnect cause.
+ * @param label The localized label to show to the user to explain the disconnect.
+ * @param description The localized description to show to the user to explain the disconnect.
+ * @param reason The reason for the disconnect.
+ * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}.
+ * @param telephonyDisconnectCause The Telephony disconnect cause.
+ * @param telephonyPreciseDisconnectCause The Telephony precise disconnect cause.
+ * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
+ * @hide
+ */
+ public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
+ int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
+ @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
+ @Nullable ImsReasonInfo imsReasonInfo) {
mDisconnectCode = code;
mDisconnectLabel = label;
mDisconnectDescription = description;
mDisconnectReason = reason;
mToneToPlay = toneToPlay;
+ mTelephonyDisconnectCause = telephonyDisconnectCause;
+ mTelephonyPreciseDisconnectCause = telephonyPreciseDisconnectCause;
+ mImsReasonInfo = imsReasonInfo;
}
/**
@@ -209,6 +241,33 @@
}
/**
+ * Returns the telephony {@link android.telephony.DisconnectCause} for the call.
+ * @return The disconnect cause.
+ * @hide
+ */
+ public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() {
+ return mTelephonyDisconnectCause;
+ }
+
+ /**
+ * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call.
+ * @return The precise disconnect cause.
+ * @hide
+ */
+ public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() {
+ return mTelephonyPreciseDisconnectCause;
+ }
+
+ /**
+ * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection.
+ * @return The {@link ImsReasonInfo} or {@code null} if not known.
+ * @hide
+ */
+ public @Nullable ImsReasonInfo getImsReasonInfo() {
+ return mImsReasonInfo;
+ }
+
+ /**
* Returns the tone to play when disconnected.
*
* @return the tone as defined in {@link ToneGenerator} to play when disconnected.
@@ -217,7 +276,8 @@
return mToneToPlay;
}
- public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR = new Creator<DisconnectCause>() {
+ public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR
+ = new Creator<DisconnectCause>() {
@Override
public DisconnectCause createFromParcel(Parcel source) {
int code = source.readInt();
@@ -225,7 +285,11 @@
CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
String reason = source.readString();
int tone = source.readInt();
- return new DisconnectCause(code, label, description, reason, tone);
+ int telephonyDisconnectCause = source.readInt();
+ int telephonyPreciseDisconnectCause = source.readInt();
+ ImsReasonInfo imsReasonInfo = source.readParcelable(null);
+ return new DisconnectCause(code, label, description, reason, tone,
+ telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo);
}
@Override
@@ -241,6 +305,9 @@
TextUtils.writeToParcel(mDisconnectDescription, destination, flags);
destination.writeString(mDisconnectReason);
destination.writeInt(mToneToPlay);
+ destination.writeInt(mTelephonyDisconnectCause);
+ destination.writeInt(mTelephonyPreciseDisconnectCause);
+ destination.writeParcelable(mImsReasonInfo, 0);
}
@Override
@@ -254,7 +321,10 @@
+ Objects.hashCode(mDisconnectLabel)
+ Objects.hashCode(mDisconnectDescription)
+ Objects.hashCode(mDisconnectReason)
- + Objects.hashCode(mToneToPlay);
+ + Objects.hashCode(mToneToPlay)
+ + Objects.hashCode(mTelephonyDisconnectCause)
+ + Objects.hashCode(mTelephonyPreciseDisconnectCause)
+ + Objects.hashCode(mImsReasonInfo);
}
@Override
@@ -265,7 +335,11 @@
&& Objects.equals(mDisconnectLabel, d.getLabel())
&& Objects.equals(mDisconnectDescription, d.getDescription())
&& Objects.equals(mDisconnectReason, d.getReason())
- && Objects.equals(mToneToPlay, d.getTone());
+ && Objects.equals(mToneToPlay, d.getTone())
+ && Objects.equals(mTelephonyDisconnectCause, d.getTelephonyDisconnectCause())
+ && Objects.equals(mTelephonyPreciseDisconnectCause,
+ d.getTelephonyPreciseDisconnectCause())
+ && Objects.equals(mImsReasonInfo, d.getImsReasonInfo());
}
return false;
}
@@ -325,6 +399,11 @@
+ " Label: (" + label + ")"
+ " Description: (" + description + ")"
+ " Reason: (" + reason + ")"
- + " Tone: (" + mToneToPlay + ") ]";
+ + " Tone: (" + mToneToPlay + ") "
+ + " TelephonyCause: " + mTelephonyDisconnectCause + "/"
+ + mTelephonyPreciseDisconnectCause
+ + " ImsReasonInfo: "
+ + mImsReasonInfo
+ + "]";
}
}
diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
index 65b4d19..fc9879a 100644
--- a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
@@ -18,6 +18,7 @@
import android.telecom.BluetoothCallQualityReport;
import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
import android.telecom.ParcelableCall;
import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
@@ -34,4 +35,5 @@
void removeDiagnosticCall(in String callId);
void receiveDeviceToDeviceMessage(in String callId, int message, int value);
void receiveBluetoothCallQualityReport(in BluetoothCallQualityReport qualityReport);
+ void notifyCallDisconnected(in String callId, in DisconnectCause disconnectCause);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1a71f80..04a0aba 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -33,6 +33,8 @@
import android.os.RemoteException;
import android.service.carrier.CarrierService;
import android.telecom.TelecomManager;
+import android.telephony.gba.TlsParams;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.ImsSsData;
@@ -42,8 +44,6 @@
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.telephony.Rlog;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.TimeUnit;
/**
@@ -114,31 +114,17 @@
*/
public static final int USSD_OVER_IMS_ONLY = 3;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "CARRIER_NR_AVAILABILITY_" }, value = {
- CARRIER_NR_AVAILABILITY_NONE,
- CARRIER_NR_AVAILABILITY_NSA,
- CARRIER_NR_AVAILABILITY_SA,
- })
- public @interface DeviceNrCapability {}
-
- /**
- * Indicates CARRIER_NR_AVAILABILITY_NONE determine that the carrier does not enable 5G NR.
- */
- public static final int CARRIER_NR_AVAILABILITY_NONE = 0;
-
/**
* Indicates CARRIER_NR_AVAILABILITY_NSA determine that the carrier enable the non-standalone
* (NSA) mode of 5G NR.
*/
- public static final int CARRIER_NR_AVAILABILITY_NSA = 1 << 0;
+ public static final int CARRIER_NR_AVAILABILITY_NSA = 1;
/**
* Indicates CARRIER_NR_AVAILABILITY_SA determine that the carrier enable the standalone (SA)
* mode of 5G NR.
*/
- public static final int CARRIER_NR_AVAILABILITY_SA = 1 << 1;
+ public static final int CARRIER_NR_AVAILABILITY_SA = 2;
private final Context mContext;
@@ -1880,23 +1866,20 @@
"show_precise_failed_cause_bool";
/**
- * Bit-field integer to determine whether the carrier enable the non-standalone (NSA) mode of
- * 5G NR, standalone (SA) mode of 5G NR
+ * A list of carrier nr availability is used to determine whether the carrier enable the
+ * non-standalone (NSA) mode of 5G NR, standalone (SA) mode of 5G NR
*
- * <UL>
- * <LI>CARRIER_NR_AVAILABILITY_NONE: non-NR = 0 </LI>
- * <LI>CARRIER_NR_AVAILABILITY_NSA: NSA = 1 << 0</LI>
- * <LI>CARRIER_NR_AVAILABILITY_SA: SA = 1 << 1</LI>
- * </UL>
- * <p> The value of this key must be bitwise OR of
- * {@link #CARRIER_NR_AVAILABILITY_NONE}, {@link #CARRIER_NR_AVAILABILITY_NSA},
- * {@link #CARRIER_NR_AVAILABILITY_SA}.
+ * <p> The value of list is
+ * {@link #CARRIER_NR_AVAILABILITY_NSA}, or {@link #CARRIER_NR_AVAILABILITY_SA}.
*
- * <p> For example, if both NSA and SA are used, the value of key is 3 (1 << 0 | 1 << 1).
- * If the carrier doesn't support 5G NR, the value of key is 0 (non-NR).
- * If the key is invalid or not configured, a default value 3 (NSA|SA = 3) will apply.
+ * <p> For example, if both NSA and SA are used, the list value is {
+ * {@link #CARRIER_NR_AVAILABILITY_NSA},{@link #CARRIER_NR_AVAILABILITY_SA}}.
+ * If the carrier doesn't support 5G NR, the value is the empty array.
+ * If the key is invalid or not configured, the default value {
+ * {@link #CARRIER_NR_AVAILABILITY_NSA},{@link #CARRIER_NR_AVAILABILITY_SA}} will apply.
*/
- public static final String KEY_CARRIER_NR_AVAILABILITY_INT = "carrier_nr_availability_int";
+ public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY =
+ "carrier_nr_availabilities_int_array";
/**
* Boolean to decide whether LTE is enabled.
@@ -2972,6 +2955,18 @@
public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool";
/**
+ * Indicates if the carrier supports upgrading a call that was previously an RTT call to VT.
+ */
+ public static final String KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL =
+ "vt_upgrade_supported_for_downgraded_rtt_call";
+
+ /**
+ * Indicates if the carrier supports upgrading a call that was previously a VT call to RTT.
+ */
+ public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL =
+ "rtt_upgrade_supported_for_downgraded_vt_call";
+
+ /**
* Indicates if the carrier supports upgrading a voice call to an RTT call during the call.
*/
public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
@@ -3696,6 +3691,70 @@
*/
public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
+ /**
+ * Indicates that GBA_ME should be used for GBA authentication, as defined in 3GPP TS 33.220.
+ * @hide
+ */
+ @SystemApi
+ public static final int GBA_ME = 1;
+
+ /**
+ * Indicates that GBA_U should be used for GBA authentication, as defined in 3GPP TS 33.220.
+ * @hide
+ */
+ @SystemApi
+ public static final int GBA_U = 2;
+
+ /**
+ * Indicates that GBA_Digest should be used for GBA authentication, as defined
+ * in 3GPP TS 33.220.
+ * @hide
+ */
+ @SystemApi
+ public static final int GBA_DIGEST = 3;
+
+ /**
+ * An integer representing the GBA mode to use for requesting credentials
+ * via {@link TelephonyManager#bootstrapAuthenticationRequest}.
+ *
+ * One of {@link #GBA_ME}, {@link #GBA_U}, or {@link #GBA_DIGEST}.
+ * @hide
+ */
+ @SystemApi
+ public static final String KEY_GBA_MODE_INT = "gba_mode_int";
+
+ /**
+ * An integer representing the organization code to be used when building the
+ * {@link UaSecurityProtocolIdentifier} used when requesting GBA authentication.
+ *
+ * See the {@code ORG_} constants in {@link UaSecurityProtocolIdentifier}.
+ * @hide
+ */
+ @SystemApi
+ public static final String KEY_GBA_UA_SECURITY_ORGANIZATION_INT =
+ "gba_ua_security_organization_int";
+
+ /**
+ * An integer representing the security protocol to be used when building the
+ * {@link UaSecurityProtocolIdentifier} used when requesting GBA authentication.
+ *
+ * See the {@code UA_SECURITY_PROTOCOL_} constants in {@link UaSecurityProtocolIdentifier}.
+ * @hide
+ */
+ @SystemApi
+ public static final String KEY_GBA_UA_SECURITY_PROTOCOL_INT =
+ "gba_ua_security_protocol_int";
+
+ /**
+ * An integer representing the cipher suite to be used when building the
+ * {@link UaSecurityProtocolIdentifier} used when requesting GBA authentication.
+ *
+ * See the {@code TLS_} constants in {@link android.telephony.gba.TlsParams}.
+ * @hide
+ */
+ @SystemApi
+ public static final String KEY_GBA_UA_TLS_CIPHER_SUITE_INT =
+ "gba_ua_tls_cipher_suite_int";
/**
* Configs used by ImsServiceEntitlement.
@@ -3706,13 +3765,64 @@
/** Prefix of all ImsServiceEntitlement.KEY_* constants. */
public static final String KEY_PREFIX = "imsserviceentitlement.";
- /** The address of the entitlement configuration server. */
+ /**
+ * The address of the entitlement configuration server.
+ *
+ * Reference: GSMA TS.43-v5, section 2.1 Default Entitlement Configuration Server.
+ */
public static final String KEY_ENTITLEMENT_SERVER_URL_STRING =
KEY_PREFIX + "entitlement_server_url_string";
+ /**
+ * For some carriers, end-users may be presented with a web portal of the carrier before
+ * being allowed to use the VoWiFi service.
+ * To support this feature, the app hosts a {@link android.webkit.WebView} in the foreground
+ * VoWiFi entitlement configuration flow to show the web portal.
+ *
+ * {@code true} - show the VoWiFi portal in a webview.
+ *
+ * Note: this is effective only if the {@link #KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING}
+ * is set to this app.
+ *
+ * Reference: GSMA TS.43-v5, section 3, VoWiFi entitlement configuration.
+ */
+ public static final String KEY_SHOW_VOWIFI_WEBVIEW_BOOL =
+ KEY_PREFIX + "show_vowifi_webview_bool";
+
+ /**
+ * For some carriers, the network is not provisioned by default to support
+ * IMS (VoLTE/VoWiFi/SMSoIP) service for all end users. Some type of network-side
+ * provisioning must then take place before offering the IMS service to the end-user.
+ *
+ * {@code true} - need this ImsServiceEntitlement app to do IMS (VoLTE/VoWiFi/SMSoIP)
+ * provisioning in the background before offering the IMS service to the end-user.
+ *
+ * Note: this is effective only if the carrier needs IMS provisioning, i.e.
+ * {@link #KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL} is set to true.
+ *
+ * Reference: GSMA TS.43-v5, section 3 - 5, VoWiFi/VoLTE/SMSoIP entitlement configuration.
+ */
+ public static final String KEY_IMS_PROVISIONING_BOOL = KEY_PREFIX + "ims_provisioning_bool";
+
+ /**
+ * The FCM sender ID for the carrier.
+ * Used to trigger a carrier network requested entitlement configuration
+ * via Firebase Cloud Messaging (FCM). Do not set if the carrier doesn't use FCM for network
+ * requested entitlement configuration.
+ *
+ * Reference: GSMA TS.43-v5, section 2.4, Network Requested Entitlement Configuration.
+ *
+ * @see <a href="https://firebase.google.com/docs/cloud-messaging/concept-options#senderid">
+ * About FCM messages - Credentials</a>
+ */
+ public static final String KEY_FCM_SENDER_ID_STRING = KEY_PREFIX + "fcm_sender_id_string";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putString(KEY_ENTITLEMENT_SERVER_URL_STRING, "");
+ defaults.putString(KEY_FCM_SENDER_ID_STRING, "");
+ defaults.putBoolean(KEY_SHOW_VOWIFI_WEBVIEW_BOOL, false);
+ defaults.putBoolean(KEY_IMS_PROVISIONING_BOOL, false);
return defaults;
}
}
@@ -3999,6 +4109,22 @@
"is_opportunistic_subscription_bool";
/**
+ * The flatten string {@link android.content.ComponentName componentName} of carrier
+ * provisioning app receiver.
+ *
+ * <p>
+ * The RadioInfo activity(*#*#INFO#*#*) will broadcast an intent to this receiver when the
+ * "Carrier Provisioning Info" or "Trigger Carrier Provisioning" button clicked.
+ *
+ * <p>
+ * e.g, com.google.android.carrierPackageName/.CarrierReceiverName
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_PROVISIONING_APP_STRING =
+ "carrier_provisioning_app_string";
+
+ /**
* Configs used by the IMS stack.
*/
public static final class Ims {
@@ -4713,6 +4839,16 @@
public static final String KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY =
"allowed_initial_attach_apn_types_string_array";
+ /**
+ * Indicates whether or not the carrier will provision merged carrier Wi-Fi offload networks.
+ * Such networks are considered part of the core carrier network.
+ *
+ * This configuration will be use to gate whether such configurations are allowed to the carrier
+ * and correspondingly enable UI elements which are required for such configurations.
+ */
+ public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL =
+ "carrier_provisions_wifi_merged_networks_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -5080,6 +5216,8 @@
sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL, false);
sDefaults.putBoolean(KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL, false);
+ sDefaults.putBoolean(KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
@@ -5093,8 +5231,8 @@
sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
- sDefaults.putInt(KEY_CARRIER_NR_AVAILABILITY_INT,
- CARRIER_NR_AVAILABILITY_NSA | CARRIER_NR_AVAILABILITY_SA);
+ sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+ new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_TDSCDMA_BOOL, false);
sDefaults.putStringArray(KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
@@ -5255,6 +5393,13 @@
// Default wifi configurations.
sDefaults.putAll(Wifi.getDefaults());
sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
+ sDefaults.putInt(KEY_GBA_MODE_INT, GBA_ME);
+ sDefaults.putInt(KEY_GBA_UA_SECURITY_ORGANIZATION_INT,
+ UaSecurityProtocolIdentifier.ORG_3GPP);
+ sDefaults.putInt(KEY_GBA_UA_SECURITY_PROTOCOL_INT,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_TLS_DEFAULT);
+ sDefaults.putInt(KEY_GBA_UA_TLS_CIPHER_SUITE_INT, TlsParams.TLS_NULL_WITH_NULL_NULL);
+
sDefaults.putBoolean(KEY_SHOW_FORWARDED_NUMBER_BOOL, false);
sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, TimeUnit.DAYS.toMillis(1));
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
@@ -5276,6 +5421,8 @@
sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false);
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.putString(KEY_CARRIER_PROVISIONING_APP_STRING, "");
}
/**
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/telephony/java/android/telephony/LinkCapacityEstimate.aidl
similarity index 82%
copy from telephony/java/android/telephony/data/SliceInfo.aidl
copy to telephony/java/android/telephony/LinkCapacityEstimate.aidl
index 286ea5e..286f33f 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/telephony/java/android/telephony/LinkCapacityEstimate.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package android.telephony;
-parcelable SliceInfo;
+parcelable LinkCapacityEstimate;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/LinkCapacityEstimate.java b/telephony/java/android/telephony/LinkCapacityEstimate.java
new file mode 100644
index 0000000..deeb809
--- /dev/null
+++ b/telephony/java/android/telephony/LinkCapacityEstimate.java
@@ -0,0 +1,179 @@
+/*
+ * 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.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Link Capacity Estimate from the modem
+ * @hide
+ */
+@SystemApi
+public final class LinkCapacityEstimate implements Parcelable {
+ /** A value indicates that the capacity estimate is not available */
+ public static final int INVALID = -1;
+
+ /**
+ * LCE for the primary network
+ */
+ public static final int LCE_TYPE_PRIMARY = 0;
+
+ /**
+ * LCE for the secondary network
+ */
+ public static final int LCE_TYPE_SECONDARY = 1;
+
+ /**
+ * Combined LCE for primary network and secondary network reported by the legacy modem
+ */
+ public static final int LCE_TYPE_COMBINED = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "LCE_TYPE_" }, value = {
+ LCE_TYPE_PRIMARY,
+ LCE_TYPE_SECONDARY,
+ LCE_TYPE_COMBINED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LceType {}
+
+ private final @LceType int mType;
+
+ /** Downlink capacity estimate in kbps */
+ private final int mDownlinkCapacityKbps;
+
+ /** Uplink capacity estimate in kbps */
+ private final int mUplinkCapacityKbps;
+
+ /**
+ * Constructor for link capacity estimate
+ */
+ public LinkCapacityEstimate(@LceType int type,
+ int downlinkCapacityKbps, int uplinkCapacityKbps) {
+ mDownlinkCapacityKbps = downlinkCapacityKbps;
+ mUplinkCapacityKbps = uplinkCapacityKbps;
+ mType = type;
+ }
+
+ /**
+ * @hide
+ */
+ public LinkCapacityEstimate(Parcel in) {
+ mDownlinkCapacityKbps = in.readInt();
+ mUplinkCapacityKbps = in.readInt();
+ mType = in.readInt();
+ }
+
+ /**
+ * Retrieves the type of LCE
+ * @return The type of link capacity estimate
+ */
+ public @LceType int getType() {
+ return mType;
+ }
+
+ /**
+ * Retrieves the downlink bandwidth in Kbps.
+ * This will be {@link #INVALID} if the network is not connected
+ * @return The estimated first hop downstream (network to device) bandwidth.
+ */
+ public int getDownlinkCapacityKbps() {
+ return mDownlinkCapacityKbps;
+ }
+
+ /**
+ * Retrieves the uplink bandwidth in Kbps.
+ * This will be {@link #INVALID} if the network is not connected
+ *
+ * @return The estimated first hop upstream (device to network) bandwidth.
+ */
+ public int getUplinkCapacityKbps() {
+ return mUplinkCapacityKbps;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{mType=")
+ .append(mType)
+ .append(", mDownlinkCapacityKbps=")
+ .append(mDownlinkCapacityKbps)
+ .append(", mUplinkCapacityKbps=")
+ .append(mUplinkCapacityKbps)
+ .append("}")
+ .toString();
+ }
+
+ /**
+ * {@link Parcelable#describeContents}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@link Parcelable#writeToParcel}
+ * @hide
+ */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mDownlinkCapacityKbps);
+ dest.writeInt(mUplinkCapacityKbps);
+ dest.writeInt(mType);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null || !(o instanceof LinkCapacityEstimate) || hashCode() != o.hashCode()) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ LinkCapacityEstimate that = (LinkCapacityEstimate) o;
+ return mDownlinkCapacityKbps == that.mDownlinkCapacityKbps
+ && mUplinkCapacityKbps == that.mUplinkCapacityKbps
+ && mType == that.mType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDownlinkCapacityKbps, mUplinkCapacityKbps, mType);
+ }
+
+ public static final
+ @android.annotation.NonNull Parcelable.Creator<LinkCapacityEstimate> CREATOR =
+ new Parcelable.Creator() {
+ public LinkCapacityEstimate createFromParcel(Parcel in) {
+ return new LinkCapacityEstimate(in);
+ }
+
+ public LinkCapacityEstimate[] newArray(int size) {
+ return new LinkCapacityEstimate[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 2d5f5fb..f7580d7 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -602,6 +602,43 @@
public @interface SimDisplayNameSource {}
/**
+ * Device status is not shared to a remote party.
+ */
+ public static final int D2D_SHARING_DISABLED = 0;
+
+ /**
+ * Device status is shared with all numbers in the user's contacts.
+ */
+ public static final int D2D_SHARING_ALL_CONTACTS = 1;
+
+ /**
+ * Device status is shared with all starred contacts.
+ */
+ public static final int D2D_SHARING_STARRED_CONTACTS = 2;
+
+ /**
+ * Device status is shared whenever possible.
+ */
+ public static final int D2D_SHARING_ALL = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"D2D_SHARING_"},
+ value = {
+ D2D_SHARING_DISABLED,
+ D2D_SHARING_ALL_CONTACTS,
+ D2D_SHARING_STARRED_CONTACTS,
+ D2D_SHARING_ALL
+ })
+ public @interface DeviceToDeviceStatusSharing {}
+
+ /**
+ * TelephonyProvider column name for device to device sharing status.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String D2D_STATUS_SHARING = SimInfo.COLUMN_D2D_STATUS_SHARING;
+
+ /**
* TelephonyProvider column name for the color of a SIM.
* <P>Type: INTEGER (int)</P>
*/
@@ -3374,6 +3411,36 @@
}
/**
+ * Set the device to device status sharing user preference for a subscription ID. The setting
+ * 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
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setDeviceToDeviceStatusSharing(@DeviceToDeviceStatusSharing int sharing,
+ int subId) {
+ if (VDBG) {
+ logd("[setDeviceToDeviceStatusSharing] + sharing: " + sharing + " subId: " + subId);
+ }
+ setSubscriptionPropertyHelper(subId, "setDeviceToDeviceSharingStatus",
+ (iSub)->iSub.setDeviceToDeviceStatusSharing(sharing, subId));
+ }
+
+ /**
+ * Returns the user-chosen device to device status sharing preference
+ * @param subId Subscription id of subscription
+ * @return The device to device status sharing preference
+ */
+ public @DeviceToDeviceStatusSharing int getDeviceToDeviceStatusSharing(int subId) {
+ if (VDBG) {
+ logd("[getDeviceToDeviceStatusSharing] + subId: " + subId);
+ }
+ return getIntegerSubscriptionProperty(subId, D2D_STATUS_SHARING, D2D_SHARING_DISABLED,
+ mContext);
+ }
+
+ /**
* DO NOT USE.
* This API is designed for features that are not finished at this point. Do not call this API.
* @hide
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2546bbe5..962200b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -139,6 +139,8 @@
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* Provides access to information about the telephony services on
@@ -3147,6 +3149,10 @@
return NETWORK_TYPE_BITMASK_LTE_CA;
case NETWORK_TYPE_NR:
return NETWORK_TYPE_BITMASK_NR;
+ case NETWORK_TYPE_IWLAN:
+ return NETWORK_TYPE_BITMASK_IWLAN;
+ case NETWORK_TYPE_IDEN:
+ return (1 << (NETWORK_TYPE_IDEN - 1));
default:
return NETWORK_TYPE_BITMASK_UNKNOWN;
}
@@ -8642,8 +8648,8 @@
public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
/**
- * Set the allowed network types of the device and
- * provide the reason triggering the allowed network change.
+ * Set the allowed network types of the device and provide the reason triggering the allowed
+ * network change.
* This can be called for following reasons
* <ol>
* <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER}
@@ -8655,10 +8661,15 @@
* </ol>
* This API will result in allowing an intersection of allowed network types for all reasons,
* including the configuration done through other reasons.
+ *
+ * The functionality of this API with the parameter
+ * {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER} is the same as the API
+ * {@link TelephonyManager#setAllowedNetworkTypes}. Use this API instead of
+ * {@link TelephonyManager#setAllowedNetworkTypes}.
* <p>
* If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
* ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
- * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
+ * setAllowedNetworkTypesBitmap is used on the radio interface. Otherwise,
* setPreferredNetworkTypesBitmap is used instead.
*
* @param reason the reason the allowed network type change is taking place
@@ -8698,21 +8709,17 @@
* {@link #getAllowedNetworkTypesForReason} returns allowed network type for a
* specific reason.
*
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
- *
* @param reason the reason the allowed network type change is taking place
* @return the allowed network type bitmask
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
* @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(
enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED)
+ @SystemApi
public @NetworkTypeBitMask long getAllowedNetworkTypesForReason(
@AllowedNetworkTypesReason int reason) {
if (!isValidAllowedNetworkTypesReason(reason)) {
@@ -8757,6 +8764,25 @@
}
/**
+ * Returns a string representation of the allowed network types{@link NetworkTypeBitMask}.
+ *
+ * @param networkTypeBitmask The bitmask of allowed network types.
+ * @return the name of the allowed network types
+ * @hide
+ */
+ public static String convertNetworkTypeBitmaskToString(
+ @NetworkTypeBitMask long networkTypeBitmask) {
+ String networkTypeName = IntStream.rangeClosed(NETWORK_TYPE_GPRS, NETWORK_TYPE_NR)
+ .filter(x -> {
+ return (networkTypeBitmask & getBitMaskForNetworkType(x))
+ == getBitMaskForNetworkType(x);
+ })
+ .mapToObj(x -> getNetworkTypeName(x))
+ .collect(Collectors.joining("|"));
+ return TextUtils.isEmpty(networkTypeName) ? "UNKNOWN" : networkTypeName;
+ }
+
+ /**
* Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
*
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
@@ -9954,7 +9980,8 @@
}
/**
- * Sets the roaming mode for CDMA phone to the given mode {@code mode}.
+ * Sets the roaming mode for CDMA phone to the given mode {@code mode}. If the phone is not
+ * CDMA capable, this method does nothing.
*
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
* given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
@@ -9977,6 +10004,7 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void setCdmaRoamingMode(@CdmaRoamingMode int mode) {
+ if (getPhoneType() != PHONE_TYPE_CDMA) return;
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -10057,7 +10085,8 @@
}
/**
- * Sets the subscription mode for CDMA phone to the given mode {@code mode}.
+ * Sets the subscription mode for CDMA phone to the given mode {@code mode}. If the phone is not
+ * CDMA capable, this method does nothing.
*
* @param mode CDMA subscription mode.
* @throws SecurityException if the caller does not have the permission.
@@ -10076,6 +10105,7 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void setCdmaSubscriptionMode(@CdmaSubscription int mode) {
+ if (getPhoneType() != PHONE_TYPE_CDMA) return;
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -12577,6 +12607,7 @@
NETWORK_TYPE_BITMASK_LTE,
NETWORK_TYPE_BITMASK_LTE_CA,
NETWORK_TYPE_BITMASK_NR,
+ NETWORK_TYPE_BITMASK_IWLAN
})
public @interface NetworkTypeBitMask {}
@@ -14476,34 +14507,6 @@
}
/**
- * Get carrier bandwidth. In case of Dual connected network this will report
- * bandwidth per primary and secondary network. It is possible that
- * some modems may not fill secondary carrier bandwidth.
- * @return CarrierBandwidth with bandwidth of both primary and secondary carrier.
- * @throws IllegalStateException if the Telephony process is not currently available.
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @NonNull
- public CarrierBandwidth getCarrierBandwidth() {
- try {
- ITelephony service = getITelephony();
- if (service != null) {
- return service.getCarrierBandwidth(getSubId());
- } else {
- throw new IllegalStateException("telephony service is null.");
- }
- } catch (RemoteException ex) {
- Log.e(TAG, "getCarrierBandwidth RemoteException", ex);
- ex.rethrowFromSystemServer();
- }
-
- //Should not reach. Adding return statement to make compiler happy
- return null;
- }
-
- /**
* Called when userActivity is signalled in the power manager.
* This should only be called from system Uid.
* @hide
@@ -14607,6 +14610,10 @@
/**
* Enable/Disable E-UTRA-NR Dual Connectivity.
*
+ * This api is supported only if
+ * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+ * returns true.
* @param nrDualConnectivityState expected NR dual connectivity state
* This can be passed following states
* <ol>
@@ -14621,6 +14628,9 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
public @EnableNrDualConnectivityResult int setNrDualConnectivityState(
@NrDualConnectivityState int nrDualConnectivityState) {
try {
@@ -14640,15 +14650,21 @@
/**
* Is E-UTRA-NR Dual Connectivity enabled.
+ * This api is supported only if
+ * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+ * returns true.
* @return true if dual connectivity is enabled else false. Enabled state does not mean dual
* connectivity is active. It means the device is allowed to connect to both primary and
* secondary cell.
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
* @throws IllegalStateException if the Telephony process is not currently available.
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
public boolean isNrDualConnectivityEnabled() {
try {
ITelephony telephony = getITelephony();
@@ -14900,11 +14916,23 @@
public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
"CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+ /**
+ * Indicates whether {@link #setNrDualConnectivityState()} and
+ * {@link #isNrDualConnectivityEnabled()} ()} are available. See comments
+ * on respective methods for more information.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE =
+ "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "CAPABILITY_", value = {
CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
+ CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE
})
public @interface RadioInterfaceCapability {}
@@ -15216,13 +15244,17 @@
* </ul>
* @param appType icc application type, like {@link #APPTYPE_USIM} or {@link
* #APPTYPE_ISIM} or {@link#APPTYPE_UNKNOWN}
- * @param nafId Network Application Function(NAF) fully qualified domain name and
- * the selected GBA mode. It shall contain two parts delimited by "@" sign. The first
- * part is the constant string "3GPP-bootstrapping" (GBA_ME),
- * "3GPP-bootstrapping-uicc" (GBA_ U), or "3GPP-bootstrapping-digest" (GBA_Digest),
- * and the latter part shall be the FQDN of the NAF (e.g.
- * "3GPP-bootstrapping@naf1.operator.com" or "3GPP-bootstrapping-uicc@naf1.operator.com",
- * or "3GPP-bootstrapping-digest@naf1.operator.com").
+ * @param nafId A URI to specify Network Application Function(NAF) fully qualified domain
+ * name (FQDN) and the selected GBA mode. The authority of the URI must contain two parts
+ * delimited by "@" sign. The first part is the constant string "3GPP-bootstrapping" (GBA_ME),
+ * "3GPP-bootstrapping-uicc" (GBA_ U), or "3GPP-bootstrapping-digest" (GBA_Digest).
+ * The second part shall be the FQDN of the NAF. The scheme of the URI is not actually used
+ * for the authentication, which may be set the same as the resource that the application is
+ * going to access. For example, the nafId can be
+ * "https://3GPP-bootstrapping@naf1.operator.com",
+ * "https://3GPP-bootstrapping-uicc@naf1.operator.com",
+ * "https://3GPP-bootstrapping-digest@naf1.operator.com",
+ * "ftps://3GPP-bootstrapping-digest@naf1.operator.com".
* @param securityProtocol Security protocol identifier between UE and NAF. See
* 3GPP TS 33.220 Annex H. Application can use
* {@link UaSecurityProtocolIdentifier#createDefaultUaSpId},
@@ -15422,7 +15454,9 @@
public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1;
/**
- * The unattended reboot was not prepared due to generic error.
+ * The unattended reboot was not prepared due to a non-recoverable error. After this error,
+ * the client that manages the unattended reboot should not try to invoke the API again
+ * until the next power cycle.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index a764229..ef02589 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -18,6 +18,7 @@
package android.telephony.data;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -29,6 +30,7 @@
import android.telephony.data.ApnSetting.ProtocolType;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -137,7 +139,7 @@
private final int mPduSessionId;
private final Qos mDefaultQos;
private final List<QosBearerSession> mQosBearerSessions;
- private final SliceInfo mSliceInfo;
+ private final NetworkSliceInfo mSliceInfo;
private final List<TrafficDescriptor> mTrafficDescriptors;
/**
@@ -200,7 +202,8 @@
@Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6,
@HandoverFailureMode int handoverFailureMode, int pduSessionId,
@Nullable Qos defaultQos, @Nullable List<QosBearerSession> qosBearerSessions,
- @Nullable SliceInfo sliceInfo, @Nullable List<TrafficDescriptor> trafficDescriptors) {
+ @Nullable NetworkSliceInfo sliceInfo,
+ @Nullable List<TrafficDescriptor> trafficDescriptors) {
mCause = cause;
mSuggestedRetryTime = suggestedRetryTime;
mId = id;
@@ -253,7 +256,7 @@
mDefaultQos = source.readParcelable(Qos.class.getClassLoader());
mQosBearerSessions = new ArrayList<>();
source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader());
- mSliceInfo = source.readParcelable(SliceInfo.class.getClassLoader());
+ mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader());
mTrafficDescriptors = new ArrayList<>();
source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader());
}
@@ -408,7 +411,7 @@
* @return The slice info related to this data connection.
*/
@Nullable
- public SliceInfo getSliceInfo() {
+ public NetworkSliceInfo getSliceInfo() {
return mSliceInfo;
}
@@ -624,7 +627,7 @@
private List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
- private SliceInfo mSliceInfo;
+ private NetworkSliceInfo mSliceInfo;
private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>();
@@ -812,11 +815,19 @@
/**
* Set pdu session id.
+ * <p/>
+ * The id must be between 1 and 15 when linked to a pdu session. If no pdu session
+ * exists for the current data call, the id must be set to {@link PDU_SESSION_ID_NOT_SET}.
*
* @param pduSessionId Pdu Session Id of the data call.
* @return The same instance of the builder.
*/
- public @NonNull Builder setPduSessionId(int pduSessionId) {
+ public @NonNull Builder setPduSessionId(
+ @IntRange(from = PDU_SESSION_ID_NOT_SET, to = 15) int pduSessionId) {
+ Preconditions.checkArgument(pduSessionId >= PDU_SESSION_ID_NOT_SET,
+ "pduSessionId must be greater than or equal to" + PDU_SESSION_ID_NOT_SET);
+ Preconditions.checkArgument(pduSessionId <= 15,
+ "pduSessionId must be less than or equal to 15.");
mPduSessionId = pduSessionId;
return this;
}
@@ -855,13 +866,13 @@
* The Slice used for this data connection.
* <p/>
* If a handover occurs from EPDG to 5G,
- * this is the {@link SliceInfo} used in {@link DataService#setupDataCall}.
+ * this is the {@link NetworkSliceInfo} used in {@link DataService#setupDataCall}.
*
* @param sliceInfo the slice info for the data call
*
* @return The same instance of the builder.
*/
- public @NonNull Builder setSliceInfo(@Nullable SliceInfo sliceInfo) {
+ public @NonNull Builder setSliceInfo(@Nullable NetworkSliceInfo sliceInfo) {
mSliceInfo = sliceInfo;
return this;
}
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index f5f29c6..2f03475 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -41,6 +41,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Base class of data service. Services that extend DataService must register the service in
@@ -217,7 +218,7 @@
boolean isRoaming, boolean allowRoaming,
@SetupDataReason int reason,
@Nullable LinkProperties linkProperties,
- @IntRange(from = 0, to = 15) int pduSessionId, @Nullable SliceInfo sliceInfo,
+ @IntRange(from = 0, to = 15) int pduSessionId, @Nullable NetworkSliceInfo sliceInfo,
@Nullable TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed,
@NonNull DataServiceCallback callback) {
/* Call the old version since the new version isn't supported */
@@ -284,11 +285,11 @@
*
* Any resources being transferred cannot be released while a
* handover is underway.
- *
+ * <p/>
* If a handover was unsuccessful, then the framework calls
* {@link DataService#cancelHandover}. The target transport retains ownership over any of
* the resources being transferred.
- *
+ * <p/>
* If a handover was successful, the framework calls {@link DataService#deactivateDataCall}
* with reason {@link DataService.REQUEST_REASON_HANDOVER}. The target transport now owns
* the transferred resources and is responsible for releasing them.
@@ -299,21 +300,27 @@
* @hide
*/
public void startHandover(int cid, @NonNull DataServiceCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
// The default implementation is to return unsupported.
- if (callback != null) {
- Log.d(TAG, "startHandover: " + cid);
- callback.onHandoverStarted(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
- } else {
- Log.e(TAG, "startHandover: " + cid + ", callback is null");
- }
+ Log.d(TAG, "startHandover: " + cid);
+ callback.onHandoverStarted(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
}
/**
* Indicates that a handover was cancelled after a call to
* {@link DataService#startHandover}. This is called on the source transport.
- *
+ * <p/>
* Since the handover was unsuccessful, the source transport retains ownership over any of
* the resources being transferred and is still responsible for releasing them.
+ * <p/>
+ * The handover can be cancelled up until either:
+ * <ul><li>
+ * The handover was successful after receiving a successful response from
+ * {@link DataService#setupDataCall} on the target transport.
+ * </li><li>
+ * The data call on the source transport was lost.
+ * </li>
+ * </ul>
*
* @param cid The identifier of the data call which is provided in {@link DataCallResponse}
* @param callback The result callback for this request.
@@ -321,13 +328,10 @@
* @hide
*/
public void cancelHandover(int cid, @NonNull DataServiceCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
// The default implementation is to return unsupported.
- if (callback != null) {
- Log.d(TAG, "cancelHandover: " + cid);
- callback.onHandoverCancelled(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
- } else {
- Log.e(TAG, "cancelHandover: " + cid + ", callback is null");
- }
+ Log.d(TAG, "cancelHandover: " + cid);
+ callback.onHandoverCancelled(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
}
/**
@@ -414,13 +418,13 @@
public final int reason;
public final LinkProperties linkProperties;
public final int pduSessionId;
- public final SliceInfo sliceInfo;
+ public final NetworkSliceInfo sliceInfo;
public final TrafficDescriptor trafficDescriptor;
public final boolean matchAllRuleAllowed;
public final IDataServiceCallback callback;
SetupDataCallRequest(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
- SliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
+ NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
boolean matchAllRuleAllowed, IDataServiceCallback callback) {
this.accessNetworkType = accessNetworkType;
this.dataProfile = dataProfile;
@@ -707,7 +711,7 @@
@Override
public void setupDataCall(int slotIndex, int accessNetworkType, DataProfile dataProfile,
boolean isRoaming, boolean allowRoaming, int reason,
- LinkProperties linkProperties, int pduSessionId, SliceInfo sliceInfo,
+ LinkProperties linkProperties, int pduSessionId, NetworkSliceInfo sliceInfo,
TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed,
IDataServiceCallback callback) {
mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, slotIndex, 0,
diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl
index 81f5fd3..1346946 100644
--- a/telephony/java/android/telephony/data/IDataService.aidl
+++ b/telephony/java/android/telephony/data/IDataService.aidl
@@ -19,7 +19,7 @@
import android.net.LinkProperties;
import android.telephony.data.DataProfile;
import android.telephony.data.IDataServiceCallback;
-import android.telephony.data.SliceInfo;
+import android.telephony.data.NetworkSliceInfo;
import android.telephony.data.TrafficDescriptor;
/**
@@ -31,7 +31,7 @@
void removeDataServiceProvider(int slotId);
void setupDataCall(int slotId, int accessNetwork, in DataProfile dataProfile, boolean isRoaming,
boolean allowRoaming, int reason, in LinkProperties linkProperties,
- int pduSessionId, in SliceInfo sliceInfo,
+ int pduSessionId, in NetworkSliceInfo sliceInfo,
in TrafficDescriptor trafficDescriptor, boolean matchAllRuleAllowed,
IDataServiceCallback callback);
void deactivateDataCall(int slotId, int cid, int reason, IDataServiceCallback callback);
diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/telephony/java/android/telephony/data/NetworkSliceInfo.aidl
similarity index 95%
rename from telephony/java/android/telephony/data/SliceInfo.aidl
rename to telephony/java/android/telephony/data/NetworkSliceInfo.aidl
index 286ea5e..e1a11f2 100644
--- a/telephony/java/android/telephony/data/SliceInfo.aidl
+++ b/telephony/java/android/telephony/data/NetworkSliceInfo.aidl
@@ -17,4 +17,4 @@
/** @hide */
package android.telephony.data;
-parcelable SliceInfo;
+parcelable NetworkSliceInfo;
diff --git a/telephony/java/android/telephony/data/SliceInfo.java b/telephony/java/android/telephony/data/NetworkSliceInfo.java
similarity index 88%
rename from telephony/java/android/telephony/data/SliceInfo.java
rename to telephony/java/android/telephony/data/NetworkSliceInfo.java
index 51857a7..1d90095 100644
--- a/telephony/java/android/telephony/data/SliceInfo.java
+++ b/telephony/java/android/telephony/data/NetworkSliceInfo.java
@@ -29,12 +29,17 @@
import java.util.Objects;
/**
- * Represents a S-NSSAI as defined in 3GPP TS 24.501.
+ * Represents a S-NSSAI as defined in 3GPP TS 24.501, which represents a network slice.
+ *
+ * There are 2 main fields that define a slice, SliceServiceType and SliceDifferentiator.
+ * SliceServiceType defines the type of service provided by the slice, and SliceDifferentiator is
+ * used to differentiate between multiple slices of the same type. If the devices is not on HPLMN,
+ * the mappedHplmn versions of these 2 fields indicate the corresponding values in HPLMN.
*
* @hide
*/
@SystemApi
-public final class SliceInfo implements Parcelable {
+public final class NetworkSliceInfo implements Parcelable {
/**
* When set on a Slice Differentiator, this value indicates that there is no corresponding
* Slice.
@@ -93,7 +98,7 @@
@IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR)
private final int mMappedHplmnSliceDifferentiator;
- private SliceInfo(@SliceServiceType int sliceServiceType,
+ private NetworkSliceInfo(@SliceServiceType int sliceServiceType,
int sliceDifferentiator, int mappedHplmnSliceServiceType,
int mappedHplmnSliceDifferentiator) {
mSliceServiceType = sliceServiceType;
@@ -136,7 +141,7 @@
}
/**
- * This Slice Differentiator corresponds to a {@link SliceInfo} (S-NSSAI) of the HPLMN;
+ * This Slice Differentiator corresponds to a {@link NetworkSliceInfo} (S-NSSAI) of the HPLMN;
* {@link #getSliceDifferentiator()} is mapped to this value.
* <p/>
* Returns {@link #SLICE_DIFFERENTIATOR_NO_SLICE} if either of the following are true:
@@ -152,7 +157,7 @@
return mMappedHplmnSliceDifferentiator;
}
- private SliceInfo(@NonNull Parcel in) {
+ private NetworkSliceInfo(@NonNull Parcel in) {
mSliceServiceType = in.readInt();
mSliceDifferentiator = in.readInt();
mMappedHplmnSliceServiceType = in.readInt();
@@ -172,18 +177,18 @@
dest.writeInt(mMappedHplmnSliceDifferentiator);
}
- public static final @android.annotation.NonNull Parcelable.Creator<SliceInfo> CREATOR =
- new Parcelable.Creator<SliceInfo>() {
+ public static final @android.annotation.NonNull Parcelable.Creator<NetworkSliceInfo> CREATOR =
+ new Parcelable.Creator<NetworkSliceInfo>() {
@Override
@NonNull
- public SliceInfo createFromParcel(@NonNull Parcel source) {
- return new SliceInfo(source);
+ public NetworkSliceInfo createFromParcel(@NonNull Parcel source) {
+ return new NetworkSliceInfo(source);
}
@Override
@NonNull
- public SliceInfo[] newArray(int size) {
- return new SliceInfo[size];
+ public NetworkSliceInfo[] newArray(int size) {
+ return new NetworkSliceInfo[size];
}
};
@@ -217,7 +222,7 @@
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- SliceInfo sliceInfo = (SliceInfo) o;
+ NetworkSliceInfo sliceInfo = (NetworkSliceInfo) o;
return mSliceServiceType == sliceInfo.mSliceServiceType
&& mSliceDifferentiator == sliceInfo.mSliceDifferentiator
&& mMappedHplmnSliceServiceType == sliceInfo.mMappedHplmnSliceServiceType
@@ -231,7 +236,7 @@
}
/**
- * Provides a convenient way to set the fields of a {@link SliceInfo} when creating a
+ * Provides a convenient way to set the fields of a {@link NetworkSliceInfo} when creating a
* new instance.
*
* <p>The example below shows how you might create a new {@code SliceInfo}:
@@ -329,13 +334,13 @@
}
/**
- * Build the {@link SliceInfo}.
+ * Build the {@link NetworkSliceInfo}.
*
- * @return the {@link SliceInfo} object.
+ * @return the {@link NetworkSliceInfo} object.
*/
@NonNull
- public SliceInfo build() {
- return new SliceInfo(this.mSliceServiceType, this.mSliceDifferentiator,
+ public NetworkSliceInfo build() {
+ return new NetworkSliceInfo(this.mSliceServiceType, this.mSliceDifferentiator,
this.mMappedHplmnSliceServiceType, this.mMappedHplmnSliceDifferentiator);
}
}
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 85cd81b..abc5606 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -1010,6 +1010,16 @@
}
}
+ @Override
+ public void onPreProvisioningReceived(byte[] configXml) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mLocalCallback.onPreProvisioningReceived(configXml));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
private void setExecutor(Executor executor) {
mExecutor = executor;
}
@@ -1022,7 +1032,7 @@
* due to various triggers defined in GSMA RCC.14 for ACS(auto configuration
* server) or other operator defined triggers. If RCS provisioning is already
* completed at the time of callback registration, then this method shall be
- * invoked with the current configuration
+ * invoked with the current configuration.
* @param configXml The RCS configuration XML received by OTA. It is defined
* by GSMA RCC.07.
*/
@@ -1055,6 +1065,20 @@
*/
public void onRemoved() {}
+ /**
+ * Some carriers using ACS (auto configuration server) may send a carrier-specific
+ * pre-provisioning configuration XML if the user has not been provisioned for RCS
+ * services yet. When this provisioning XML is received, the framework will move
+ * into a "not provisioned" state for RCS. In order for provisioning to proceed,
+ * the application must parse this configuration XML and perform the carrier specific
+ * opt-in flow for RCS services. If the user accepts, {@link #triggerRcsReconfiguration}
+ * must be called in order for the device to move out of this state and try to fetch
+ * the RCS provisioning information.
+ *
+ * @param configXml the pre-provisioning config in carrier specified format.
+ */
+ public void onPreProvisioningReceived(@NonNull byte[] configXml) {}
+
/**@hide*/
public final IRcsConfigCallback getBinder() {
return mBinder;
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index cedf48b..9c28c36 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -25,7 +25,6 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -389,22 +388,6 @@
* The optional timestamp indicating the data and time of the status change of this tuple.
* Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format
* string per RFC3339.
- * @hide
- */
- public @NonNull Builder setTimestamp(@NonNull String timestamp) {
- try {
- mPresenceTuple.mTimestamp =
- DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from);
- } catch (DateTimeParseException e) {
- Log.d(LOG_TAG, "Parse timestamp failed " + e);
- }
- return this;
- }
-
- /**
- * The optional timestamp indicating the data and time of the status change of this tuple.
- * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format
- * string per RFC3339.
*/
public @NonNull Builder setTime(@NonNull Instant timestamp) {
mPresenceTuple.mTimestamp = timestamp;
@@ -534,14 +517,6 @@
return mContactUri;
}
- /**
- * @return the timestamp element contained in the tuple if it exists
- * @hide
- */
- public @Nullable String getTimestamp() {
- return (mTimestamp == null) ? null : mTimestamp.toString();
- }
-
/** @return the timestamp element contained in the tuple if it exists */
public @Nullable Instant getTime() {
return mTimestamp;
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 815c08d..dd91026 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -439,8 +439,7 @@
/**
* The pending request has resulted in an error and may need to be retried, depending on the
- * error code. The callback {@link #onCapabilitiesReceived(List)}
- * for each contacts is required to be called before {@link #onError} is called.
+ * error code.
* @param errorCode The reason for the framework being unable to process the request.
* @param retryIntervalMillis The time in milliseconds the requesting application should
* wait before retrying, if non-zero.
@@ -487,93 +486,6 @@
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
* @hide
*/
- @RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
- Manifest.permission.READ_CONTACTS})
- public void requestCapabilities(@NonNull List<Uri> contactNumbers,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull CapabilitiesCallback c) throws ImsException {
- if (c == null) {
- throw new IllegalArgumentException("Must include a non-null CapabilitiesCallback.");
- }
- if (executor == null) {
- throw new IllegalArgumentException("Must include a non-null Executor.");
- }
- if (contactNumbers == null) {
- throw new IllegalArgumentException("Must include non-null contact number list.");
- }
-
- IImsRcsController imsRcsController = getIImsRcsController();
- if (imsRcsController == null) {
- Log.e(TAG, "requestCapabilities: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
- ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
- }
-
- IRcsUceControllerCallback internalCallback = new IRcsUceControllerCallback.Stub() {
- @Override
- public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> c.onCapabilitiesReceived(contactCapabilities));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
- @Override
- public void onComplete() {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> c.onComplete());
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
- @Override
- public void onError(int errorCode, long retryAfterMilliseconds) {
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> c.onError(errorCode, retryAfterMilliseconds));
- } finally {
- restoreCallingIdentity(callingIdentity);
- }
- }
- };
-
- try {
- imsRcsController.requestCapabilities(mSubId, mContext.getOpPackageName(),
- mContext.getAttributionTag(), contactNumbers, internalCallback);
- } catch (ServiceSpecificException e) {
- throw new ImsException(e.toString(), e.errorCode);
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling IImsRcsController#requestCapabilities", e);
- throw new ImsException("Remote IMS Service is not available",
- ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
- }
- }
-
- /**
- * Request the User Capability Exchange capabilities for one or more contacts.
- * <p>
- * This will return the cached capabilities of the contact and will not perform a capability
- * poll on the network unless there are contacts being queried with stale information.
- * <p>
- * Be sure to check the availability of this feature using
- * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
- * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
- * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
- *
- * @param contactNumbers A list of numbers that the capabilities are being requested for.
- * @param executor The executor that will be used when the request is completed and the
- * {@link CapabilitiesCallback} is called.
- * @param c A one-time callback for when the request for capabilities completes or there is an
- * error processing the request.
- * @throws ImsException if the subscription associated with this instance of
- * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
- * available. This can happen if the ImsService has crashed, for example, or if the subscription
- * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
- * @hide
- */
@SystemApi
@RequiresPermission(allOf = {Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
Manifest.permission.READ_CONTACTS})
diff --git a/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl
index 5a8973e..d0853d1 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl
@@ -25,5 +25,6 @@
void onAutoConfigurationErrorReceived(int errorCode, String errorString);
void onConfigurationReset();
void onRemoved();
+ void onPreProvisioningReceived(in byte[] config);
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 21aeb64..d75da90 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -552,11 +552,34 @@
}
mRcsCallbacks.broadcastAction(c -> {
try {
- //TODO compressed by default?
c.onAutoConfigurationErrorReceived(errorCode, errorString);
} catch (RemoteException e) {
Log.w(TAG, "dead binder in notifyAutoConfigurationErrorReceived, skipping.");
}
});
}
+
+ /**
+ * Notifies application that pre-provisioning config is received.
+ *
+ * <p>Some carriers using ACS (auto configuration server) may send a carrier-specific
+ * pre-provisioning configuration XML if the user has not been provisioned for RCS
+ * services yet. When such provisioning XML is received, ACS client must call this
+ * method to notify the application with the XML.
+ *
+ * @param configXml the pre-provisioning config in carrier specified format.
+ */
+ public final void notifyPreProvisioningReceived(@NonNull byte[] configXml) {
+ // can be null in testing
+ if (mRcsCallbacks == null) {
+ return;
+ }
+ mRcsCallbacks.broadcastAction(c -> {
+ try {
+ c.onPreProvisioningReceived(configXml);
+ } catch (RemoteException e) {
+ Log.w(TAG, "dead binder in notifyPreProvisioningReceived, skipping.");
+ }
+ });
+ }
}
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index 00c9168..03e17fb 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -386,41 +386,6 @@
* {@link SubscribeResponseCallback#onTerminated(String, long)} must be called for the
* framework to finish listening for NOTIFY responses.
*
- * @param uris A {@link List} of the {@link Uri}s that the framework is requesting the UCE
- * capabilities for.
- * @param cb The callback of the subscribe request.
- * @hide
- */
- // executor used is defined in the constructor.
- @SuppressLint("ExecutorRegistration")
- public void subscribeForCapabilities(@NonNull List<Uri> uris,
- @NonNull SubscribeResponseCallback cb) {
- // Stub - to be implemented by service
- Log.w(LOG_TAG, "subscribeForCapabilities called with no implementation.");
- try {
- cb.onCommandError(COMMAND_CODE_NOT_SUPPORTED);
- } catch (ImsException e) {
- // Do not do anything, this is a stub implementation.
- }
- }
-
- /**
- * The user capabilities of one or multiple contacts have been requested by the framework.
- * <p>
- * The implementer must follow up this call with an
- * {@link SubscribeResponseCallback#onCommandError} call to indicate this operation has failed.
- * The response from the network to the SUBSCRIBE request must be sent back to the framework
- * using {@link SubscribeResponseCallback#onNetworkResponse(int, String)}.
- * As NOTIFY requests come in from the network, the requested contact’s capabilities should be
- * sent back to the framework using
- * {@link SubscribeResponseCallback#onNotifyCapabilitiesUpdate(List<String>}) and
- * {@link SubscribeResponseCallback#onResourceTerminated(List<Pair<Uri, String>>)}
- * should be called with the presence information for the contacts specified.
- * <p>
- * Once the subscription is terminated,
- * {@link SubscribeResponseCallback#onTerminated(String, long)} must be called for the
- * framework to finish listening for NOTIFY responses.
- *
* @param uris A {@link Collection} of the {@link Uri}s that the framework is requesting the
* UCE capabilities for.
* @param cb The callback of the subscribe request.
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 571efce..9493c76 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -300,4 +300,6 @@
boolean canDisablePhysicalSubscription();
int setUiccApplicationsEnabled(boolean enabled, int subscriptionId);
+
+ int setDeviceToDeviceStatusSharing(int sharing, int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 40b8696..96af172 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -31,7 +31,6 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.CallForwardingInfo;
-import android.telephony.CarrierBandwidth;
import android.telephony.CarrierRestrictionRules;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
@@ -55,6 +54,7 @@
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.RcsClientConfiguration;
+import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
@@ -2238,12 +2238,6 @@
boolean isNrDualConnectivityEnabled(int subId);
/**
- * Get carrier bandwidth per primary and secondary carrier
- * @return CarrierBandwidth with bandwidth of both primary and secondary carrier.
- */
- CarrierBandwidth getCarrierBandwidth(int subId);
-
- /**
* Checks whether the device supports the given capability on the radio interface.
*
* @param capability the name of the capability
@@ -2381,6 +2375,41 @@
void setDeviceUceEnabled(boolean isEnabled);
/**
+ * Add feature tags to the IMS registration being tracked by UCE and potentially
+ * generate a new PUBLISH to the network.
+ * Note: This is designed for a SHELL command only.
+ */
+ RcsContactUceCapability addUceRegistrationOverrideShell(int subId, in List<String> featureTags);
+
+ /**
+ * Remove feature tags from the IMS registration being tracked by UCE and potentially
+ * generate a new PUBLISH to the network.
+ * Note: This is designed for a SHELL command only.
+ */
+ RcsContactUceCapability removeUceRegistrationOverrideShell(int subId,
+ in List<String> featureTags);
+
+ /**
+ * Clear overridden feature tags in the IMS registration being tracked by UCE and potentially
+ * generate a new PUBLISH to the network.
+ * Note: This is designed for a SHELL command only.
+ */
+ RcsContactUceCapability clearUceRegistrationOverrideShell(int subId);
+
+ /**
+ * Get the latest RcsContactUceCapability structure that is used in SIP PUBLISH procedures.
+ * Note: This is designed for a SHELL command only.
+ */
+ RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId);
+
+ /**
+ * Returns the last PIDF XML sent to the network during the last PUBLISH or "none" if the
+ * device does not have an active PUBLISH.
+ * Note: This is designed for a SHELL command only.
+ */
+ String getLastUcePidfXmlShell(int subId);
+
+ /**
* Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the
* specified thresholds.
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index a8b7b05..e6a4501 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -35,15 +35,6 @@
}
}
-@JvmOverloads
-fun FlickerTestParameter.visibleWindowsShownMoreThanOneConsecutiveEntry(
- ignoreWindows: List<String> = emptyList()
-) {
- assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoreWindows)
- }
-}
-
fun FlickerTestParameter.launcherReplacesAppWindowAsTopWindow(testApp: IAppHelper) {
assertWm {
this.showsAppWindowOnTop(testApp.getPackage())
@@ -184,15 +175,6 @@
}
}
-@JvmOverloads
-fun FlickerTestParameter.visibleLayersShownMoreThanOneConsecutiveEntry(
- ignoreLayers: List<String> = emptyList()
-) {
- assertLayers {
- this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoreLayers)
- }
-}
-
fun FlickerTestParameter.appLayerReplacesWallpaperLayer(appName: String) {
assertLayers {
this.isVisible(WALLPAPER_TITLE)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index fef49d9..e118363 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -36,11 +35,8 @@
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.wallpaperLayerReplacesAppLayer
import com.android.server.wm.flicker.wallpaperWindowBecomesVisible
-import org.junit.Assume
import org.junit.Test
abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) {
@@ -94,41 +90,29 @@
@Presubmit
@Test
open fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
-
- @FlakyTest
- @Test
- open fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
}
@Presubmit
@Test
open fun statusBarLayerRotatesScales() {
- Assume.assumeFalse(testSpec.isRotated)
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
}
- @FlakyTest
- @Test
- open fun statusBarLayerRotatesScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
-
- @FlakyTest(bugId = 173689015)
+ @Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
}
- @FlakyTest(bugId = 173689015)
+ @Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
}
@Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index f7e7493..fad25b4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -16,31 +16,13 @@
package com.android.server.wm.flicker.helpers
-import android.os.RemoteException
-import android.view.Surface
import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
/**
* Changes the device [rotation] and wait for the rotation animation to complete
*
* @param rotation New device rotation
*/
-fun Flicker.setRotation(rotation: Int) {
- try {
- when (rotation) {
- Surface.ROTATION_270 -> device.setOrientationRight()
- Surface.ROTATION_90 -> device.setOrientationLeft()
- Surface.ROTATION_0 -> device.setOrientationNatural()
- else -> device.setOrientationNatural()
- }
-
- wmHelper.waitForRotation(rotation)
- wmHelper.waitForNavBarStatusBarVisible()
- wmHelper.waitForAppTransitionIdle()
-
- // Ensure WindowManagerService wait until all animations have completed
- instrumentation.uiAutomation.syncInputTransactions()
- } catch (e: RemoteException) {
- throw RuntimeException(e)
- }
-}
\ No newline at end of file
+fun Flicker.setRotation(rotation: Int) =
+ ChangeDisplayOrientationRule.setRotation(rotation, instrumentation, wmHelper)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 17aa1d1..90c2338 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -18,7 +18,6 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
@@ -28,20 +27,15 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import org.junit.Assume
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,23 +57,15 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
eachRun {
testApp.launchViaIntent(wmHelper)
testApp.openIME(device, wmHelper)
- this.setRotation(testSpec.config.startRotation)
}
}
teardown {
test {
- testApp.exit()
- wmHelper.waitForAppTransitionIdle()
- this.setRotation(Surface.ROTATION_0)
+ testApp.exit(wmHelper)
}
}
transitions {
@@ -98,8 +84,13 @@
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ }
+ }
@Presubmit
@Test
@@ -125,39 +116,26 @@
@Test
fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp)
- @Presubmit
+ @FlakyTest
@Test
fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(testSpec.isRotated)
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
}
@FlakyTest
@Test
- fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ fun statusBarLayerRotatesScales() {
+ testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
}
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
+ fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
}
- @FlakyTest
- @Test
- fun statusBarLayerRotatesScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
- }
-
- @FlakyTest
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 26afb79..b25bc99 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -28,20 +28,15 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import org.junit.Assume
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,22 +58,15 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
eachRun {
testApp.launchViaIntent(wmHelper)
testApp.openIME(device, wmHelper)
- this.setRotation(testSpec.config.startRotation)
}
}
teardown {
test {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
+ testApp.exit(wmHelper)
}
}
transitions {
@@ -99,8 +87,13 @@
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ }
+ }
@Presubmit
@Test
@@ -123,31 +116,15 @@
@Test
fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp)
- @Presubmit
+ @FlakyTest
@Test
fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(testSpec.isRotated)
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
}
@FlakyTest
@Test
- fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
-
- @Presubmit
- @Test
fun statusBarLayerRotatesScales() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
-
- @FlakyTest
- @Test
- fun statusBarLayerRotatesScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
}
@@ -162,15 +139,10 @@
@Presubmit
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
- }
-
- @FlakyTest
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(IME_WINDOW_TITLE, WindowManagerStateHelper.SPLASH_SCREEN_NAME))
+ }
}
companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 2c4c627..6b8bf63 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -18,7 +18,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Surface
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -27,18 +27,15 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import org.junit.Assume
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,13 +57,9 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
test {
- device.wakeUpAndGoToHomeScreen()
testApp.launchViaIntent()
- this.setRotation(testSpec.config.startRotation)
}
eachRun {
testApp.openIME(device, wmHelper)
@@ -74,8 +67,7 @@
}
teardown {
test {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
+ testApp.exit(wmHelper)
}
}
transitions {
@@ -94,8 +86,13 @@
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ }
+ }
@Presubmit
@Test
@@ -115,18 +112,39 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
+ fun navBarLayerRotatesAndScales() {
+ Assume.assumeFalse(testSpec.isRotated)
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ }
+
+ @FlakyTest
+ @Test
+ fun navBarLayerRotatesAndScales_Flaky() {
+ Assume.assumeTrue(testSpec.isRotated)
+ testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ }
@Presubmit
@Test
- fun statusBarLayerRotatesScales() =
+ fun statusBarLayerRotatesScales() {
+ Assume.assumeFalse(testSpec.isRotated)
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
+ }
+
+ @FlakyTest
+ @Test
+ fun statusBarLayerRotatesScales_Flaky() {
+ Assume.assumeTrue(testSpec.isRotated)
+ testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
+ }
@Presubmit
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+ }
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 2bcdcd9..9b37caf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -19,7 +19,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
+import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -28,19 +28,14 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import org.junit.Assume
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,15 +56,9 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
eachRun {
testApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.config.startRotation)
testApp.openIME(device, wmHelper)
}
}
@@ -85,7 +74,6 @@
}
test {
testApp.exit()
- this.setRotation(Surface.ROTATION_0)
}
}
}
@@ -101,8 +89,13 @@
@Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
+ WindowManagerStateHelper.SPLASH_SCREEN_NAME,
+ WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ }
+ }
@Presubmit
@Test
@@ -141,29 +134,31 @@
@Presubmit
@Test
fun statusBarLayerRotatesScales() {
- Assume.assumeFalse(testSpec.isRotated)
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
}
- @FlakyTest
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(IME_WINDOW_TITLE, WindowManagerStateHelper.SPLASH_SCREEN_NAME))
+ }
}
- @FlakyTest
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(repetitions = 5,
- supportedRotations = listOf(Surface.ROTATION_0))
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedRotations = listOf(Surface.ROTATION_0),
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ )
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 6b2b930..d39044ab 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -19,7 +19,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
+import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -27,23 +27,17 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.appWindowAlwaysVisibleOnTop
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.layerAlwaysVisible
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,13 +59,9 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
test {
- device.wakeUpAndGoToHomeScreen()
testApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.config.startRotation)
}
}
transitions {
@@ -83,7 +73,6 @@
}
test {
testApp.exit()
- this.setRotation(Surface.ROTATION_0)
}
}
}
@@ -128,48 +117,44 @@
@Presubmit
@Test
fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
- }
-
- @FlakyTest
- @Test
- fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
}
@Presubmit
@Test
fun statusBarLayerRotatesScales() {
- Assume.assumeFalse(testSpec.isRotated)
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
}
- @FlakyTest
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
+ fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
}
- @FlakyTest
+ @Presubmit
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @FlakyTest
- @Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+ }
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(repetitions = 5,
- supportedRotations = listOf(Surface.ROTATION_0))
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedRotations = listOf(Surface.ROTATION_0),
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ )
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 0cd5d79..95b1d3c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import android.view.WindowManagerPolicyConstants
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
@@ -29,24 +30,19 @@
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible
import com.android.server.wm.flicker.appLayerReplacesWallpaperLayer
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.testapp.ActivityOptions
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,11 +65,8 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
test {
- device.wakeUpAndGoToHomeScreen()
testApp.launchViaIntent(wmHelper)
testApp.openIME(device, wmHelper)
}
@@ -90,7 +83,6 @@
}
teardown {
test {
- this.setRotation(Surface.ROTATION_0)
testApp.exit()
}
}
@@ -105,10 +97,13 @@
@Test
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
- @Presubmit
+ @FlakyTest
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+ }
@Presubmit
@Test
@@ -146,45 +141,38 @@
fun appLayerReplacesWallpaperLayer() =
testSpec.appLayerReplacesWallpaperLayer(testAppComponentName.className)
- @Presubmit
+ @FlakyTest
@Test
fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(testSpec.isRotated)
testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
}
@FlakyTest
@Test
- fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() {
+ testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
}
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
+ fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
}
- @FlakyTest
- @Test
- fun statusBarLayerRotatesScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
-
- @FlakyTest
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(repetitions = 1)
+ .getConfigNonRotationTests(
+ repetitions = 1,
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ )
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 56ed21b..a9888b1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -60,7 +60,8 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 4a32a9e..a19a95d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -16,20 +16,17 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.Postsubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -66,69 +63,21 @@
}
}
+ @Postsubmit
@Test
override fun appWindowReplacesLauncherAsTopWindow() =
super.appWindowReplacesLauncherAsTopWindow()
+ @Postsubmit
@Test
override fun wallpaperWindowBecomesInvisible() {
testSpec.wallpaperWindowBecomesInvisible()
}
- @Presubmit
- @Test
- override fun statusBarLayerIsAlwaysVisible() {
- Assume.assumeTrue(testSpec.isRotated)
- super.statusBarLayerIsAlwaysVisible()
- }
-
- @Presubmit
- @Test
- override fun navBarLayerIsAlwaysVisible() {
- Assume.assumeTrue(testSpec.isRotated)
- super.navBarLayerIsAlwaysVisible()
- }
-
@FlakyTest
@Test
- fun statusBarLayerIsAlwaysVisible_Flaky() {
- Assume.assumeFalse(testSpec.isRotated)
- super.statusBarLayerIsAlwaysVisible()
- }
-
- @FlakyTest
- @Test
- fun navBarLayerIsAlwaysVisible_Flaky() {
- Assume.assumeFalse(testSpec.isRotated)
- super.navBarLayerIsAlwaysVisible()
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- Assume.assumeFalse(testSpec.isRotated)
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
-
- @FlakyTest
- @Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- Assume.assumeFalse(testSpec.isRotated)
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
- }
-
- @FlakyTest
- @Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ override fun navBarLayerRotatesAndScales() {
+ super.navBarLayerRotatesAndScales()
}
companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index e9f0534..cd5c61a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -40,10 +39,7 @@
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.wallpaperWindowBecomesInvisible
-import org.junit.Assume
import org.junit.Test
abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
@@ -88,14 +84,6 @@
@Presubmit
@Test
open fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
-
- @FlakyTest
- @Test
- open fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
}
@@ -114,27 +102,23 @@
@Presubmit
@Test
open fun statusBarLayerRotatesScales() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
-
- @FlakyTest
- @Test
- open fun statusBarLayerRotatesScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
}
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
}
- @FlakyTest
+ @Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
}
@Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index a8b5ea1..dcc64c9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.launch
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -25,7 +24,6 @@
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -63,16 +61,12 @@
}
}
- @FlakyTest
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 6985b36..f037f1d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -17,12 +17,14 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,6 +53,26 @@
}
}
+ @FlakyTest(bugId = 151179149)
+ @Test
+ override fun focusDoesNotChange() {
+ super.focusDoesNotChange()
+ }
+
+ @Presubmit
+ @Test
+ override fun navBarLayerRotatesAndScales() {
+ Assume.assumeFalse(testSpec.isRotated)
+ super.navBarLayerRotatesAndScales()
+ }
+
+ @FlakyTest(bugId = 140855415)
+ @Test
+ fun navBarLayerRotatesAndScales_flaky() {
+ Assume.assumeTrue(testSpec.isRotated)
+ super.navBarLayerRotatesAndScales()
+ }
+
@Presubmit
@Test
fun screenshotLayerBecomesInvisible() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 9d78eb8..6d2a923 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -18,7 +18,6 @@
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
@@ -28,18 +27,14 @@
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
import org.junit.Test
abstract class RotationTransition(protected val testSpec: FlickerTestParameter) {
@@ -50,12 +45,7 @@
protected val endingPos get() = WindowUtils.getDisplayBounds(testSpec.config.endRotation)
protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = {
- withTestName { testSpec.name }
- repeat { testSpec.config.repetitions }
setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
eachRun {
this.setRotation(testSpec.config.startRotation)
}
@@ -83,13 +73,13 @@
testSpec.navBarWindowIsAlwaysVisible()
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
open fun navBarLayerIsAlwaysVisible() {
testSpec.navBarLayerIsAlwaysVisible()
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
open fun navBarLayerRotatesAndScales() {
testSpec.navBarLayerRotatesAndScales(
@@ -102,28 +92,33 @@
testSpec.statusBarWindowIsAlwaysVisible()
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
open fun statusBarLayerIsAlwaysVisible() {
testSpec.statusBarLayerIsAlwaysVisible()
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
open fun statusBarLayerRotatesScales() {
testSpec.statusBarLayerRotatesScales(
testSpec.config.startRotation, testSpec.config.endRotation)
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
- open fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ testSpec.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+ }
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ testSpec.assertWm {
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
}
@Presubmit
@@ -133,13 +128,13 @@
testSpec.config.endRotation, allStates = false)
}
- @FlakyTest(bugId = 151179149)
+ @Presubmit
@Test
open fun focusDoesNotChange() {
testSpec.focusDoesNotChange()
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
open fun appLayerRotates_StartingPos() {
testSpec.assertLayersStart {
@@ -147,7 +142,7 @@
}
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
open fun appLayerRotates_EndingPos() {
testSpec.assertLayersEnd {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 45d3006..fe444bd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -22,7 +22,6 @@
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.appWindowAlwaysVisibleOnTop
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
import com.android.server.wm.flicker.layerAlwaysVisible
@@ -61,26 +60,24 @@
@FlakyTest(bugId = 140855415)
@Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+ override fun navBarLayerRotatesAndScales() {
+ super.navBarLayerRotatesAndScales()
+ }
@FlakyTest(bugId = 140855415)
@Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- @FlakyTest(bugId = 147659548)
- @Test
- override fun noUncoveredRegions() = super.noUncoveredRegions()
+ override fun statusBarLayerRotatesScales() {
+ super.statusBarLayerRotatesScales()
+ }
@Presubmit
@Test
- fun appWindowAlwaysVisibleOnTop() = testSpec.appWindowAlwaysVisibleOnTop(testApp.`package`)
+ fun appLayerAlwaysVisible() {
+ testSpec.layerAlwaysVisible(testApp.`package`)
+ }
@Presubmit
@Test
- fun layerAlwaysVisible() = testSpec.layerAlwaysVisible(testApp.`package`)
-
- @FlakyTest(bugId = 147659548)
- @Test
fun appLayerRotates() {
testSpec.assertLayers {
this.coversExactly(startingPos, testApp.`package`)
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 335c8d0..eacf5b2 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -9,14 +9,17 @@
android_test {
name: "InputTests",
- srcs: ["src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
platform_apis: true,
certificate: "platform",
static_libs: [
- "androidx.test.ext.junit",
- "androidx.test.rules",
- "truth-prebuilt",
- "ub-uiautomator",
- ],
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "ub-uiautomator",
+ ],
test_suites: ["device-tests"],
}
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
new file mode 100644
index 0000000..6350077
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceTest {
+ private static final float DELTA = 0.01f;
+ private static final int DEVICE_ID = 1000;
+
+ private void assertMotionRangeEquals(InputDevice.MotionRange range,
+ InputDevice.MotionRange outRange) {
+ assertEquals(range.getAxis(), outRange.getAxis());
+ assertEquals(range.getSource(), outRange.getSource());
+ assertEquals(range.getMin(), outRange.getMin(), DELTA);
+ assertEquals(range.getMax(), outRange.getMax(), DELTA);
+ assertEquals(range.getFlat(), outRange.getFlat(), DELTA);
+ assertEquals(range.getFuzz(), outRange.getFuzz(), DELTA);
+ assertEquals(range.getResolution(), outRange.getResolution(), DELTA);
+ }
+
+ private void assertDeviceEquals(InputDevice device, InputDevice outDevice) {
+ assertEquals(device.getId(), outDevice.getId());
+ assertEquals(device.getGeneration(), outDevice.getGeneration());
+ assertEquals(device.getControllerNumber(), outDevice.getControllerNumber());
+ assertEquals(device.getName(), outDevice.getName());
+ assertEquals(device.getVendorId(), outDevice.getVendorId());
+ assertEquals(device.getProductId(), outDevice.getProductId());
+ assertEquals(device.getDescriptor(), outDevice.getDescriptor());
+ assertEquals(device.isExternal(), outDevice.isExternal());
+ assertEquals(device.getSources(), outDevice.getSources());
+ assertEquals(device.getKeyboardType(), outDevice.getKeyboardType());
+ assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size());
+
+ KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap();
+ KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap();
+ assertTrue("keyCharacterMap not equal", keyCharacterMap.equals(outKeyCharacterMap));
+
+ for (int j = 0; j < device.getMotionRanges().size(); j++) {
+ assertMotionRangeEquals(device.getMotionRanges().get(j),
+ outDevice.getMotionRanges().get(j));
+ }
+ }
+
+ private void assertInputDeviceParcelUnparcel(KeyCharacterMap keyCharacterMap) {
+ final InputDevice device =
+ new InputDevice(DEVICE_ID, 0 /* generation */, 0 /* controllerNumber */, "name",
+ 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */,
+ 0 /* sources */, 0 /* keyboardType */, keyCharacterMap,
+ false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */,
+ true /* hasSensor */, false /* hasBattery */);
+
+ Parcel parcel = Parcel.obtain();
+ device.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ InputDevice outDevice = InputDevice.CREATOR.createFromParcel(parcel);
+ assertDeviceEquals(device, outDevice);
+ }
+
+ @Test
+ public void testParcelUnparcelInputDevice_VirtualCharacterMap() {
+ final KeyCharacterMap keyCharacterMap =
+ KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ assertInputDeviceParcelUnparcel(keyCharacterMap);
+ }
+
+ @Test
+ public void testParcelUnparcelInputDevice_EmptyCharacterMap() {
+ final KeyCharacterMap keyCharacterMap = KeyCharacterMap.obtainEmptyMap(DEVICE_ID);
+ assertInputDeviceParcelUnparcel(keyCharacterMap);
+ }
+}
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
index 2e985fb..b9b347b 100644
--- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -43,7 +43,7 @@
xPrecision, yPrecision, deviceId, edgeFlags, source, displayId)
}
-fun createKeyEvent(action: Int, eventTime: Long): KeyEvent {
+private fun createKeyEvent(action: Int, eventTime: Long): KeyEvent {
val code = KeyEvent.KEYCODE_A
val repeat = 0
return KeyEvent(eventTime, eventTime, action, code, repeat)
diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
new file mode 100644
index 0000000..4f95ce5
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.input
+
+import android.os.HandlerThread
+import android.os.Looper
+import android.view.InputChannel
+import android.view.InputEvent
+import android.view.InputEventReceiver
+import android.view.InputEventSender
+import android.view.KeyEvent
+import android.view.MotionEvent
+import java.util.concurrent.CountDownLatch
+import org.junit.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) {
+ assertEquals(expected.action, received.action)
+ assertEquals(expected.deviceId, received.deviceId)
+ assertEquals(expected.downTime, received.downTime)
+ assertEquals(expected.eventTime, received.eventTime)
+ assertEquals(expected.keyCode, received.keyCode)
+ assertEquals(expected.scanCode, received.scanCode)
+ assertEquals(expected.repeatCount, received.repeatCount)
+ assertEquals(expected.metaState, received.metaState)
+ assertEquals(expected.flags, received.flags)
+ assertEquals(expected.source, received.source)
+ assertEquals(expected.displayId, received.displayId)
+}
+
+class TestInputEventReceiver(channel: InputChannel, looper: Looper) :
+ InputEventReceiver(channel, looper) {
+ companion object {
+ const val TAG = "TestInputEventReceiver"
+ }
+
+ var lastEvent: InputEvent? = null
+
+ override fun onInputEvent(event: InputEvent) {
+ lastEvent = when (event) {
+ is KeyEvent -> KeyEvent.obtain(event)
+ is MotionEvent -> MotionEvent.obtain(event)
+ else -> throw Exception("Received $event is neither a key nor a motion")
+ }
+ finishInputEvent(event, true /*handled*/)
+ }
+}
+
+class TestInputEventSender(channel: InputChannel, looper: Looper) :
+ InputEventSender(channel, looper) {
+ companion object {
+ const val TAG = "TestInputEventSender"
+ }
+ data class FinishedResult(val seq: Int, val handled: Boolean)
+
+ private var mFinishedSignal = CountDownLatch(1)
+ override fun onInputEventFinished(seq: Int, handled: Boolean) {
+ finishedResult = FinishedResult(seq, handled)
+ mFinishedSignal.countDown()
+ }
+ lateinit var finishedResult: FinishedResult
+
+ fun waitForFinish() {
+ mFinishedSignal.await()
+ mFinishedSignal = CountDownLatch(1) // Ready for next event
+ }
+}
+
+class InputEventSenderAndReceiverTest {
+ companion object {
+ private const val TAG = "InputEventSenderAndReceiverTest"
+ }
+ private val mHandlerThread = HandlerThread("Process input events")
+ private lateinit var mReceiver: TestInputEventReceiver
+ private lateinit var mSender: TestInputEventSender
+
+ @Before
+ fun setUp() {
+ val channels = InputChannel.openInputChannelPair("TestChannel")
+ mHandlerThread.start()
+
+ val looper = mHandlerThread.getLooper()
+ mSender = TestInputEventSender(channels[0], looper)
+ mReceiver = TestInputEventReceiver(channels[1], looper)
+ }
+
+ @After
+ fun tearDown() {
+ mHandlerThread.quitSafely()
+ }
+
+ @Test
+ fun testSendAndReceiveKey() {
+ val key = KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_A, 0 /*repeat*/)
+ val seq = 10
+ mSender.sendInputEvent(seq, key)
+ mSender.waitForFinish()
+
+ // Check receiver
+ assertKeyEvent(key, mReceiver.lastEvent!! as KeyEvent)
+
+ // Check sender
+ assertEquals(seq, mSender.finishedResult.seq)
+ assertEquals(true, mSender.finishedResult.handled)
+ }
+}
diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
index e121b68..df58da5 100644
--- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
+++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
@@ -100,7 +100,6 @@
platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig),
packageName);
try {
- uiAutomation.dropShellPermissionIdentity();
mTestStatement.evaluate();
} finally {
adoptShellPermissions(uiAutomation);
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
index ce226fd..926ff4d 100644
--- a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -130,6 +130,9 @@
return result;
}
sBuffers[slot] = anb;
+ if (timeoutMs == 0) {
+ return android::OK;
+ }
android::sp<android::Fence> fence(new android::Fence(fenceFd));
int waitResult = fence->wait(timeoutMs);
if (waitResult != android::OK) {
@@ -197,6 +200,28 @@
return result;
}
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetAsyncMode(JNIEnv* /* env */,
+ jclass /* clazz */,
+ jboolean async) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ return surface->setAsyncMode(async);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetDequeueTimeout(
+ JNIEnv* /* env */, jclass /* clazz */, jlong timeoutMs) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ return surface->setDequeueTimeout(timeoutMs);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetMaxDequeuedBufferCount(
+ JNIEnv* /* env */, jclass /* clazz */, jint maxDequeuedBuffers) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ return surface->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+}
+
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetBufferCount(
JNIEnv* /* env */, jclass /* clazz */, jint count) {
assert(sAnw);
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
index 7d278dc..b67dc380 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
@@ -17,6 +17,7 @@
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -93,4 +94,80 @@
assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
}
+
+ @Test
+ // Leave IGBP in sync mode, try to dequeue and queue as fast as possible. Check that we
+ // occasionally get timeout errors.
+ fun testSyncMode_dequeueWithoutBlockingFails() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ var failures = false
+ for (i in 1..numFrames) {
+ if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+ failures = true
+ break
+ }
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ assertTrue(failures)
+ }
+ }
+
+ @Test
+ // Set IGBP to be in async mode, try to dequeue and queue as fast as possible. Client should be
+ // able to dequeue and queue buffers without being blocked.
+ fun testAsyncMode_dequeueWithoutBlocking() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+ for (i in 1..numFrames) {
+ assertEquals(0, activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */))
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ }
+ }
+
+ @Test
+ // Disable triple buffering in the system and leave IGBP in sync mode. Check that we
+ // occasionally get timeout errors.
+ fun testSyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ var failures = false
+ for (i in 1..numFrames) {
+ if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+ failures = true
+ break
+ }
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ assertTrue(failures)
+ }
+ }
+
+ @Test
+ // Disable triple buffering in the system and set IGBP to be in async mode. Try to dequeue and
+ // queue as fast as possible. Without triple buffering, the client does not have an extra buffer
+ // to dequeue and will not be able to dequeue and queue buffers without being blocked.
+ fun testAsyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+ var failures = false
+ for (i in 1..numFrames) {
+ if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+ failures = true
+ break
+ }
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ assertTrue(failures)
+ }
+ }
}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
index cfbd3ac..45a7094 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -54,7 +54,13 @@
external fun SurfaceSetScalingMode(scalingMode: Int)
external fun SurfaceDequeueBuffer(slot: Int, timeoutMs: Int): Int
external fun SurfaceCancelBuffer(slot: Int)
- external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true)
+ external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true): Int
+ external fun SurfaceSetAsyncMode(async: Boolean): Int
+ external fun SurfaceSetDequeueTimeout(timeout: Long): Int
+ external fun SurfaceQuery(what: Int): Int
+ external fun SurfaceSetMaxDequeuedBufferCount(maxDequeuedBuffers: Int): Int
+
+ // system/native_window.h functions
external fun NativeWindowSetBufferCount(count: Int): Int
external fun NativeWindowSetSharedBufferMode(shared: Boolean): Int
external fun NativeWindowSetAutoRefresh(autoRefresh: Boolean): Int
diff --git a/tests/net/common/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
index 9c0fc7c..50ecb42 100644
--- a/tests/net/common/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -113,6 +113,15 @@
p = new IpPrefix("f00:::/32");
fail("Expected IllegalArgumentException: invalid IPv6 address");
} catch (IllegalArgumentException expected) { }
+
+ p = new IpPrefix("/64");
+ assertEquals("::/64", p.toString());
+
+ p = new IpPrefix("/128");
+ assertEquals("::1/128", p.toString());
+
+ p = new IpPrefix("[2001:db8::123]/64");
+ assertEquals("2001:db8::/64", p.toString());
}
@Test
diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
index 1eaf30c..2cf3cf9 100644
--- a/tests/net/common/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -53,6 +53,7 @@
import org.junit.runner.RunWith;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -117,6 +118,20 @@
assertEquals(456, address.getScope());
assertTrue(address.isIpv4());
+ address = new LinkAddress("/64", 1 /* flags */, 2 /* scope */);
+ assertEquals(Inet6Address.LOOPBACK, address.getAddress());
+ assertEquals(64, address.getPrefixLength());
+ assertEquals(1, address.getFlags());
+ assertEquals(2, address.getScope());
+ assertTrue(address.isIpv6());
+
+ address = new LinkAddress("[2001:db8::123]/64", 3 /* flags */, 4 /* scope */);
+ assertEquals(InetAddresses.parseNumericAddress("2001:db8::123"), address.getAddress());
+ assertEquals(64, address.getPrefixLength());
+ assertEquals(3, address.getFlags());
+ assertEquals(4, address.getScope());
+ assertTrue(address.isIpv6());
+
// InterfaceAddress doesn't have a constructor. Fetch some from an interface.
List<InterfaceAddress> addrs = NetworkInterface.getByName("lo").getInterfaceAddresses();
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
index d04c87b..b7a42ec 100644
--- a/tests/net/java/android/net/VpnTransportInfoTest.java
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -42,7 +42,13 @@
VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE);
VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+ VpnTransportInfo v4 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY);
+ VpnTransportInfo v5 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM);
+
assertNotEquals(v1, v2);
+ assertNotEquals(v3, v4);
+ assertNotEquals(v4, v5);
+
assertEquals(v1, v3);
assertEquals(v1.hashCode(), v3.hashCode());
}
diff --git a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
index 8ea226d..b62bdbc 100644
--- a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.res.Resources
+import android.net.ConnectivityResources
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.MAX_TRANSPORT
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
@@ -26,13 +27,15 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import androidx.test.filters.SmallTest
import com.android.internal.R
+import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.any
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -47,21 +50,33 @@
class KeepaliveUtilsTest {
// Prepare mocked context with given resource strings.
- private fun getMockedContextWithStringArrayRes(id: Int, res: Array<out String?>?): Context {
+ private fun getMockedContextWithStringArrayRes(
+ id: Int,
+ name: String,
+ res: Array<out String?>?
+ ): Context {
val mockRes = mock(Resources::class.java)
- doReturn(res).`when`(mockRes).getStringArray(ArgumentMatchers.eq(id))
+ doReturn(res).`when`(mockRes).getStringArray(eq(id))
+ doReturn(id).`when`(mockRes).getIdentifier(eq(name), any(), any())
return mock(Context::class.java).apply {
doReturn(mockRes).`when`(this).getResources()
+ ConnectivityResources.setResourcesContextForTest(this)
}
}
+ @After
+ fun tearDown() {
+ ConnectivityResources.setResourcesContextForTest(null)
+ }
+
@Test
fun testGetSupportedKeepalives() {
fun assertRunWithException(res: Array<out String?>?) {
try {
val mockContext = getMockedContextWithStringArrayRes(
- R.array.config_networkSupportedKeepaliveCount, res)
+ R.array.config_networkSupportedKeepaliveCount,
+ "config_networkSupportedKeepaliveCount", res)
KeepaliveUtils.getSupportedKeepalives(mockContext)
fail("Expected KeepaliveDeviceConfigurationException")
} catch (expected: KeepaliveUtils.KeepaliveDeviceConfigurationException) {
@@ -89,7 +104,8 @@
val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0)
val mockContext = getMockedContextWithStringArrayRes(
- R.array.config_networkSupportedKeepaliveCount, validRes)
+ R.array.config_networkSupportedKeepaliveCount,
+ "config_networkSupportedKeepaliveCount", validRes)
val actual = KeepaliveUtils.getSupportedKeepalives(mockContext)
assertArrayEquals(expectedValidRes, actual)
}
diff --git a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
index c1315f6..25aa626 100644
--- a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
+++ b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
@@ -21,18 +21,20 @@
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
+import android.net.ConnectivityResources
+import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
+import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
import android.provider.Settings
-import android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI
-import android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.test.mock.MockContentResolver
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
-import com.android.internal.R
+import com.android.connectivity.resources.R
import com.android.internal.util.test.FakeSettingsProvider
+import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -41,6 +43,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -57,6 +60,8 @@
@SmallTest
class MultinetworkPolicyTrackerTest {
private val resources = mock(Resources::class.java).also {
+ doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
+ eq("config_networkAvoidBadWifi"), eq("integer"), any())
doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi)
}
private val telephonyManager = mock(TelephonyManager::class.java)
@@ -75,6 +80,7 @@
doReturn(resources).`when`(it).resources
doReturn(it).`when`(it).createConfigurationContext(any())
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
+ ConnectivityResources.setResourcesContextForTest(it)
}
private val tracker = MultinetworkPolicyTracker(context, null /* handler */)
@@ -85,6 +91,11 @@
assertEquals(preference, tracker.meteredMultipathPreference)
}
+ @After
+ fun tearDown() {
+ ConnectivityResources.setResourcesContextForTest(null)
+ }
+
@Test
fun testUpdateMeteredMultipathPreference() {
assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER)
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index fadd1ea..d70572d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,8 @@
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -35,11 +37,14 @@
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
@@ -82,10 +87,10 @@
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.RULE_ALLOW_METERED;
-import static android.net.NetworkPolicyManager.RULE_NONE;
-import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
-import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+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;
@@ -174,6 +179,8 @@
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.ConnectivityManager.PacketKeepaliveCallback;
import android.net.ConnectivityManager.TooManyRequestsException;
+import android.net.ConnectivityResources;
+import android.net.ConnectivitySettingsManager;
import android.net.ConnectivityThread;
import android.net.DataStallReportParcelable;
import android.net.EthernetManager;
@@ -182,8 +189,7 @@
import android.net.INetd;
import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
-import android.net.INetworkPolicyListener;
-import android.net.IOnSetOemNetworkPreferenceListener;
+import android.net.IOnCompleteListener;
import android.net.IQosCallback;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
@@ -201,7 +207,9 @@
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkPolicyManager;
+import android.net.NetworkPolicyManager.NetworkPolicyCallback;
import android.net.NetworkRequest;
+import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkStackClient;
@@ -275,7 +283,6 @@
import com.android.net.module.util.ArrayTrackRecord;
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.connectivity.ConnectivityConstants;
-import com.android.server.connectivity.ConnectivityResources;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
@@ -377,6 +384,11 @@
// Set a non-zero value to verify the flow to set tcp init rwnd value.
private static final int TEST_TCP_INIT_RWND = 60;
+ // Used for testing the per-work-profile default network.
+ private static final int TEST_APP_ID = 103;
+ private static final int TEST_WORK_PROFILE_USER_ID = 2;
+ private static final int TEST_WORK_PROFILE_APP_UID =
+ UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID);
private static final String CLAT_PREFIX = "v4-";
private static final String MOBILE_IFNAME = "test_rmnet_data0";
private static final String WIFI_IFNAME = "test_wlan0";
@@ -411,7 +423,7 @@
private TestNetworkAgentWrapper mEthernetNetworkAgent;
private MockVpn mMockVpn;
private Context mContext;
- private INetworkPolicyListener mPolicyListener;
+ private NetworkPolicyCallback mPolicyCallback;
private WrappedMultinetworkPolicyTracker mPolicyTracker;
private HandlerThread mAlarmManagerThread;
private TestNetIdManager mNetIdManager;
@@ -420,10 +432,10 @@
private VpnManagerService mVpnManagerService;
private TestNetworkCallback mDefaultNetworkCallback;
private TestNetworkCallback mSystemDefaultNetworkCallback;
+ private TestNetworkCallback mProfileDefaultNetworkCallback;
// State variables required to emulate NetworkPolicyManagerService behaviour.
- private int mUidRules = RULE_NONE;
- private boolean mRestrictBackground = false;
+ private int mBlockedReasons = BLOCKED_REASON_NONE;
@Mock DeviceIdleInternal mDeviceIdleInternal;
@Mock INetworkManagementService mNetworkManagementService;
@@ -541,13 +553,26 @@
return super.getSystemService(name);
}
+ final HashMap<UserHandle, UserManager> mUserManagers = new HashMap<>();
@Override
public Context createContextAsUser(UserHandle user, int flags) {
final Context asUser = mock(Context.class, AdditionalAnswers.delegatesTo(this));
doReturn(user).when(asUser).getUser();
+ doAnswer((inv) -> {
+ final UserManager um = mUserManagers.computeIfAbsent(user,
+ u -> mock(UserManager.class, AdditionalAnswers.delegatesTo(mUserManager)));
+ return um;
+ }).when(asUser).getSystemService(Context.USER_SERVICE);
return asUser;
}
+ public void setWorkProfile(@NonNull final UserHandle userHandle, boolean value) {
+ // This relies on all contexts for a given user returning the same UM mock
+ final UserManager umMock = createContextAsUser(userHandle, 0 /* flags */)
+ .getSystemService(UserManager.class);
+ doReturn(value).when(umMock).isManagedProfile();
+ }
+
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
@@ -1078,6 +1103,10 @@
public void triggerUnfulfillable(NetworkRequest r) {
super.releaseRequestAsUnfulfillableByAnyFactory(r);
}
+
+ public void assertNoRequestChanged() {
+ assertNull(mRequestHistory.poll(0, r -> true));
+ }
}
private Set<UidRange> uidRangesForUids(int... uids) {
@@ -1345,28 +1374,13 @@
}
private void mockUidNetworkingBlocked() {
- doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class)
- .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules,
- i.getArgument(1) /* metered */, mRestrictBackground)
+ doAnswer(i -> NetworkPolicyManager.isUidBlocked(mBlockedReasons, i.getArgument(1))
).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
-
- doAnswer(inv -> mContext.getSystemService(NetworkPolicyManager.class)
- .checkUidNetworkingBlocked(inv.getArgument(0) /* uid */,
- inv.getArgument(1) /* uidRules */,
- inv.getArgument(2) /* isNetworkMetered */,
- inv.getArgument(3) /* isBackgroundRestricted */)
- ).when(mNetworkPolicyManager).checkUidNetworkingBlocked(
- anyInt(), anyInt(), anyBoolean(), anyBoolean());
}
- private void setUidRulesChanged(int uidRules) throws RemoteException {
- mUidRules = uidRules;
- mPolicyListener.onUidRulesChanged(Process.myUid(), mUidRules);
- }
-
- private void setRestrictBackgroundChanged(boolean restrictBackground) throws RemoteException {
- mRestrictBackground = restrictBackground;
- mPolicyListener.onRestrictBackgroundChanged(mRestrictBackground);
+ private void setBlockedReasonChanged(int blockedReasons) {
+ mBlockedReasons = blockedReasons;
+ mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
}
private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) {
@@ -1403,17 +1417,36 @@
fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
}
- private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback,
- int uid) {
+ private <T> T doAsUid(final int uid, @NonNull final Supplier<T> what) {
when(mDeps.getCallingUid()).thenReturn(uid);
try {
- mCm.registerNetworkCallback(request, callback);
- waitForIdle();
+ return what.get();
} finally {
returnRealCallingUid();
}
}
+ private void doAsUid(final int uid, @NonNull final Runnable what) {
+ doAsUid(uid, () -> {
+ what.run(); return Void.TYPE;
+ });
+ }
+
+ private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback,
+ int uid) {
+ doAsUid(uid, () -> {
+ mCm.registerNetworkCallback(request, callback);
+ });
+ }
+
+ private void registerDefaultNetworkCallbackAsUid(@NonNull final NetworkCallback callback,
+ final int uid) {
+ doAsUid(uid, () -> {
+ mCm.registerDefaultNetworkCallback(callback);
+ waitForIdle();
+ });
+ }
+
private static final int PRIMARY_USER = 0;
private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100);
private static final int APP2_UID = UserHandle.getUid(PRIMARY_USER, 10101);
@@ -1460,6 +1493,9 @@
Looper.prepare();
}
mockDefaultPackages();
+ mockHasSystemFeature(FEATURE_WIFI, true);
+ mockHasSystemFeature(FEATURE_WIFI_DIRECT, true);
+ doReturn(true).when(mTelephonyManager).isDataCapable();
FakeSettingsProvider.clearSettingsProvider();
mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
@@ -1486,10 +1522,11 @@
mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS;
verify(mDeps).makeMultinetworkPolicyTracker(any(), any(), any());
- final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor =
- ArgumentCaptor.forClass(INetworkPolicyListener.class);
- verify(mNetworkPolicyManager).registerListener(policyListenerCaptor.capture());
- mPolicyListener = policyListenerCaptor.getValue();
+ final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkPolicyCallback.class);
+ verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(),
+ policyCallbackCaptor.capture());
+ mPolicyCallback = policyCallbackCaptor.getValue();
// Create local CM before sending system ready so that we can answer
// getSystemService() correctly.
@@ -1502,7 +1539,7 @@
mQosCallbackTracker = mock(QosCallbackTracker.class);
// Ensure that the default setting for Captive Portals is used for most tests
- setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+ setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
setAlwaysOnNetworks(false);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
}
@@ -1512,10 +1549,7 @@
}
private ConnectivityService.Dependencies makeDependencies() {
- doReturn(TEST_TCP_INIT_RWND).when(mSystemProperties)
- .getInt("net.tcp.default_init_rwnd", 0);
doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false);
- doNothing().when(mSystemProperties).setTcpInitRwnd(anyInt());
final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class);
doReturn(mCsHandlerThread).when(deps).makeHandlerThread();
doReturn(mNetIdManager).when(deps).makeNetIdManager();
@@ -1536,11 +1570,19 @@
com.android.connectivity.resources.R.string.config_networkCaptivePortalServerUrl);
doReturn(new String[]{ WIFI_WOL_IFNAME }).when(mResources).getStringArray(
com.android.connectivity.resources.R.array.config_wakeonlan_supported_interfaces);
- final com.android.server.connectivity.ConnectivityResources connRes = mock(
- ConnectivityResources.class);
+ 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());
+ final ConnectivityResources connRes = mock(ConnectivityResources.class);
doReturn(mResources).when(connRes).get();
doReturn(connRes).when(deps).getResources(any());
+ final Context mockResContext = mock(Context.class);
+ doReturn(mResources).when(mockResContext).getResources();
+ ConnectivityResources.setResourcesContextForTest(mockResContext);
+
return deps;
}
@@ -1573,6 +1615,7 @@
@After
public void tearDown() throws Exception {
unregisterDefaultNetworkCallbacks();
+ maybeTearDownEnterpriseNetwork();
setAlwaysOnNetworks(false);
if (mCellNetworkAgent != null) {
mCellNetworkAgent.disconnect();
@@ -1595,6 +1638,7 @@
waitForIdle();
FakeSettingsProvider.clearSettingsProvider();
+ ConnectivityResources.setResourcesContextForTest(null);
mCsHandlerThread.quitSafely();
mAlarmManagerThread.quitSafely();
@@ -1783,7 +1827,8 @@
assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
- assertFalse(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
+ assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
+ assertFalse(mCm.isNetworkSupported(TYPE_PROXY));
// Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
// mocks, this assert exercises the ConnectivityService code path that ensures that
@@ -3356,7 +3401,7 @@
.addCapability(NET_CAPABILITY_VALIDATED).build();
mCm.registerNetworkCallback(validatedRequest, validatedCallback);
- setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_AVOID);
+ setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID);
// Bring up a network with a captive portal.
// Expect it to fail to connect and not result in any callbacks.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -4006,20 +4051,21 @@
private void setCaptivePortalMode(int mode) {
ContentResolver cr = mServiceContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
+ Settings.Global.putInt(cr, ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE, mode);
}
private void setAlwaysOnNetworks(boolean enable) {
ContentResolver cr = mServiceContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
+ Settings.Global.putInt(cr, ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON,
+ enable ? 1 : 0);
mService.updateAlwaysOnNetworks();
waitForIdle();
}
private void setPrivateDnsSettings(String mode, String specifier) {
final ContentResolver cr = mServiceContext.getContentResolver();
- Settings.Global.putString(cr, Settings.Global.PRIVATE_DNS_MODE, mode);
- Settings.Global.putString(cr, Settings.Global.PRIVATE_DNS_SPECIFIER, specifier);
+ Settings.Global.putString(cr, ConnectivitySettingsManager.PRIVATE_DNS_MODE, mode);
+ Settings.Global.putString(cr, ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER, specifier);
mService.updatePrivateDnsSettings();
waitForIdle();
}
@@ -4257,7 +4303,7 @@
@Test
public void testAvoidBadWifiSetting() throws Exception {
final ContentResolver cr = mServiceContext.getContentResolver();
- final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
+ final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
String[] values = new String[] {null, "0", "1"};
@@ -4314,7 +4360,7 @@
TestNetworkCallback validatedWifiCallback = new TestNetworkCallback();
mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
- Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0);
+ Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 0);
mPolicyTracker.reevaluate();
// Bring up validated cell.
@@ -4382,7 +4428,7 @@
validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
// Simulate the user selecting "switch" and checking the don't ask again checkbox.
- Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
+ Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
mPolicyTracker.reevaluate();
// We now switch to cell.
@@ -4395,11 +4441,11 @@
// Simulate the user turning the cellular fallback setting off and then on.
// We switch to wifi and then to cell.
- Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
+ Settings.Global.putString(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
- Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
+ Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
@@ -4418,7 +4464,7 @@
@Test
public void testMeteredMultipathPreferenceSetting() throws Exception {
final ContentResolver cr = mServiceContext.getContentResolver();
- final String settingName = Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+ final String settingName = ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
for (int config : Arrays.asList(0, 3, 2)) {
for (String setting: Arrays.asList(null, "0", "2", "1")) {
@@ -7213,7 +7259,7 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
- setUidRulesChanged(RULE_REJECT_ALL);
+ setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7221,17 +7267,17 @@
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
// ConnectivityService should cache it not to invoke the callback again.
- setUidRulesChanged(RULE_REJECT_METERED);
+ setBlockedReasonChanged(BLOCKED_METERED_REASON_USER_RESTRICTED);
cellNetworkCallback.assertNoCallback();
- setUidRulesChanged(RULE_NONE);
+ setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
- setUidRulesChanged(RULE_REJECT_METERED);
+ setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7256,33 +7302,33 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
- setUidRulesChanged(RULE_ALLOW_METERED);
+ setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
- setUidRulesChanged(RULE_NONE);
+ setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.assertNoCallback();
// Restrict background data. Networking is not blocked because the network is unmetered.
- setRestrictBackgroundChanged(true);
+ setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
- setRestrictBackgroundChanged(true);
+ setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
cellNetworkCallback.assertNoCallback();
- setUidRulesChanged(RULE_ALLOW_METERED);
+ setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
- setRestrictBackgroundChanged(false);
+ setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.assertNoCallback();
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
@@ -7299,9 +7345,9 @@
mockUidNetworkingBlocked();
// No Networkcallbacks invoked before any network is active.
- setUidRulesChanged(RULE_REJECT_ALL);
- setUidRulesChanged(RULE_NONE);
- setUidRulesChanged(RULE_REJECT_METERED);
+ setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER);
+ setBlockedReasonChanged(BLOCKED_REASON_NONE);
+ setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
defaultCallback.assertNoCallback();
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -7326,8 +7372,8 @@
defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
// Verify there's no Networkcallbacks invoked after data saver on/off.
- setRestrictBackgroundChanged(true);
- setRestrictBackgroundChanged(false);
+ setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
+ setBlockedReasonChanged(BLOCKED_REASON_NONE);
defaultCallback.assertNoCallback();
mCellNetworkAgent.disconnect();
@@ -7992,7 +8038,6 @@
// Switching default network updates TCP buffer sizes.
verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
- verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND));
// Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that
// the NAT64 prefix was removed because one was never discovered.
cellLp.addLinkAddress(myIpv4);
@@ -8478,14 +8523,12 @@
mCellNetworkAgent.connect(false);
networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
- verify(mSystemProperties, times(1)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND));
// Change link Properties should have updated tcp buffer size.
LinkProperties lp = new LinkProperties();
lp.setTcpBufferSizes(testTcpBufferSizes);
mCellNetworkAgent.sendLinkProperties(lp);
networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
verifyTcpBufferSizeChange(testTcpBufferSizes);
- verify(mSystemProperties, times(2)).setTcpInitRwnd(eq(TEST_TCP_INIT_RWND));
// Clean up.
mCellNetworkAgent.disconnect();
networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
@@ -9160,7 +9203,8 @@
ConnectivityManager.getNetworkTypeName(TYPE_MOBILE),
TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE));
return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(),
- nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0,
+ nc, new NetworkScore.Builder().setLegacyInt(0).build(),
+ mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0,
INVALID_UID, mQosCallbackTracker, new ConnectivityService.Dependencies());
}
@@ -10112,9 +10156,12 @@
Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
mSystemDefaultNetworkCallback = new TestNetworkCallback();
mDefaultNetworkCallback = new TestNetworkCallback();
+ mProfileDefaultNetworkCallback = new TestNetworkCallback();
mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback,
new Handler(ConnectivityThread.getInstanceLooper()));
mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
+ registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
+ TEST_WORK_PROFILE_APP_UID);
mServiceContext.setPermission(
Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
}
@@ -10126,6 +10173,9 @@
if (null != mSystemDefaultNetworkCallback) {
mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback);
}
+ if (null != mProfileDefaultNetworkCallback) {
+ mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback);
+ }
}
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
@@ -10181,7 +10231,7 @@
oemPrefListener.expectOnComplete();
}
- private static class TestOemListenerCallback implements IOnSetOemNetworkPreferenceListener {
+ private static class TestOemListenerCallback implements IOnCompleteListener {
final CompletableFuture<Object> mDone = new CompletableFuture<>();
@Override
@@ -11119,11 +11169,494 @@
mCm.unregisterNetworkCallback(cellCb);
}
+ // Cannot be part of MockNetworkFactory since it requires method of the test.
+ private void expectNoRequestChanged(@NonNull MockNetworkFactory factory) {
+ waitForIdle();
+ factory.assertNoRequestChanged();
+ }
+
@Test
- public void testRegisterBestMatchingNetworkCallback() throws Exception {
- final NetworkRequest request = new NetworkRequest.Builder().build();
- assertThrows(UnsupportedOperationException.class,
- () -> mCm.registerBestMatchingNetworkCallback(request, new NetworkCallback(),
- mCsHandlerThread.getThreadHandler()));
+ public void testRegisterBestMatchingNetworkCallback_noIssueToFactory() throws Exception {
+ // Prepare mock mms factory.
+ final HandlerThread handlerThread = new HandlerThread("MockCellularFactory");
+ handlerThread.start();
+ NetworkCapabilities filter = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_MMS);
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter, mCsHandlerThread);
+ testFactory.setScoreFilter(40);
+
+ try {
+ // Register the factory and expect it will see default request, because all requests
+ // are sent to all factories.
+ testFactory.register();
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
+ // The factory won't try to start the network since the default request doesn't
+ // match the filter (no INTERNET capability).
+ assertFalse(testFactory.getMyStartRequested());
+
+ // Register callback for listening best matching network. Verify that the request won't
+ // be sent to factory.
+ final TestNetworkCallback bestMatchingCb = new TestNetworkCallback();
+ mCm.registerBestMatchingNetworkCallback(
+ new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(),
+ bestMatchingCb, mCsHandlerThread.getThreadHandler());
+ bestMatchingCb.assertNoCallback();
+ expectNoRequestChanged(testFactory);
+ testFactory.assertRequestCountEquals(1);
+ assertFalse(testFactory.getMyStartRequested());
+
+ // Fire a normal mms request, verify the factory will only see the request.
+ final TestNetworkCallback mmsNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest mmsRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_MMS).build();
+ mCm.requestNetwork(mmsRequest, mmsNetworkCallback);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(2);
+ assertTrue(testFactory.getMyStartRequested());
+
+ // Unregister best matching callback, verify factory see no change.
+ mCm.unregisterNetworkCallback(bestMatchingCb);
+ expectNoRequestChanged(testFactory);
+ testFactory.assertRequestCountEquals(2);
+ assertTrue(testFactory.getMyStartRequested());
+ } finally {
+ testFactory.terminate();
+ }
+ }
+
+ @Test
+ public void testRegisterBestMatchingNetworkCallback_trackBestNetwork() throws Exception {
+ final TestNetworkCallback bestMatchingCb = new TestNetworkCallback();
+ mCm.registerBestMatchingNetworkCallback(
+ new NetworkRequest.Builder().addCapability(NET_CAPABILITY_TRUSTED).build(),
+ bestMatchingCb, mCsHandlerThread.getThreadHandler());
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ bestMatchingCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+
+ // Change something on cellular to trigger capabilities changed, since the callback
+ // only cares about the best network, verify it received nothing from cellular.
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ bestMatchingCb.assertNoCallback();
+
+ // Make cellular the best network again, verify the callback now tracks cellular.
+ mWiFiNetworkAgent.adjustScore(-50);
+ bestMatchingCb.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ // Make cellular temporary non-trusted, which will not satisfying the request.
+ // Verify the callback switch from/to the other network accordingly.
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED);
+ bestMatchingCb.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_TRUSTED);
+ bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mCellNetworkAgent);
+
+ // Verify the callback doesn't care about wifi disconnect.
+ mWiFiNetworkAgent.disconnect();
+ bestMatchingCb.assertNoCallback();
+ mCellNetworkAgent.disconnect();
+ bestMatchingCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ }
+
+ private UidRangeParcel[] uidRangeFor(final UserHandle handle) {
+ UidRange range = UidRange.createForUser(handle);
+ return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) };
+ }
+
+ private static class TestOnCompleteListener implements Runnable {
+ final class OnComplete {}
+ final ArrayTrackRecord<OnComplete>.ReadHead mHistory =
+ new ArrayTrackRecord<OnComplete>().newReadHead();
+
+ @Override
+ public void run() {
+ mHistory.add(new OnComplete());
+ }
+
+ public void expectOnComplete() {
+ assertNotNull(mHistory.poll(TIMEOUT_MS, it -> true));
+ }
+ }
+
+ private TestNetworkAgentWrapper makeEnterpriseNetworkAgent() throws Exception {
+ final NetworkCapabilities workNc = new NetworkCapabilities();
+ workNc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
+ }
+
+ private TestNetworkCallback mEnterpriseCallback;
+ private UserHandle setupEnterpriseNetwork() {
+ final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+ mServiceContext.setWorkProfile(userHandle, true);
+
+ // File a request to avoid the enterprise network being disconnected as soon as the default
+ // request goes away – it would make impossible to test that networkRemoveUidRanges
+ // is called, as the network would disconnect first for lack of a request.
+ mEnterpriseCallback = new TestNetworkCallback();
+ final NetworkRequest keepUpRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_ENTERPRISE)
+ .build();
+ mCm.requestNetwork(keepUpRequest, mEnterpriseCallback);
+ return userHandle;
+ }
+
+ private void maybeTearDownEnterpriseNetwork() {
+ if (null != mEnterpriseCallback) {
+ mCm.unregisterNetworkCallback(mEnterpriseCallback);
+ }
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active. Make sure they behave as expected whether
+ * there is a general default network or not.
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDown() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ registerDefaultNetworkCallbacks();
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+ INetd.PERMISSION_NONE);
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+
+ // Setting a network preference for this user will create a new set of routing rules for
+ // the UID range that corresponds to this user, so as to define the default network
+ // for these apps separately. This is true because the multi-layer request relevant to
+ // this UID range contains a TRACK_DEFAULT, so the range will be moved through UID-specific
+ // rules to the correct network – in this case the system default network. The case where
+ // the default network for the profile happens to be the same as the system default
+ // is not handled specially, the rules are always active as long as a preference is set.
+ inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+
+ // The enterprise network is not ready yet.
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+ mProfileDefaultNetworkCallback);
+
+ final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+ workAgent.connect(false);
+
+ mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent);
+ mSystemDefaultNetworkCallback.assertNoCallback();
+ mDefaultNetworkCallback.assertNoCallback();
+ inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId,
+ INetd.PERMISSION_SYSTEM);
+ inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+ inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+
+ // Make sure changes to the work agent send callbacks to the app in the work profile, but
+ // not to the other apps.
+ workAgent.setNetworkValid(true /* isStrictMode */);
+ workAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
+ nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
+ && nc.hasCapability(NET_CAPABILITY_ENTERPRISE));
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+
+ workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc ->
+ nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+
+ // Conversely, change a capability on the system-wide default network and make sure
+ // that only the apps outside of the work profile receive the callbacks.
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
+ nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
+ nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ mProfileDefaultNetworkCallback.assertNoCallback();
+
+ // Disconnect and reconnect the system-wide default network and make sure that the
+ // apps on this network see the appropriate callbacks, and the app on the work profile
+ // doesn't because it continues to use the enterprise network.
+ mCellNetworkAgent.disconnect();
+ mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mProfileDefaultNetworkCallback.assertNoCallback();
+ inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mProfileDefaultNetworkCallback.assertNoCallback();
+ inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+ INetd.PERMISSION_NONE);
+
+ // When the agent disconnects, test that the app on the work profile falls back to the
+ // default network.
+ workAgent.disconnect();
+ mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent);
+ mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+ inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId);
+
+ mCellNetworkAgent.disconnect();
+ mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+
+ // Waiting for the handler to be idle before checking for networkDestroy is necessary
+ // here because ConnectivityService calls onLost before the network is fully torn down.
+ waitForIdle();
+ inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);
+
+ // If the control comes here, callbacks seem to behave correctly in the presence of
+ // a default network when the enterprise network goes up and down. Now, make sure they
+ // also behave correctly in the absence of a system-wide default network.
+ final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent();
+ workAgent2.connect(false);
+
+ mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ inOrder.verify(mMockNetd).networkCreatePhysical(workAgent2.getNetwork().netId,
+ INetd.PERMISSION_SYSTEM);
+ inOrder.verify(mMockNetd).networkAddUidRanges(workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle));
+
+ workAgent2.setNetworkValid(true /* isStrictMode */);
+ workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
+ mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
+ nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
+ && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ inOrder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
+
+ // When the agent disconnects, test that the app on the work profile falls back to the
+ // default network.
+ workAgent2.disconnect();
+ mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId);
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+ mProfileDefaultNetworkCallback);
+
+ // Callbacks will be unregistered by tearDown()
+ }
+
+ /**
+ * Test that, in a given networking context, calling setPreferenceForUser to set per-profile
+ * defaults on then off works as expected.
+ */
+ @Test
+ public void testSetPreferenceForUserOnOff() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+
+ // Connect both a regular cell agent and an enterprise network first.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+ workAgent.connect(true);
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+ INetd.PERMISSION_NONE);
+ inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+
+ registerDefaultNetworkCallbacks();
+
+ mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
+
+ mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+
+ mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+
+ workAgent.disconnect();
+ mCellNetworkAgent.disconnect();
+
+ // Callbacks will be unregistered by tearDown()
+ }
+
+ /**
+ * Test per-profile default networks for two different profiles concurrently.
+ */
+ @Test
+ public void testSetPreferenceForTwoProfiles() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle2 = setupEnterpriseNetwork();
+ final UserHandle testHandle4 = UserHandle.of(TEST_WORK_PROFILE_USER_ID + 2);
+ mServiceContext.setWorkProfile(testHandle4, true);
+ registerDefaultNetworkCallbacks();
+
+ final TestNetworkCallback app4Cb = new TestNetworkCallback();
+ final int testWorkProfileAppUid4 =
+ UserHandle.getUid(testHandle4.getIdentifier(), TEST_APP_ID);
+ registerDefaultNetworkCallbackAsUid(app4Cb, testWorkProfileAppUid4);
+
+ // Connect both a regular cell agent and an enterprise network first.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+ workAgent.connect(true);
+
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ app4Cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+ INetd.PERMISSION_NONE);
+ inOrder.verify(mMockNetd).networkCreatePhysical(workAgent.getNetwork().netId,
+ INetd.PERMISSION_SYSTEM);
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+ uidRangeFor(testHandle2));
+
+ mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+ app4Cb);
+
+ mCm.setProfileNetworkPreference(testHandle4, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId,
+ uidRangeFor(testHandle4));
+
+ app4Cb.expectAvailableCallbacksValidated(workAgent);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+ mProfileDefaultNetworkCallback);
+
+ mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_DEFAULT,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId,
+ uidRangeFor(testHandle2));
+
+ mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
+ app4Cb);
+
+ workAgent.disconnect();
+ mCellNetworkAgent.disconnect();
+
+ mCm.unregisterNetworkCallback(app4Cb);
+ // Other callbacks will be unregistered by tearDown()
+ }
+
+ @Test
+ public void testProfilePreferenceRemovedUponUserRemoved() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ inOrder.verify(mMockNetd).networkCreatePhysical(mCellNetworkAgent.getNetwork().netId,
+ INetd.PERMISSION_NONE);
+ inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+
+ final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
+ removedIntent.putExtra(Intent.EXTRA_USER, testHandle);
+ processBroadcast(removedIntent);
+
+ inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId,
+ uidRangeFor(testHandle));
+ }
+
+ /**
+ * Make sure that OEM preference and per-profile preference can't be used at the same
+ * time and throw ISE if tried
+ */
+ @Test
+ public void testOemPreferenceAndProfilePreferenceExclusive() throws Exception {
+ final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+ mServiceContext.setWorkProfile(testHandle, true);
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+ setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
+ OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY);
+ assertThrows("Should not be able to set per-profile pref while OEM prefs present",
+ IllegalStateException.class, () ->
+ mCm.setProfileNetworkPreference(testHandle,
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener));
+
+ // Empty the OEM prefs
+ final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback();
+ final OemNetworkPreferences emptyOemPref = new OemNetworkPreferences.Builder().build();
+ mService.setOemNetworkPreference(emptyOemPref, oemPrefListener);
+ oemPrefListener.expectOnComplete();
+
+ mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ assertThrows("Should not be able to set OEM prefs while per-profile pref is on",
+ IllegalStateException.class , () ->
+ mService.setOemNetworkPreference(emptyOemPref, oemPrefListener));
+ }
+
+ /**
+ * Make sure wrong preferences for per-profile default networking are rejected.
+ */
+ @Test
+ public void testProfileNetworkPrefWrongPreference() throws Exception {
+ final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+ mServiceContext.setWorkProfile(testHandle, true);
+ assertThrows("Should not be able to set an illegal preference",
+ IllegalArgumentException.class,
+ () -> mCm.setProfileNetworkPreference(testHandle,
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE + 1, null, null));
+ }
+
+ /**
+ * Make sure requests for per-profile default networking for a non-work profile are
+ * rejected
+ */
+ @Test
+ public void testProfileNetworkPrefWrongProfile() throws Exception {
+ final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+ mServiceContext.setWorkProfile(testHandle, false);
+ assertThrows("Should not be able to set a user pref for a non-work profile",
+ IllegalArgumentException.class , () ->
+ mCm.setProfileNetworkPreference(testHandle,
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null));
}
}
diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
index a10a3c8..5ec1119 100644
--- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
+++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -21,13 +21,29 @@
package com.android.server
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_WIFI
+import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT
import android.net.ConnectivityManager.TYPE_ETHERNET
import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_MOBILE_CBS
+import android.net.ConnectivityManager.TYPE_MOBILE_DUN
+import android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY
+import android.net.ConnectivityManager.TYPE_MOBILE_FOTA
+import android.net.ConnectivityManager.TYPE_MOBILE_HIPRI
+import android.net.ConnectivityManager.TYPE_MOBILE_IA
+import android.net.ConnectivityManager.TYPE_MOBILE_IMS
+import android.net.ConnectivityManager.TYPE_MOBILE_MMS
import android.net.ConnectivityManager.TYPE_MOBILE_SUPL
+import android.net.ConnectivityManager.TYPE_VPN
import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.ConnectivityManager.TYPE_WIFI_P2P
import android.net.ConnectivityManager.TYPE_WIMAX
+import android.net.EthernetManager
import android.net.NetworkInfo.DetailedState.CONNECTED
import android.net.NetworkInfo.DetailedState.DISCONNECTED
+import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.server.ConnectivityService.LegacyTypeTracker
@@ -36,7 +52,6 @@
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
@@ -52,88 +67,130 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
class LegacyTypeTrackerTest {
- private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_SUPL)
+ private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE,
+ TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI,
+ TYPE_MOBILE_FOTA, TYPE_MOBILE_IMS, TYPE_MOBILE_CBS, TYPE_MOBILE_IA,
+ TYPE_MOBILE_EMERGENCY, TYPE_VPN)
private val mMockService = mock(ConnectivityService::class.java).apply {
doReturn(false).`when`(this).isDefaultNetwork(any())
}
- private val mTracker = LegacyTypeTracker(mMockService).apply {
- supportedTypes.forEach {
- addSupportedType(it)
- }
+ private val mPm = mock(PackageManager::class.java)
+ private val mContext = mock(Context::class.java).apply {
+ doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI)
+ doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
+ doReturn(mPm).`when`(this).packageManager
+ doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService(
+ Context.ETHERNET_SERVICE)
+ }
+ private val mTm = mock(TelephonyManager::class.java).apply {
+ doReturn(true).`when`(this).isDataCapable
+ }
+
+ private fun makeTracker() = LegacyTypeTracker(mMockService).apply {
+ loadSupportedTypes(mContext, mTm)
}
@Test
fun testSupportedTypes() {
- try {
- mTracker.addSupportedType(supportedTypes[0])
- fail("Expected IllegalStateException")
- } catch (expected: IllegalStateException) {}
+ val tracker = makeTracker()
supportedTypes.forEach {
- assertTrue(mTracker.isTypeSupported(it))
+ assertTrue(tracker.isTypeSupported(it))
}
- assertFalse(mTracker.isTypeSupported(UNSUPPORTED_TYPE))
+ assertFalse(tracker.isTypeSupported(UNSUPPORTED_TYPE))
+ }
+
+ @Test
+ fun testSupportedTypes_NoEthernet() {
+ doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE)
+ assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET))
+ }
+
+ @Test
+ fun testSupportedTypes_NoTelephony() {
+ doReturn(false).`when`(mTm).isDataCapable
+ val tracker = makeTracker()
+ val nonMobileTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_VPN)
+ nonMobileTypes.forEach {
+ assertTrue(tracker.isTypeSupported(it))
+ }
+ supportedTypes.toSet().minus(nonMobileTypes).forEach {
+ assertFalse(tracker.isTypeSupported(it))
+ }
+ }
+
+ @Test
+ fun testSupportedTypes_NoWifiDirect() {
+ doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT)
+ val tracker = makeTracker()
+ assertFalse(tracker.isTypeSupported(TYPE_WIFI_P2P))
+ supportedTypes.toSet().minus(TYPE_WIFI_P2P).forEach {
+ assertTrue(tracker.isTypeSupported(it))
+ }
}
@Test
fun testSupl() {
+ val tracker = makeTracker()
val mobileNai = mock(NetworkAgentInfo::class.java)
- mTracker.add(TYPE_MOBILE, mobileNai)
+ tracker.add(TYPE_MOBILE, mobileNai)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE)
reset(mMockService)
- mTracker.add(TYPE_MOBILE_SUPL, mobileNai)
+ tracker.add(TYPE_MOBILE_SUPL, mobileNai)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
reset(mMockService)
- mTracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */)
+ tracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
reset(mMockService)
- mTracker.add(TYPE_MOBILE_SUPL, mobileNai)
+ tracker.add(TYPE_MOBILE_SUPL, mobileNai)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL)
reset(mMockService)
- mTracker.remove(mobileNai, false)
+ tracker.remove(mobileNai, false)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE)
}
@Test
fun testAddNetwork() {
+ val tracker = makeTracker()
val mobileNai = mock(NetworkAgentInfo::class.java)
val wifiNai = mock(NetworkAgentInfo::class.java)
- mTracker.add(TYPE_MOBILE, mobileNai)
- mTracker.add(TYPE_WIFI, wifiNai)
- assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
- assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ tracker.add(TYPE_MOBILE, mobileNai)
+ tracker.add(TYPE_WIFI, wifiNai)
+ assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+ assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
// Make sure adding a second NAI does not change the results.
val secondMobileNai = mock(NetworkAgentInfo::class.java)
- mTracker.add(TYPE_MOBILE, secondMobileNai)
- assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
- assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ tracker.add(TYPE_MOBILE, secondMobileNai)
+ assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+ assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
// Make sure removing a network that wasn't added for this type is a no-op.
- mTracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
- assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
- assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ tracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
+ assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+ assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
// Remove the top network for mobile and make sure the second one becomes the network
// of record for this type.
- mTracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
- assertSame(mTracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
- assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ tracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
+ assertSame(tracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
+ assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai)
// Make sure adding a network for an unsupported type does not register it.
- mTracker.add(UNSUPPORTED_TYPE, mobileNai)
- assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE))
+ tracker.add(UNSUPPORTED_TYPE, mobileNai)
+ assertNull(tracker.getNetworkForType(UNSUPPORTED_TYPE))
}
@Test
fun testBroadcastOnDisconnect() {
+ val tracker = makeTracker()
val mobileNai1 = mock(NetworkAgentInfo::class.java)
val mobileNai2 = mock(NetworkAgentInfo::class.java)
doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1)
- mTracker.add(TYPE_MOBILE, mobileNai1)
+ tracker.add(TYPE_MOBILE, mobileNai1)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE)
reset(mMockService)
doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2)
- mTracker.add(TYPE_MOBILE, mobileNai2)
+ tracker.add(TYPE_MOBILE, mobileNai2)
verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt())
- mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
+ tracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE)
verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE)
}
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 5760211..692c50f 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -18,15 +18,15 @@
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER;
import static android.net.NetworkCapabilities.MAX_TRANSPORT;
import static android.net.NetworkCapabilities.MIN_TRANSPORT;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
-import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
-import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static com.android.testutils.MiscAsserts.assertContainsExactly;
import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
@@ -312,14 +312,14 @@
@Test
public void testOverrideDefaultMode() throws Exception {
// Hard-coded default is opportunistic mode.
- final PrivateDnsConfig cfgAuto = DnsManager.getPrivateDnsConfig(mContentResolver);
+ final PrivateDnsConfig cfgAuto = DnsManager.getPrivateDnsConfig(mCtx);
assertTrue(cfgAuto.useTls);
assertEquals("", cfgAuto.hostname);
assertEquals(new InetAddress[0], cfgAuto.ips);
// Pretend a gservices push sets the default to "off".
Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, "off");
- final PrivateDnsConfig cfgOff = DnsManager.getPrivateDnsConfig(mContentResolver);
+ final PrivateDnsConfig cfgOff = DnsManager.getPrivateDnsConfig(mCtx);
assertFalse(cfgOff.useTls);
assertEquals("", cfgOff.hostname);
assertEquals(new InetAddress[0], cfgOff.ips);
@@ -328,7 +328,7 @@
Settings.Global.putString(
mContentResolver, PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com");
- final PrivateDnsConfig cfgStrict = DnsManager.getPrivateDnsConfig(mContentResolver);
+ final PrivateDnsConfig cfgStrict = DnsManager.getPrivateDnsConfig(mCtx);
assertTrue(cfgStrict.useTls);
assertEquals("strictmode.com", cfgStrict.hostname);
assertEquals(new InetAddress[0], cfgStrict.ips);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 1c0ba4f..ea2b362 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -40,6 +40,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkProvider;
+import android.net.NetworkScore;
import android.os.Binder;
import android.text.format.DateUtils;
@@ -355,8 +356,9 @@
caps.addCapability(0);
caps.addTransportType(transport);
NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info,
- new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */,
- mConnService, mNetd, mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(),
+ new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(),
+ mCtx, null, new NetworkAgentConfig() /* config */, mConnService, mNetd,
+ mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(),
mQosCallbackTracker, new ConnectivityService.Dependencies());
nai.everValidated = true;
return nai;
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index b8f7fbc..11fcea6 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -1026,7 +1026,11 @@
.thenReturn(new Network[] { new Network(101) });
when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
- anyInt(), any(), anyInt())).thenReturn(new Network(102));
+ any(), any(), anyInt())).thenAnswer(invocation -> {
+ // The runner has registered an agent and is now ready.
+ legacyRunnerReady.open();
+ return new Network(102);
+ });
final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile);
final TestDeps deps = (TestDeps) vpn.mDeps;
try {
@@ -1048,7 +1052,7 @@
ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
- lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt());
+ lpCaptor.capture(), ncCaptor.capture(), any(), any(), anyInt());
// In this test the expected address is always v4 so /32.
// Note that the interface needs to be specified because RouteInfo objects stored in
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 a07bce3..8932892 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
@@ -30,12 +30,21 @@
* -e selectTest_verbose true \
* com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
* </pre>
+ *
+ * <p>Use this filter when running FrameworksMockingCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.server.wm.test.filters.FrameworksTestsFilter \
+ * -e selectTest_verbose true \
+ * com.android.frameworks.mockingcoretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
*/
public final class FrameworksTestsFilter extends SelectTest {
private static final String[] SELECTED_TESTS = {
// Test specifications for FrameworksMockingCoreTests.
"android.app.activity.ActivityThreadClientTest",
+ "android.view.DisplayTest",
// Test specifications for FrameworksCoreTests.
"android.app.servertransaction.", // all tests under the package.
"android.view.CutoutSpecificationTest",
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 5b17aad..8a0c923 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -109,16 +109,6 @@
}
@Test
- public void testBuilderRequiresNonEmptyUnderlyingCaps() {
- try {
- newBuilder().addExposedCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
-
- fail("Expected exception due to invalid required underlying capabilities");
- } catch (IllegalArgumentException e) {
- }
- }
-
- @Test
public void testBuilderRequiresNonNullRetryInterval() {
try {
newBuilder().setRetryInterval(null);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 11498de..814cad4 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -74,7 +74,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.LocationPermissionChecker;
+import com.android.net.module.util.LocationPermissionChecker;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.VcnManagementService.VcnStatusCallbackInfo;
import com.android.server.vcn.TelephonySubscriptionTracker;
@@ -593,6 +593,16 @@
mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
}
+ @Test(expected = SecurityException.class)
+ public void testRemoveVcnUnderlyingNetworkPolicyListenerInvalidPermission() {
+ doThrow(new SecurityException())
+ .when(mMockContext)
+ .enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.NETWORK_FACTORY), any());
+
+ mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+ }
+
@Test
public void testRemoveVcnUnderlyingNetworkPolicyListenerNeverRegistered() {
mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 0e5f5e4..ca6448c 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -188,7 +188,7 @@
any(),
lpCaptor.capture(),
ncCaptor.capture(),
- anyInt(),
+ any(),
any(),
anyInt());
verify(mIpSecSvc)